Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {TextInputProps} from '../TextInput'
import TextInput from '../TextInput'
import {get} from '../constants'
import {ActionList} from '../deprecated/ActionList'
import type {GroupedListProps, ListPropsBase} from '../deprecated/ActionList/List'
import type {GroupedListProps, ListPropsBase} from '../SelectPanel/types'
Copy link
Member

@siddharthkp siddharthkp Aug 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(non blocker) Should we bring FilteredActionList into src/SelectPanel, it's not used anywhere else. (And we probably don't want it to be used anywhere else?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense to me! Perhaps in a follow-up PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think we exported it recently? #4816

This comment was marked as outdated.

import {useFocusZone} from '../hooks/useFocusZone'
import {useId} from '../hooks/useId'
import {useProvidedRefOrCreate} from '../hooks/useProvidedRefOrCreate'
Expand Down
244 changes: 244 additions & 0 deletions packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
import React, {useState} from 'react'
import Box from '../Box'
import type {Meta} from '@storybook/react'
import {Button} from '../Button'
import type {ItemInput} from '../deprecated/ActionList/List'
import {SelectPanel} from './SelectPanel'
import type {OverlayProps} from '../Overlay'
import {TriangleDownIcon} from '@primer/octicons-react'

const meta = {
title: 'Components/SelectPanel/Examples',
component: SelectPanel,
} satisfies Meta<typeof SelectPanel>

export default meta

function getColorCircle(color: string) {
return function () {
return (
<Box
bg={color}
borderColor={color}
width={14}
height={14}
borderRadius={10}
margin="auto"
borderWidth="1px"
borderStyle="solid"
/>
)
}
}

const items = [
{leadingVisual: getColorCircle('#a2eeef'), text: 'enhancement', id: 1},
{leadingVisual: getColorCircle('#d73a4a'), text: 'bug', id: 2},
{leadingVisual: getColorCircle('#0cf478'), text: 'good first issue', id: 3},
{leadingVisual: getColorCircle('#ffd78e'), text: 'design', id: 4},
{leadingVisual: getColorCircle('#ff0000'), text: 'blocker', id: 5},
{leadingVisual: getColorCircle('#a4f287'), text: 'backend', id: 6},
{leadingVisual: getColorCircle('#8dc6fc'), text: 'frontend', id: 7},
]

export const HeightInitialWithOverflowingItemsStory = () => {
const [selected, setSelected] = React.useState<ItemInput | undefined>(items[0])
const [filter, setFilter] = React.useState('')
const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase()))
const [open, setOpen] = useState(false)

return (
<>
<h1>Single Select Panel</h1>
<div>Please select a label that describe your issue:</div>
<SelectPanel
renderAnchor={({children, 'aria-labelledby': ariaLabelledBy, ...anchorProps}) => (
<Button trailingAction={TriangleDownIcon} aria-labelledby={` ${ariaLabelledBy}`} {...anchorProps}>
{children ?? 'Select Labels'}
</Button>
)}
placeholderText="Filter Labels"
open={open}
onOpenChange={setOpen}
items={filteredItems}
selected={selected}
onSelectedChange={setSelected}
onFilterChange={setFilter}
showItemDividers={true}
overlayProps={{width: 'small', height: 'initial', maxHeight: 'xsmall'}}
/>
</>
)
}
HeightInitialWithOverflowingItemsStory.storyName = 'Height: Initial, Overflowing Items'

export const HeightInitialWithUnderflowingItemsStory = () => {
const underflowingItems = [items[0], items[1]]
const [selected, setSelected] = React.useState<ItemInput | undefined>(underflowingItems[0])
const [filter, setFilter] = React.useState('')
const filteredItems = underflowingItems.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase()))
const [open, setOpen] = useState(false)

return (
<>
<h1>Single Select Panel</h1>
<div>Please select a label that describe your issue:</div>
<SelectPanel
renderAnchor={({children, 'aria-labelledby': ariaLabelledBy, ...anchorProps}) => (
<Button trailingAction={TriangleDownIcon} aria-labelledby={` ${ariaLabelledBy}`} {...anchorProps}>
{children ?? 'Select Labels'}
</Button>
)}
placeholderText="Filter Labels"
open={open}
onOpenChange={setOpen}
items={filteredItems}
selected={selected}
onSelectedChange={setSelected}
onFilterChange={setFilter}
showItemDividers={true}
overlayProps={{width: 'small', height: 'initial', maxHeight: 'xsmall'}}
/>
</>
)
}
HeightInitialWithUnderflowingItemsStory.storyName = 'Height: Initial, Underflowing Items'

export const HeightInitialWithUnderflowingItemsAfterFetch = () => {
const [selected, setSelected] = React.useState<ItemInput | undefined>(items[0])
const [filter, setFilter] = React.useState('')
const [fetchedItems, setFetchedItems] = useState<typeof items>([])
const filteredItems = React.useMemo(
() => fetchedItems.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())),
[fetchedItems, filter],
)
const [open, setOpen] = useState(false)
const [height, setHeight] = useState<OverlayProps['height']>('auto')

const onOpenChange = () => {
setOpen(!open)
setTimeout(() => {
setFetchedItems([items[0], items[1]])
setHeight('initial')
}, 1500)
}

return (
<>
<h1>Single Select Panel</h1>
<div>Please select a label that describe your issue:</div>
<SelectPanel
renderAnchor={({children, 'aria-labelledby': ariaLabelledBy, ...anchorProps}) => (
<Button trailingAction={TriangleDownIcon} aria-labelledby={` ${ariaLabelledBy}`} {...anchorProps}>
{children ?? 'Select Labels'}
</Button>
)}
placeholderText="Filter Labels"
open={open}
onOpenChange={onOpenChange}
loading={filteredItems.length === 0}
items={filteredItems}
selected={selected}
onSelectedChange={setSelected}
onFilterChange={setFilter}
showItemDividers={true}
overlayProps={{width: 'small', height, maxHeight: 'xsmall'}}
/>
</>
)
}
HeightInitialWithUnderflowingItemsAfterFetch.storyName = 'Height: Initial, Underflowing Items (After Fetch)'

export const AboveTallBody = () => {
const [selected, setSelected] = React.useState<ItemInput | undefined>(items[0])
const [filter, setFilter] = React.useState('')
const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase()))
const [open, setOpen] = useState(false)

return (
<>
<h1>Single Select Panel</h1>
<div>Please select a label that describe your issue:</div>
<SelectPanel
renderAnchor={({children, 'aria-labelledby': ariaLabelledBy, ...anchorProps}) => (
<Button trailingAction={TriangleDownIcon} aria-labelledby={` ${ariaLabelledBy}`} {...anchorProps}>
{children ?? 'Select Labels'}
</Button>
)}
placeholderText="Filter Labels"
open={open}
onOpenChange={setOpen}
items={filteredItems}
selected={selected}
onSelectedChange={setSelected}
onFilterChange={setFilter}
showItemDividers={true}
overlayProps={{width: 'small', height: 'xsmall'}}
/>
<div
style={{
backgroundColor: '#9c27b0',
height: '100vh',
padding: '20px',
margin: '20px',
color: 'white',
}}
>
This element makes the body really tall. This is to test that we do not have layout/focus issues if the Portal
is far down the page
</div>
</>
)
}

export const HeightVariantionsAndScroll = () => {
const longItems = [...items, ...items, ...items, ...items, ...items, ...items, ...items, ...items]
const [selectedA, setSelectedA] = React.useState<ItemInput | undefined>(longItems[0])
const [selectedB, setSelectedB] = React.useState<ItemInput | undefined>(longItems[0])
const [filter, setFilter] = React.useState('')
const filteredItems = longItems.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase()))
const [openA, setOpenA] = useState(false)
const [openB, setOpenB] = useState(false)

return (
<>
<h2>With height:medium</h2>
<SelectPanel
renderAnchor={({children, 'aria-labelledby': ariaLabelledBy, ...anchorProps}) => (
<Button trailingAction={TriangleDownIcon} aria-labelledby={` ${ariaLabelledBy}`} {...anchorProps}>
{children ?? 'Select Labels'}
</Button>
)}
placeholderText="Filter Labels"
open={openA}
onOpenChange={setOpenA}
items={filteredItems}
selected={selectedA}
onSelectedChange={setSelectedA}
onFilterChange={setFilter}
showItemDividers={true}
overlayProps={{height: 'medium'}}
/>
<h2>With height:auto, maxheight:medium</h2>
<SelectPanel
renderAnchor={({children, 'aria-labelledby': ariaLabelledBy, ...anchorProps}) => (
<Button trailingAction={TriangleDownIcon} aria-labelledby={` ${ariaLabelledBy}`} {...anchorProps}>
{children ?? 'Select Labels'}
</Button>
)}
placeholderText="Filter Labels"
open={openB}
onOpenChange={setOpenB}
items={filteredItems}
selected={selectedB}
onSelectedChange={setSelectedB}
onFilterChange={setFilter}
showItemDividers={true}
overlayProps={{
height: 'auto',
maxHeight: 'medium',
}}
/>
</>
)
}
Loading