diff --git a/.changeset/new-mangos-fold.md b/.changeset/new-mangos-fold.md deleted file mode 100644 index 55929a994c9..00000000000 --- a/.changeset/new-mangos-fold.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@primer/react": minor ---- - -SelectPanel: Implement empty state (behind ff) diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-colorblind-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-colorblind-linux.png deleted file mode 100644 index c8493aad1bd..00000000000 Binary files a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-colorblind-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-colorblind-modern-action-list--true-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-colorblind-modern-action-list--true-linux.png deleted file mode 100644 index eb4d308d64d..00000000000 Binary files a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-colorblind-modern-action-list--true-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-dimmed-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-dimmed-linux.png deleted file mode 100644 index b86f6e84846..00000000000 Binary files a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-dimmed-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-dimmed-modern-action-list--true-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-dimmed-modern-action-list--true-linux.png deleted file mode 100644 index 639bd1c8bcb..00000000000 Binary files a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-dimmed-modern-action-list--true-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-high-contrast-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-high-contrast-linux.png deleted file mode 100644 index 9f418e05f37..00000000000 Binary files a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-high-contrast-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-high-contrast-modern-action-list--true-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-high-contrast-modern-action-list--true-linux.png deleted file mode 100644 index 0187b5d0afb..00000000000 Binary files a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-high-contrast-modern-action-list--true-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-linux.png deleted file mode 100644 index e2440902da4..00000000000 Binary files a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-modern-action-list--true-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-modern-action-list--true-linux.png deleted file mode 100644 index eb4d308d64d..00000000000 Binary files a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-modern-action-list--true-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-tritanopia-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-tritanopia-linux.png deleted file mode 100644 index e2440902da4..00000000000 Binary files a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-tritanopia-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-tritanopia-modern-action-list--true-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-tritanopia-modern-action-list--true-linux.png deleted file mode 100644 index eb4d308d64d..00000000000 Binary files a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-dark-tritanopia-modern-action-list--true-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-colorblind-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-colorblind-linux.png deleted file mode 100644 index 6e8caf4cac2..00000000000 Binary files a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-colorblind-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-colorblind-modern-action-list--true-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-colorblind-modern-action-list--true-linux.png deleted file mode 100644 index 498f43b7b50..00000000000 Binary files a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-colorblind-modern-action-list--true-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-high-contrast-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-high-contrast-linux.png deleted file mode 100644 index f4a0743991b..00000000000 Binary files a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-high-contrast-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-high-contrast-modern-action-list--true-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-high-contrast-modern-action-list--true-linux.png deleted file mode 100644 index 245dd42872f..00000000000 Binary files a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-high-contrast-modern-action-list--true-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-linux.png deleted file mode 100644 index 6e8caf4cac2..00000000000 Binary files a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-modern-action-list--true-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-modern-action-list--true-linux.png deleted file mode 100644 index 498f43b7b50..00000000000 Binary files a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-modern-action-list--true-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-tritanopia-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-tritanopia-linux.png deleted file mode 100644 index 6e8caf4cac2..00000000000 Binary files a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-tritanopia-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-tritanopia-modern-action-list--true-linux.png b/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-tritanopia-modern-action-list--true-linux.png deleted file mode 100644 index 498f43b7b50..00000000000 Binary files a/.playwright/snapshots/components/SelectPanel.test.ts-snapshots/SelectPanel-Height-Variations-and-Scroll-light-tritanopia-modern-action-list--true-linux.png and /dev/null differ diff --git a/e2e/components/SelectPanel.test.ts b/e2e/components/SelectPanel.test.ts index 10c4bb90356..49198328841 100644 --- a/e2e/components/SelectPanel.test.ts +++ b/e2e/components/SelectPanel.test.ts @@ -20,7 +20,7 @@ const scenarios = matrix({ name: 'With Placeholder for Search Input', }, {id: 'components-selectpanel-examples--above-tall-body', name: 'Above Tall Body'}, - {id: 'components-selectpanel-examples--height-variations-and-scroll', name: 'Height Variations and Scroll'}, + {id: 'components-selectpanel-examples--height-variantions-and-scroll', name: 'Height Variantions and Scroll'}, { id: 'components-selectpanel-examples--height-initial-with-overflowing-items-story', name: 'Height Initial with Overflowing Items', diff --git a/packages/react/src/FilteredActionList/FilteredActionList.module.css b/packages/react/src/FilteredActionList/FilteredActionList.module.css deleted file mode 100644 index 93312579243..00000000000 --- a/packages/react/src/FilteredActionList/FilteredActionList.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.Container { - display: flex; - height: 100%; - overflow: auto; - flex-grow: 1; -} diff --git a/packages/react/src/FilteredActionList/FilteredActionListWithModernActionList.tsx b/packages/react/src/FilteredActionList/FilteredActionListWithModernActionList.tsx index e4ee594fd25..e2dab51eae7 100644 --- a/packages/react/src/FilteredActionList/FilteredActionListWithModernActionList.tsx +++ b/packages/react/src/FilteredActionList/FilteredActionListWithModernActionList.tsx @@ -14,7 +14,6 @@ import {VisuallyHidden} from '../VisuallyHidden' import type {SxProp} from '../sx' import type {FilteredActionListLoadingType} from './FilteredActionListLoaders' import {FilteredActionListLoadingTypes, FilteredActionListBodyLoader} from './FilteredActionListLoaders' -import classes from './FilteredActionList.module.css' import {ActionListContainerContext} from '../ActionList/ActionListContainerContext' import {isValidElementType} from 'react-is' @@ -33,7 +32,6 @@ export interface FilteredActionListProps onInputRefChanged?: (ref: React.RefObject) => void textInputProps?: Partial> inputRef?: React.RefObject - message?: React.ReactNode className?: string announcementsEnabled?: boolean } @@ -56,7 +54,6 @@ export function FilteredActionList({ sx, groupMetadata, showItemDividers, - message, className, selectionVariant, announcementsEnabled = true, @@ -153,47 +150,6 @@ export function FilteredActionList({ return itemsInGroup } - function getBodyContent() { - if (loading && scrollContainerRef.current && loadingType.appearsInBody) { - return - } - if (message) { - return message - } - - return ( - - - {groupMetadata?.length - ? groupMetadata.map((group, index) => { - return ( - - - {group.header?.title ? group.header.title : `Group ${group.groupId}`} - - {getItemListForEachGroup(group.groupId).map((item, index) => { - const key = item.key ?? item.id?.toString() ?? index.toString() - return - })} - - ) - }) - : items.map((item, index) => { - const key = item.key ?? item.id?.toString() ?? index.toString() - return - })} - - - ) - } useAnnouncements(items, listRef, inputRef, enableAnnouncements) return ( @@ -227,9 +183,42 @@ export function FilteredActionList({ /> Items will be filtered as you type -
- {getBodyContent()} -
+ + {loading && scrollContainerRef.current && loadingType.appearsInBody ? ( + + ) : ( + + + {groupMetadata?.length + ? groupMetadata.map((group, index) => { + return ( + + + {group.header?.title ? group.header.title : `Group ${group.groupId}`} + + {getItemListForEachGroup(group.groupId).map((item, index) => { + const key = item.key ?? item.id?.toString() ?? index.toString() + return + })} + + ) + }) + : items.map((item, index) => { + const key = item.key ?? item.id?.toString() ?? index.toString() + return + })} + + + )} + ) } diff --git a/packages/react/src/SelectPanel/SelectPanel.dev.stories.tsx b/packages/react/src/SelectPanel/SelectPanel.dev.stories.tsx index 89f76426e17..168e79e4ebb 100644 --- a/packages/react/src/SelectPanel/SelectPanel.dev.stories.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.dev.stories.tsx @@ -9,21 +9,13 @@ import type {ItemInput} from '../deprecated/ActionList/List' import {FeatureFlags} from '../FeatureFlags' import FormControl from '../FormControl' -const meta: Meta = { +const meta = { title: 'Components/SelectPanel/Dev', component: SelectPanel, } satisfies Meta export default meta -const NoResultsMessage = (filter: string): {variant: 'empty'; title: string; body: string} => { - return { - variant: 'empty', - title: `No language found for \`${filter}\``, - body: 'Adjust your search term to find other languages', - } -} - function getColorCircle(color: string) { return function () { return ( @@ -116,7 +108,6 @@ export const WithCss = () => { onSelectedChange={setSelected} onFilterChange={setFilter} className="testCustomClassnameMono" - message={selectedItemsSortedFirst.length === 0 ? NoResultsMessage(filter) : undefined} /> @@ -168,7 +159,6 @@ export const WithSx = () => { onSelectedChange={setSelected} onFilterChange={setFilter} sx={{fontFamily: 'Times New Roman'}} - message={selectedItemsSortedFirst.length === 0 ? NoResultsMessage(filter) : undefined} /> @@ -221,7 +211,6 @@ export const WithSxAndCSS = () => { onFilterChange={setFilter} sx={{fontFamily: 'Times New Roman'}} className="testCustomClassnameMono" - message={selectedItemsSortedFirst.length === 0 ? NoResultsMessage(filter) : undefined} /> diff --git a/packages/react/src/SelectPanel/SelectPanel.docs.json b/packages/react/src/SelectPanel/SelectPanel.docs.json index ba48ce18d94..d28e8c92ea5 100644 --- a/packages/react/src/SelectPanel/SelectPanel.docs.json +++ b/packages/react/src/SelectPanel/SelectPanel.docs.json @@ -150,48 +150,7 @@ "type": "string | React.ReactElement", "defaultValue": "null", "description": "Footer rendered at the end of the panel" - }, - { - "name": "message", - "type": "{title: string | React.ReactElement; variant: 'empty' | 'error' | 'warning'; body: React.ReactNode;}", - "defaultValue": "A default empty message is provided by default if this option is not supplied", - "description": "Message to display in the panel in case of error or empty results" - }, - { - "name": "notice", - "type": "{text: string | React.ReactElement; variant: 'empty' | 'error' | 'warning';}", - "description": "Optional notice to display on top of the panel" } ], - "subcomponents": [ - { - "name": "SelectPanel.Message", - "props": [ - { - "name": "title", - "type": "string", - "description": "A title for the message" - }, - { - "name": "variant", - "type": "'empty' | 'error' | 'warning'", - "description": "The variant of the message", - "required": true - }, - { - "name": "className", - "type": "string", - "defaultValue": "", - "description": "Custom className" - }, - { - "name": "children", - "type": "React.ReactNode", - "defaultValue": "", - "required": true, - "description": "The message to display" - } - ] - } - ] + "subcomponents": [] } diff --git a/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx b/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx index 2f2eea43248..36abedbf65f 100644 --- a/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx @@ -11,21 +11,13 @@ import FormControl from '../FormControl' import {Stack} from '../Stack' import {Dialog} from '../experimental' -const meta: Meta = { +const meta = { title: 'Components/SelectPanel/Examples', component: SelectPanel, } satisfies Meta export default meta -const NoResultsMessage = (filter: string): {variant: 'empty'; title: string; body: string} => { - return { - variant: 'empty', - title: `No language found for \`${filter}\``, - body: 'Adjust your search term to find other languages', - } -} - function getColorCircle(color: string) { return function () { return ( @@ -90,7 +82,6 @@ export const HeightInitialWithOverflowingItemsStory = () => { onSelectedChange={setSelected} onFilterChange={setFilter} overlayProps={{width: 'small', height: 'initial', maxHeight: 'xsmall'}} - message={selectedItemsSortedFirst.length === 0 ? NoResultsMessage(filter) : undefined} /> ) @@ -136,7 +127,6 @@ export const HeightInitialWithUnderflowingItemsStory = () => { onFilterChange={setFilter} showItemDividers={true} overlayProps={{width: 'small', height: 'initial', maxHeight: 'xsmall'}} - message={selectedItemsSortedFirst.length === 0 ? NoResultsMessage(filter) : undefined} /> ) @@ -159,7 +149,7 @@ export const HeightInitialWithUnderflowingItemsAfterFetch = () => { [fetchedItems, filter, selected], ) // design guidelines say to sort selected items first - const selectedItemsSortedFirst = filteredItems.sort((a, b) => { + const selectedItemsSortedFirst = fetchedItems.sort((a, b) => { const aIsSelected = selected.some(selectedItem => selectedItem.text === a.text) const bIsSelected = selected.some(selectedItem => selectedItem.text === b.text) if (aIsSelected && !bIsSelected) return -1 @@ -189,14 +179,13 @@ export const HeightInitialWithUnderflowingItemsAfterFetch = () => { placeholder="Select labels" // button text when no items are selected open={open} onOpenChange={onOpenChange} - loading={filteredItems.length === 0 && !filter} + loading={filteredItems.length === 0} items={selectedItemsSortedFirst} selected={selected} onSelectedChange={setSelected} onFilterChange={setFilter} showItemDividers={true} overlayProps={{width: 'small', height, maxHeight: 'xsmall'}} - message={selectedItemsSortedFirst.length === 0 ? NoResultsMessage(filter) : undefined} /> ) @@ -239,7 +228,6 @@ export const AboveTallBody = () => { onSelectedChange={setSelected} onFilterChange={setFilter} showItemDividers={true} - message={selectedItemsSortedFirst.length === 0 ? NoResultsMessage(filter) : undefined} />
{ ) } -export const HeightVariationsAndScroll = () => { +export const HeightVariantionsAndScroll = () => { const longItems = [...items, ...items, ...items, ...items, ...items, ...items, ...items, ...items] const [filter, setFilter] = useState('') // Example A @@ -305,7 +293,6 @@ export const HeightVariationsAndScroll = () => { onFilterChange={setFilter} showItemDividers={true} overlayProps={{height: 'medium'}} - message={selectedItemsSortedFirstA.length === 0 ? NoResultsMessage(filter) : undefined} />
@@ -329,7 +316,6 @@ export const HeightVariationsAndScroll = () => { height: 'auto', maxHeight: 'medium', }} - message={selectedItemsSortedFirstB.length === 0 ? NoResultsMessage(filter) : undefined} /> @@ -404,7 +390,6 @@ export const CustomItemRenderer = () => { )} - message={selectedItemsSortedFirst.length === 0 ? NoResultsMessage(filter) : undefined} /> ) @@ -454,7 +439,6 @@ export const ItemsInScope = () => { selected={selected} onSelectedChange={setSelected} onFilterChange={setFilter} - message={selectedItemsSortedFirst.length === 0 ? NoResultsMessage(filter) : undefined} /> ) @@ -500,7 +484,6 @@ export const RepositionAfterLoading = () => { selected={selected} onSelectedChange={setSelected} onFilterChange={setFilter} - message={filteredItems.length === 0 ? NoResultsMessage(filter) : undefined} /> @@ -548,7 +531,6 @@ export const SelectPanelRepositionInsideDialog = () => { onSelectedChange={setSelected} onFilterChange={setFilter} overlayProps={{anchorSide: 'outside-top'}} - message={filteredItems.length === 0 ? NoResultsMessage(filter) : undefined} /> diff --git a/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx b/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx index c6c6cedb2d8..637340a5268 100644 --- a/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx @@ -3,7 +3,6 @@ import type {Meta, StoryObj} from '@storybook/react' import Box from '../Box' import {Button} from '../Button' import type {ItemInput, GroupedListProps} from '../deprecated/ActionList/List' -import Link from '../Link' import {SelectPanel, type SelectPanelProps} from './SelectPanel' import { AlertIcon, @@ -19,43 +18,18 @@ import { VersionsIcon, } from '@primer/octicons-react' import useSafeTimeout from '../hooks/useSafeTimeout' -import ToggleSwitch from '../ToggleSwitch' -import Text from '../Text' import FormControl from '../FormControl' +import Link from '../Link' import {SegmentedControl} from '../SegmentedControl' import {Stack} from '../Stack' -const meta: Meta = { +const meta = { title: 'Components/SelectPanel/Features', component: SelectPanel, } satisfies Meta export default meta -const NoResultsMessage = (filter: string): {variant: 'empty'; title: string; body: string} => { - return { - variant: 'empty', - title: `No language found for \`${filter}\``, - body: 'Adjust your search term to find other languages', - } -} - -const EmptyMessage: {variant: 'empty'; title: string; body: React.ReactElement} = { - variant: 'empty', - title: `You haven't created any projects yet`, - body: ( - <> - Start your first project to organise your issues. - - ), -} - -const ErrorMessage: {variant: 'error'; title: string; body: string} = { - variant: 'error', - title: 'Oops', - body: 'Something went wrong.', -} - function getColorCircle(color: string) { return function () { return ( @@ -123,7 +97,6 @@ export const WithItemDividers = () => { onFilterChange={setFilter} showItemDividers={true} width="medium" - message={selectedItemsSortedFirst.length === 0 ? NoResultsMessage(filter) : undefined} /> ) @@ -169,7 +142,6 @@ export const WithPlaceholderForSearchInput = () => { onSelectedChange={setSelected} onFilterChange={setFilter} width="medium" - message={selectedItemsSortedFirst.length === 0 ? NoResultsMessage(filter) : undefined} /> ) @@ -207,7 +179,6 @@ export const SingleSelect = () => { onFilterChange={setFilter} onCancel={() => setOpen(false)} width="medium" - message={selectedItemsSortedFirst.length === 0 ? NoResultsMessage(filter) : undefined} /> ) @@ -252,7 +223,6 @@ export const MultiSelect = () => { onSelectedChange={setSelected} onFilterChange={setFilter} width="medium" - message={selectedItemsSortedFirst.length === 0 ? NoResultsMessage(filter) : undefined} /> ) @@ -295,7 +265,6 @@ export const WithExternalAnchor = () => { onSelectedChange={setSelected} onFilterChange={setFilter} width="medium" - message={filteredItems.length === 0 ? NoResultsMessage(filter) : undefined} /> ) @@ -344,7 +313,6 @@ export const WithFooter = () => { } width="medium" - message={selectedItemsSortedFirst.length === 0 ? NoResultsMessage(filter) : undefined} /> ) @@ -524,7 +492,6 @@ export const WithGroups = () => { onFilterChange={setFilter} overlayProps={{width: 'large', height: 'xlarge'}} width="medium" - message={selectedItemsSortedFirst.length === 0 ? NoResultsMessage(filter) : undefined} /> ) @@ -569,7 +536,6 @@ export const WithLabelVisuallyHidden = () => { onSelectedChange={setSelected} onFilterChange={setFilter} width="medium" - message={selectedItemsSortedFirst.length === 0 ? NoResultsMessage(filter) : undefined} /> ) @@ -617,7 +583,6 @@ export const WithLabelInternally = () => { onSelectedChange={setSelected} onFilterChange={setFilter} width="medium" - message={selectedItemsSortedFirst.length === 0 ? NoResultsMessage(filter) : undefined} /> ) } @@ -629,12 +594,10 @@ export const AsyncFetch: StoryObj = { const [open, setOpen] = useState(false) const filterTimerId = useRef(null) const {safeSetTimeout, safeClearTimeout} = useSafeTimeout() - const [query, setQuery] = useState('') const fetchItems = (query: string) => { if (filterTimerId.current) { safeClearTimeout(filterTimerId.current) - setQuery(query) } filterTimerId.current = safeSetTimeout(() => { @@ -672,122 +635,6 @@ export const AsyncFetch: StoryObj = { height={height} initialLoadingType={initialLoadingType} width="medium" - message={filteredItems.length === 0 ? NoResultsMessage(query) : undefined} - /> - ) - }, - args: { - initialLoadingType: 'spinner', - height: 'medium', - }, - argTypes: { - initialLoadingType: { - control: 'select', - options: ['spinner', 'skeleton'], - }, - height: { - control: 'select', - options: ['auto', 'xsmall', 'small', 'medium', 'large', 'xlarge'], - }, - }, -} - -export const CustomisedNoInitialItems = () => { - const [selected, setSelected] = React.useState([]) - const [filteredItems, setFilteredItems] = React.useState([]) - const [open, setOpen] = useState(false) - const [filter, setFilter] = useState('') - const onFilterChange = (value: string = '') => { - setFilter(value) - setTimeout(() => { - // fetch the items - setFilteredItems([]) - }, 0) - } - const [isError, setIsError] = React.useState(false) - - const onClick = React.useCallback(() => { - setIsError(!isError) - }, [setIsError, isError]) - - function getMessage(): {variant: 'empty' | 'error'; title: string; body: string | React.ReactElement} { - if (isError) return ErrorMessage - else if (filter) return NoResultsMessage(filter) - else return EmptyMessage - } - - return ( - <> - - Enable Error State :{isError ? 'On' : 'Off'} - - - ( - - )} - open={open} - onOpenChange={setOpen} - items={filteredItems} - selected={selected} - onSelectedChange={setSelected} - onFilterChange={onFilterChange} - width="medium" - height="large" - message={getMessage()} - /> - - ) -} - -export const CustomisedNoResults: StoryObj = { - render: ({initialLoadingType, height}) => { - const [selected, setSelected] = React.useState([]) - const [filteredItems, setFilteredItems] = React.useState([]) - const [filterValue, setFilterValue] = React.useState('') - const [open, setOpen] = useState(false) - const filterTimerId = useRef(null) - const {safeSetTimeout, safeClearTimeout} = useSafeTimeout() - const onFilterChange = (value: string) => { - setFilterValue(value) - if (filterTimerId.current) { - safeClearTimeout(filterTimerId.current) - } - - filterTimerId.current = safeSetTimeout(() => { - setFilteredItems(items.filter(item => item.text.toLowerCase().startsWith(value.toLowerCase()))) - }, 2000) as unknown as number - } - - return ( - ( - - )} - placeholderText="Filter labels" - open={open} - onOpenChange={setOpen} - items={filteredItems} - selected={selected} - onSelectedChange={setSelected} - onFilterChange={onFilterChange} - showItemDividers={true} - initialLoadingType={initialLoadingType} - height={height} - overlayProps={{maxHeight: height === 'auto' || height === 'initial' ? 'xlarge' : height}} - message={filteredItems.length === 0 ? NoResultsMessage(filterValue) : undefined} /> ) }, diff --git a/packages/react/src/SelectPanel/SelectPanel.module.css b/packages/react/src/SelectPanel/SelectPanel.module.css index e737e17f13b..9b3acb44a17 100644 --- a/packages/react/src/SelectPanel/SelectPanel.module.css +++ b/packages/react/src/SelectPanel/SelectPanel.module.css @@ -1,8 +1,3 @@ -.Overlay { - /* CSS variables values are passed in via styles */ - --max-height: 0; -} - .Wrapper { display: flex; height: inherit; @@ -79,45 +74,6 @@ max-height: inherit; } -.Message { - display: flex; - height: 100%; - min-height: min(calc(var(--max-height) - 150px), 324px); /* maxHeight of dialog - (header & footer) */ - padding: var(--base-size-24); - text-align: center; - flex-direction: column; - justify-content: center; - align-items: center; - flex-grow: 1; - gap: var(--base-size-4); - - a { - color: inherit; - text-decoration: underline; - } -} - -.MessageTitle { - font-size: var(--text-body-size-medium); - font-weight: var(--base-text-weight-semibold); -} - -.MessageBody { - font-size: var(--text-body-size-small); - color: var(--fgColor-muted); - align-items: center; - gap: var(--stack-gap-condensed); -} - -.MessageIcon { - margin-bottom: var(--base-size-8); - color: var(--fgColor-attention); - - &:where([data-variant='error']) { - color: var(--fgColor-danger); - } -} - .ResponsiveCloseButton { display: none; diff --git a/packages/react/src/SelectPanel/SelectPanel.stories.tsx b/packages/react/src/SelectPanel/SelectPanel.stories.tsx index 96e8e20c23f..24d0ba377d5 100644 --- a/packages/react/src/SelectPanel/SelectPanel.stories.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.stories.tsx @@ -8,7 +8,7 @@ import {SelectPanel} from '../SelectPanel' import type {ItemInput} from '../deprecated/ActionList/List' import FormControl from '../FormControl' -const meta: Meta = { +const meta = { title: 'Components/SelectPanel', component: SelectPanel, } satisfies Meta @@ -101,15 +101,6 @@ export const Default = () => { onSelectedChange={setSelected} onFilterChange={setFilter} width="medium" - message={ - selectedItemsSortedFirst.length === 0 - ? { - variant: 'empty', - title: `No language found for \`${filter}\``, - body: 'Adjust your search term to find other languages', - } - : undefined - } /> ) diff --git a/packages/react/src/SelectPanel/SelectPanel.test.tsx b/packages/react/src/SelectPanel/SelectPanel.test.tsx index f8112d38010..d00754bf453 100644 --- a/packages/react/src/SelectPanel/SelectPanel.test.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.test.tsx @@ -491,97 +491,6 @@ for (const useModernActionList of [false, true]) { ) } - const SelectPanelWithCustomMessages: React.FC<{items: SelectPanelProps['items']}> = ({items}) => { - const [selected, setSelected] = React.useState([]) - const [filter, setFilter] = React.useState('') - const [open, setOpen] = React.useState(false) - - const onSelectedChange = (selected: SelectPanelProps['items']) => { - setSelected(selected) - } - - const emptyMessage: {variant: 'empty'; title: string; body: string} = { - variant: 'empty', - title: "You haven't created any projects yet", - body: 'Start your first project to organise your issues', - } - - const noResultsMessage = (filter: string): {variant: 'empty'; title: string; body: string} => ({ - variant: 'empty', - title: `No language found for ${filter}`, - body: 'Adjust your search term to find other languages', - }) - - const filteredItems = items.filter(item => item.text?.includes(filter)) - - function getMessage() { - if (filteredItems.length === 0 && !filter) { - return emptyMessage - } - if (filteredItems.length === 0 && filter) { - return noResultsMessage(filter) - } - return undefined - } - - return ( - - { - setFilter(value) - }} - open={open} - onOpenChange={isOpen => { - setOpen(isOpen) - }} - message={getMessage()} - /> - - ) - } - - function NoItemAvailableSelectPanel() { - const [selected, setSelected] = React.useState([]) - const [filter, setFilter] = React.useState('') - const [open, setOpen] = React.useState(false) - - const onSelectedChange = (selected: SelectPanelProps['items']) => { - setSelected(selected) - } - - const items: SelectPanelProps['items'] = [] - - return ( - - item.text?.includes(filter))} - placeholder="Select items" - placeholderText="Filter items" - selected={selected} - onSelectedChange={onSelectedChange} - filterValue={filter} - onFilterChange={value => { - setFilter(value) - }} - open={open} - onOpenChange={isOpen => { - setOpen(isOpen) - }} - /> - - ) - } - describe('filtering', () => { it('should filter the list of items when the user types into the input', async () => { const user = userEvent.setup() @@ -743,76 +652,6 @@ for (const useModernActionList of [false, true]) { }) }) - describe('Empty state', () => { - // This is only implemented with the feature flag (for now) - if (!useModernActionList) return - - it('should display the default empty state message when there is no matching item after filtering (No custom message is provided)', async () => { - const user = userEvent.setup() - - renderWithFlag(, useModernActionList) - - await user.click(screen.getByText('Select items')) - - expect(screen.getAllByRole('option')).toHaveLength(3) - - await user.type(document.activeElement!, 'something') - expect(screen.getByText("You haven't created any items yet")).toBeVisible() - expect(screen.getByText('Please add or create new items to populate the list.')).toBeVisible() - }) - - it('should display the default empty state message when there is no item after the initial load (No custom message is provided)', async () => { - const user = userEvent.setup() - - renderWithFlag(, useModernActionList) - - await waitFor(async () => { - await user.click(screen.getByText('Select items')) - expect(screen.getByText("You haven't created any items yet")).toBeVisible() - expect(screen.getByText('Please add or create new items to populate the list.')).toBeVisible() - }) - }) - it('should display the custom empty state message when there is no matching item after filtering', async () => { - const user = userEvent.setup() - - renderWithFlag( - , - useModernActionList, - ) - - await user.click(screen.getByText('Select items')) - - expect(screen.getAllByRole('option')).toHaveLength(3) - - await user.type(document.activeElement!, 'something') - expect(screen.getByText('No language found for something')).toBeVisible() - expect(screen.getByText('Adjust your search term to find other languages')).toBeVisible() - }) - - it('should display the custom empty state message when there is no item after the initial load', async () => { - const user = userEvent.setup() - - renderWithFlag(, useModernActionList) - - await waitFor(async () => { - await user.click(screen.getByText('Select items')) - expect(screen.getByText("You haven't created any projects yet")).toBeVisible() - expect(screen.getByText('Start your first project to organise your issues')).toBeVisible() - }) - }) - }) describe('with footer', () => { function SelectPanelWithFooter() { const [selected, setSelected] = React.useState([]) diff --git a/packages/react/src/SelectPanel/SelectPanel.tsx b/packages/react/src/SelectPanel/SelectPanel.tsx index 0758d4a6b2f..3154baa1682 100644 --- a/packages/react/src/SelectPanel/SelectPanel.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.tsx @@ -10,13 +10,13 @@ import Heading from '../Heading' import type {OverlayProps} from '../Overlay' import type {TextInputProps} from '../TextInput' import type {ItemProps, ItemInput} from './types' -import {SelectPanelMessage} from './SelectPanelMessage' import {Button, IconButton} from '../Button' import {useProvidedRefOrCreate} from '../hooks' import type {FocusZoneHookSettings} from '../hooks/useFocusZone' import {useId} from '../hooks/useId' import {useProvidedStateOrCreate} from '../hooks/useProvidedStateOrCreate' +import {LiveRegion, LiveRegionOutlet, Message} from '../internal/components/LiveRegion' import useSafeTimeout from '../hooks/useSafeTimeout' import type {FilteredActionListLoadingType} from '../FilteredActionList/FilteredActionListLoaders' import {FilteredActionListLoadingTypes} from '../FilteredActionList/FilteredActionListLoaders' @@ -24,18 +24,11 @@ import {useFeatureFlag} from '../FeatureFlags' import {announce} from '@primer/live-region-element' import classes from './SelectPanel.module.css' import {clsx} from 'clsx' -import {heightMap} from '../Overlay/Overlay' // we add a delay so that it does not interrupt default screen reader announcement and queues after it const delayMs = 500 const loadingDelayMs = 1000 -const DefaultEmptyMessage = ( - - Please add or create new items to populate the list. - -) - const getItemWithActiveDescendant = ( listRef: React.RefObject, items: FilteredActionListProps['items'], @@ -51,7 +44,7 @@ const getItemWithActiveDescendant = ( const activeItem = items[index] as ItemInput | undefined const text = activeItem?.text - const selected = activeItemElement.getAttribute('aria-selected') === 'true' + const selected = activeItem?.selected return {index, text, selected} } @@ -134,21 +127,15 @@ interface SelectPanelBaseProps { text: string | React.ReactElement variant: 'info' | 'warning' | 'error' } - message?: { - title: string - body: string | React.ReactElement - variant: 'empty' | 'error' | 'warning' - } onCancel?: () => void } -export type SelectPanelProps = React.PropsWithChildren< - SelectPanelBaseProps & - Omit & - Pick & - AnchoredOverlayWrapperAnchorProps & - (SelectPanelSingleSelection | SelectPanelMultiSelection) -> +export type SelectPanelProps = SelectPanelBaseProps & + Omit & + Pick & + AnchoredOverlayWrapperAnchorProps & + (SelectPanelSingleSelection | SelectPanelMultiSelection) + function isMultiSelectVariant( selected: SelectPanelSingleSelection['selected'] | SelectPanelMultiSelection['selected'], ): selected is SelectPanelMultiSelection['selected'] { @@ -202,7 +189,6 @@ export function SelectPanel({ height, width, id, - message, notice, onCancel, ...listProps @@ -286,15 +272,13 @@ export function SelectPanel({ useEffect(() => { if (open) { - if (!usingModernActionList) { - if (items.length === 0) { - announceNoItems() + if (items.length === 0 && !usingModernActionList) { + announceNoItems() + } else { + if (listContainerElement) { + announceItemsChanged(items, {current: listContainerElement}) } else { - if (listContainerElement) { - announceItemsChanged(items, {current: listContainerElement}) - } else { - setNeedItemsChangedAnnouncement(true) - } + setNeedItemsChangedAnnouncement(true) } } } @@ -307,7 +291,7 @@ export function SelectPanel({ return } - if (isLoading || items.length > 0) { + if (isLoading) { setIsLoading(false) setDataLoadedOnce(true) } @@ -449,172 +433,167 @@ export function SelectPanel({ error: , } - function getMessage() { - // If there is no items after the first load, show the no items state - if (items.length === 0 && !message) { - return DefaultEmptyMessage - } else if (message) { - return ( - - {message.body} - - ) - } - } - return ( - - + + + {usingModernActionList ? null : ( + + )} -
- - {title} - - {subtitle ? ( - - {subtitle} - - ) : null} -
- {onCancel && ( - { - onCancel() - onClose('escape') - }} - /> - )} -
- {notice && ( -
- {iconForNoticeVariant[notice.variant]} -
{notice.text}
-
- )} - - {footer ? ( - {footer} - - ) : isMultiSelectVariant(selected) ? ( - /* Save and Cancel buttons are only useful for multiple selection, single selection instantly closes the panel */ -
- {/* we add a save and cancel button on narrow screens when SelectPanel is full-screen */} +
+ + {title} + + {subtitle ? ( + + {subtitle} + + ) : null} +
{onCancel && ( - + /> )} - -
- ) : null} -
-
+ {footer} + + ) : isMultiSelectVariant(selected) ? ( + /* Save and Cancel buttons are only useful for multiple selection, single selection instantly closes the panel */ +
+ {/* we add a save and cancel button on narrow screens when SelectPanel is full-screen */} + {onCancel && ( + + )} + +
+ ) : null} + + + ) } + +SelectPanel.displayName = 'SelectPanel' diff --git a/packages/react/src/SelectPanel/SelectPanelMessage.tsx b/packages/react/src/SelectPanel/SelectPanelMessage.tsx deleted file mode 100644 index a2f099d231d..00000000000 --- a/packages/react/src/SelectPanel/SelectPanelMessage.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react' -import Text from '../Text' -import Octicon from '../Octicon' -import {AlertIcon} from '@primer/octicons-react' -import classes from './SelectPanel.module.css' -import {clsx} from 'clsx' - -export type SelectPanelMessageProps = { - children: React.ReactNode - title: string - variant: 'empty' | 'error' | 'warning' - className?: string -} - -export const SelectPanelMessage: React.FC = ({variant, title, children, className}) => { - return ( -
- {variant !== 'empty' ? : null} - {title} - {children} -
- ) -} diff --git a/script/generate-e2e-tests.js b/script/generate-e2e-tests.js index 3c38372326f..8405ba30bd8 100755 --- a/script/generate-e2e-tests.js +++ b/script/generate-e2e-tests.js @@ -1142,8 +1142,8 @@ const components = new Map([ name: 'Above Tall Body', }, { - id: 'components-selectpanel-examples--height-variations-and-scroll', - name: 'Height Variations and Scroll', + id: 'components-selectpanel-examples--height-variantions-and-scroll', + name: 'Height Variantions and Scroll', }, { id: 'components-selectpanel-examples--height-initial-with-overflowing-items-story',