Skip to content
Merged
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
299 changes: 277 additions & 22 deletions e2e/components/SelectPanel.test.ts

Large diffs are not rendered by default.

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