From 0003e59a0563e065d7898c641134486e00ed7cea Mon Sep 17 00:00:00 2001 From: Armagan Ersoz Date: Fri, 2 Aug 2024 13:11:39 +1000 Subject: [PATCH 01/11] wip --- .../FilteredActionList/FilteredActionList.tsx | 2 +- .../SelectPanel.examples.stories.tsx | 156 ++++++++++++++++++ .../SelectPanel.features.stories.tsx | 6 +- .../src/SelectPanel/SelectPanel.stories.tsx | 6 +- .../react/src/SelectPanel/SelectPanel.tsx | 4 +- packages/react/src/SelectPanel/types.ts | 4 + .../react/src/deprecated/ActionList/Item.tsx | 2 + .../react/src/deprecated/ActionList/List.tsx | 1 - 8 files changed, 173 insertions(+), 8 deletions(-) create mode 100644 packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx create mode 100644 packages/react/src/SelectPanel/types.ts diff --git a/packages/react/src/FilteredActionList/FilteredActionList.tsx b/packages/react/src/FilteredActionList/FilteredActionList.tsx index 44b5f661c19..7bec423a15c 100644 --- a/packages/react/src/FilteredActionList/FilteredActionList.tsx +++ b/packages/react/src/FilteredActionList/FilteredActionList.tsx @@ -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' import {useFocusZone} from '../hooks/useFocusZone' import {useId} from '../hooks/useId' import {useProvidedRefOrCreate} from '../hooks/useProvidedRefOrCreate' diff --git a/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx b/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx new file mode 100644 index 00000000000..4b6cd435a1a --- /dev/null +++ b/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx @@ -0,0 +1,156 @@ +import React, {useState, useRef} from 'react' +import type {Meta} from '@storybook/react' +import {Button} from '../Button' +import type {ItemInput, GroupedListProps} from '../deprecated/ActionList/List' +import {ActionList} from '../deprecated/ActionList' +import {SelectPanel} from './SelectPanel' +import { + FilterIcon, + GearIcon, + NoteIcon, + ProjectIcon, + SearchIcon, + TriangleDownIcon, + TypographyIcon, + VersionsIcon, +} from '@primer/octicons-react' + +const meta = { + title: 'Components/SelectPanel/Examples', + component: SelectPanel, +} satisfies Meta + +export default meta + +export const WithGroups = () => { + // const items2 = [ + // { + // id: '1', + // key: 1, + // leadingVisual: TriangleDownIcon, + // text: 'Current attachments', + // groupId: '1', + // }, + // { + // id: '2', + // key: 2, + // leadingVisual: TriangleDownIcon, + // text: 'Files', + // groupId: '2', + // }, + // { + // id: '3', + // key: 3, + // leadingVisual: TriangleDownIcon, + // text: 'Symbols', + // groupId: '3', + // }, + // { + // id: '4', + // key: 4, + // leadingVisual: TriangleDownIcon, + // text: 'Symbols', + // groupId: '3', + // }, + // { + // id: '5', + // key: 5, + // leadingVisual: TriangleDownIcon, + // text: 'Symbols', + // groupId: '3', + // }, + // ] + + const items = [ + { + id: '1', + key: 1, + leadingVisual: SearchIcon, + text: 'repo:github/memex', + groupId: '1', + renderItem: props => , + }, + { + id: '2', + key: 2, + leadingVisual: NoteIcon, + text: 'Table', + description: 'Information-dense table optimized for operations across teams', + descriptionVariant: 'block', + groupId: '1', + }, + { + id: '3', + key: 3, + leadingVisual: ProjectIcon, + text: 'Board', + description: 'Kanban-style board focused on visual states', + descriptionVariant: 'block', + groupId: '2', + }, + { + id: '4', + key: 4, + leadingVisual: FilterIcon, + text: 'Save sort and filters to current view', + groupId: '2', + }, + {id: '5', key: 5, leadingVisual: FilterIcon, text: 'Save sort and filters to new view', groupId: '1'}, + {id: '6', key: 6, leadingVisual: GearIcon, text: 'View settings', groupId: '0'}, + {id: '7', key: 7, leadingVisual: TypographyIcon, text: 'Rename', groupId: '0'}, + {id: '8', key: 8, leadingVisual: VersionsIcon, text: 'Duplicate', groupId: '0'}, + ] + const [selected, setSelected] = React.useState([items[0], items[1]]) + const [filter, setFilter] = React.useState('') + const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())) + const [open, setOpen] = useState(false) + const buttonRef = useRef(null) + + const groupMetadata: GroupedListProps['groupMetadata'] = [ + {groupId: '0'}, + {groupId: '1', header: {title: 'Live query', variant: 'filled', 'aria-level': 3}}, + {groupId: '2', header: {title: 'Layout', variant: 'subtle', 'aria-level': 3}}, + // {groupId: '3', renderItem: props => }, + // { + // groupId: '4', + // renderItem: ({leadingVisual: LeadingVisual, ...props}) => ( + // ( + // svg': {fill: 'white'}}}>{LeadingVisual && } + // )} + // /> + // ), + // renderGroup: ({sx: sxProps, ...props}) => ( + // + // ), + // }, + ] + + return ( + <> +

Multi Select Panel With Footer

+ ( + + )} + anchorRef={buttonRef} + groupMetadata={groupMetadata} + placeholderText="Filter Labels" + open={open} + onOpenChange={setOpen} + items={filteredItems} + selected={selected} + onSelectedChange={setSelected} + onFilterChange={setFilter} + showItemDividers={true} + overlayProps={{width: 'large', height: 'xlarge'}} + /> + + ) +} diff --git a/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx b/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx index a47a4ca7de6..99fd5052322 100644 --- a/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx @@ -8,10 +8,12 @@ import {SelectPanel} from './SelectPanel' import {TriangleDownIcon} from '@primer/octicons-react' import type {OverlayProps} from '../Overlay' -export default { +const meta = { title: 'Components/SelectPanel/Features', component: SelectPanel, -} as Meta +} satisfies Meta + +export default meta function getColorCircle(color: string) { return function () { diff --git a/packages/react/src/SelectPanel/SelectPanel.stories.tsx b/packages/react/src/SelectPanel/SelectPanel.stories.tsx index 97d7903a42e..acc3fe1683e 100644 --- a/packages/react/src/SelectPanel/SelectPanel.stories.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.stories.tsx @@ -7,10 +7,12 @@ import {Button} from '../Button' import {SelectPanel} from '../SelectPanel' import type {ItemInput} from '../deprecated/ActionList/List' -export default { +const meta = { title: 'Components/SelectPanel', component: SelectPanel, -} as Meta +} satisfies Meta + +export default meta function getColorCircle(color: string) { return function () { diff --git a/packages/react/src/SelectPanel/SelectPanel.tsx b/packages/react/src/SelectPanel/SelectPanel.tsx index 73bbcaa5948..5763081361b 100644 --- a/packages/react/src/SelectPanel/SelectPanel.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.tsx @@ -9,8 +9,8 @@ import {FilteredActionList} from '../FilteredActionList' import Heading from '../Heading' import type {OverlayProps} from '../Overlay' import type {TextInputProps} from '../TextInput' -import type {ItemProps} from '../deprecated/ActionList' -import type {ItemInput} from '../deprecated/ActionList/List' +import type {ItemProps, ItemInput} from './types' + import {Button} from '../Button' import {useProvidedRefOrCreate} from '../hooks' import type {FocusZoneHookSettings} from '../hooks/useFocusZone' diff --git a/packages/react/src/SelectPanel/types.ts b/packages/react/src/SelectPanel/types.ts new file mode 100644 index 00000000000..fddc8d0b09d --- /dev/null +++ b/packages/react/src/SelectPanel/types.ts @@ -0,0 +1,4 @@ +import type {ItemProps} from '../deprecated/ActionList' +import type {ItemInput, GroupedListProps, ListPropsBase} from '../deprecated/ActionList/List' +// Re-exporting the types from SelectPanel for the gradual migration to drop deprecated ActionList props. +export type {ItemProps, ItemInput, GroupedListProps, ListPropsBase} diff --git a/packages/react/src/deprecated/ActionList/Item.tsx b/packages/react/src/deprecated/ActionList/Item.tsx index e2841e632d9..a9bc199974b 100644 --- a/packages/react/src/deprecated/ActionList/Item.tsx +++ b/packages/react/src/deprecated/ActionList/Item.tsx @@ -357,6 +357,8 @@ export const Item = React.forwardRef((itemProps, ref) => { const labelId = useId() const descriptionId = useId() + console.log({selectionVariant}) + const keyPressHandler = useCallback( (event: React.KeyboardEvent) => { if (disabled) { diff --git a/packages/react/src/deprecated/ActionList/List.tsx b/packages/react/src/deprecated/ActionList/List.tsx index f472fb33af2..0df41c0fbb1 100644 --- a/packages/react/src/deprecated/ActionList/List.tsx +++ b/packages/react/src/deprecated/ActionList/List.tsx @@ -149,7 +149,6 @@ function useListVariant(variant: ListProps['variant'] = 'inset'): { export const List = React.forwardRef((props, forwardedRef): JSX.Element => { // Get `sx` prop values for `List` children matching the given `List` style variation. const {firstGroupStyle, lastGroupStyle, headerStyle, itemStyle} = useListVariant(props.variant) - /** * Render a `Group` using the first of the following renderers that is defined: * A `Group`-level or `List`-level custom `Group` renderer, or From 713b5adb88c189b5eb7dec29793feff681ed6305 Mon Sep 17 00:00:00 2001 From: Armagan Ersoz Date: Tue, 6 Aug 2024 07:33:36 +1000 Subject: [PATCH 02/11] wip wip --- .../SelectPanel.examples.stories.tsx | 32 +++++++++++-------- .../react/src/deprecated/ActionList/Item.tsx | 2 ++ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx b/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx index 4b6cd435a1a..f98d6fd170b 100644 --- a/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx @@ -61,21 +61,20 @@ export const WithGroups = () => { // }, // ] - const items = [ + const items: Array = [ { id: '1', key: 1, leadingVisual: SearchIcon, - text: 'repo:github/memex', + text: 'item 1', groupId: '1', - renderItem: props => , }, { id: '2', key: 2, leadingVisual: NoteIcon, - text: 'Table', - description: 'Information-dense table optimized for operations across teams', + text: 'Item 2', + description: 'Some description', descriptionVariant: 'block', groupId: '1', }, @@ -83,8 +82,8 @@ export const WithGroups = () => { id: '3', key: 3, leadingVisual: ProjectIcon, - text: 'Board', - description: 'Kanban-style board focused on visual states', + text: 'Item 3', + description: 'Some description as well', descriptionVariant: 'block', groupId: '2', }, @@ -92,7 +91,7 @@ export const WithGroups = () => { id: '4', key: 4, leadingVisual: FilterIcon, - text: 'Save sort and filters to current view', + text: 'Item 4', groupId: '2', }, {id: '5', key: 5, leadingVisual: FilterIcon, text: 'Save sort and filters to new view', groupId: '1'}, @@ -102,14 +101,14 @@ export const WithGroups = () => { ] const [selected, setSelected] = React.useState([items[0], items[1]]) const [filter, setFilter] = React.useState('') - const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())) + const filteredItems = items.filter(item => item.text?.toLowerCase().startsWith(filter.toLowerCase())) const [open, setOpen] = useState(false) const buttonRef = useRef(null) const groupMetadata: GroupedListProps['groupMetadata'] = [ - {groupId: '0'}, - {groupId: '1', header: {title: 'Live query', variant: 'filled', 'aria-level': 3}}, - {groupId: '2', header: {title: 'Layout', variant: 'subtle', 'aria-level': 3}}, + {groupId: '0', header: {title: 'Repos', variant: 'filled'}}, + {groupId: '1', header: {title: 'Live query', variant: 'filled'}}, + {groupId: '2', header: {title: 'Layout', variant: 'filled'}}, // {groupId: '3', renderItem: props => }, // { // groupId: '4', @@ -127,6 +126,11 @@ export const WithGroups = () => { // }, ] + const onSelectedChange = (newSelected: ItemInput[]) => { + console.log({newSelected}) + setSelected(newSelected) + } + return ( <>

Multi Select Panel With Footer

@@ -141,12 +145,12 @@ export const WithGroups = () => { )} anchorRef={buttonRef} groupMetadata={groupMetadata} - placeholderText="Filter Labels" + placeholderText="Filter things" open={open} onOpenChange={setOpen} items={filteredItems} selected={selected} - onSelectedChange={setSelected} + onSelectedChange={onSelectedChange} onFilterChange={setFilter} showItemDividers={true} overlayProps={{width: 'large', height: 'xlarge'}} diff --git a/packages/react/src/deprecated/ActionList/Item.tsx b/packages/react/src/deprecated/ActionList/Item.tsx index a9bc199974b..cc10491053b 100644 --- a/packages/react/src/deprecated/ActionList/Item.tsx +++ b/packages/react/src/deprecated/ActionList/Item.tsx @@ -354,6 +354,8 @@ export const Item = React.forwardRef((itemProps, ref) => { ...props } = itemProps + console.log({selected}, {text}) + const labelId = useId() const descriptionId = useId() From 6fc7f2b8ffba7b9c2793bb07985fb28aff9eb261 Mon Sep 17 00:00:00 2001 From: Armagan Ersoz Date: Thu, 8 Aug 2024 12:41:48 +1000 Subject: [PATCH 03/11] add stories --- .../SelectPanel.examples.stories.tsx | 319 ++++++++----- .../SelectPanel.features.stories.tsx | 436 +++++++++--------- .../src/SelectPanel/SelectPanel.stories.tsx | 5 - 3 files changed, 434 insertions(+), 326 deletions(-) diff --git a/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx b/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx index f98d6fd170b..3cd82317977 100644 --- a/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx @@ -1,9 +1,10 @@ -import React, {useState, useRef} from 'react' +import React, {useState, useRef, useMemo} from 'react' +import Box from '../Box' import type {Meta} from '@storybook/react' import {Button} from '../Button' import type {ItemInput, GroupedListProps} from '../deprecated/ActionList/List' -import {ActionList} from '../deprecated/ActionList' import {SelectPanel} from './SelectPanel' +import type {OverlayProps} from '../Overlay' import { FilterIcon, GearIcon, @@ -22,138 +23,230 @@ const meta = { export default meta -export const WithGroups = () => { - // const items2 = [ - // { - // id: '1', - // key: 1, - // leadingVisual: TriangleDownIcon, - // text: 'Current attachments', - // groupId: '1', - // }, - // { - // id: '2', - // key: 2, - // leadingVisual: TriangleDownIcon, - // text: 'Files', - // groupId: '2', - // }, - // { - // id: '3', - // key: 3, - // leadingVisual: TriangleDownIcon, - // text: 'Symbols', - // groupId: '3', - // }, - // { - // id: '4', - // key: 4, - // leadingVisual: TriangleDownIcon, - // text: 'Symbols', - // groupId: '3', - // }, - // { - // id: '5', - // key: 5, - // leadingVisual: TriangleDownIcon, - // text: 'Symbols', - // groupId: '3', - // }, - // ] +function getColorCircle(color: string) { + return function () { + return ( + + ) + } +} + +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}, +] - const items: Array = [ - { - id: '1', - key: 1, - leadingVisual: SearchIcon, - text: 'item 1', - groupId: '1', - }, - { - id: '2', - key: 2, - leadingVisual: NoteIcon, - text: 'Item 2', - description: 'Some description', - descriptionVariant: 'block', - groupId: '1', - }, - { - id: '3', - key: 3, - leadingVisual: ProjectIcon, - text: 'Item 3', - description: 'Some description as well', - descriptionVariant: 'block', - groupId: '2', - }, - { - id: '4', - key: 4, - leadingVisual: FilterIcon, - text: 'Item 4', - groupId: '2', - }, - {id: '5', key: 5, leadingVisual: FilterIcon, text: 'Save sort and filters to new view', groupId: '1'}, - {id: '6', key: 6, leadingVisual: GearIcon, text: 'View settings', groupId: '0'}, - {id: '7', key: 7, leadingVisual: TypographyIcon, text: 'Rename', groupId: '0'}, - {id: '8', key: 8, leadingVisual: VersionsIcon, text: 'Duplicate', groupId: '0'}, - ] - const [selected, setSelected] = React.useState([items[0], items[1]]) +export const HeightInitialWithOverflowingItemsStory = () => { + const [selected, setSelected] = React.useState(items[0]) const [filter, setFilter] = React.useState('') - const filteredItems = items.filter(item => item.text?.toLowerCase().startsWith(filter.toLowerCase())) + const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())) const [open, setOpen] = useState(false) - const buttonRef = useRef(null) - const groupMetadata: GroupedListProps['groupMetadata'] = [ - {groupId: '0', header: {title: 'Repos', variant: 'filled'}}, - {groupId: '1', header: {title: 'Live query', variant: 'filled'}}, - {groupId: '2', header: {title: 'Layout', variant: 'filled'}}, - // {groupId: '3', renderItem: props => }, - // { - // groupId: '4', - // renderItem: ({leadingVisual: LeadingVisual, ...props}) => ( - // ( - // svg': {fill: 'white'}}}>{LeadingVisual && } - // )} - // /> - // ), - // renderGroup: ({sx: sxProps, ...props}) => ( - // - // ), - // }, - ] + return ( + <> +

Single Select Panel

+
Please select a label that describe your issue:
+ ( + + )} + 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(underflowingItems[0]) + const [filter, setFilter] = React.useState('') + const filteredItems = underflowingItems.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())) + const [open, setOpen] = useState(false) - const onSelectedChange = (newSelected: ItemInput[]) => { - console.log({newSelected}) - setSelected(newSelected) + return ( + <> +

Single Select Panel

+
Please select a label that describe your issue:
+ ( + + )} + 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(items[0]) + const [filter, setFilter] = React.useState('') + const [fetchedItems, setFetchedItems] = useState([]) + const filteredItems = React.useMemo( + () => fetchedItems.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())), + [fetchedItems, filter], + ) + const [open, setOpen] = useState(false) + const [height, setHeight] = useState('auto') + + const onOpenChange = () => { + setOpen(!open) + setTimeout(() => { + setFetchedItems([items[0], items[1]]) + setHeight('initial') + }, 1500) } return ( <> -

Multi Select Panel With Footer

+

Single Select Panel

+
Please select a label that describe your issue:
( )} - anchorRef={buttonRef} - groupMetadata={groupMetadata} - placeholderText="Filter things" + 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(items[0]) + const [filter, setFilter] = React.useState('') + const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())) + const [open, setOpen] = useState(false) + + return ( + <> +

Single Select Panel

+
Please select a label that describe your issue:
+ ( + + )} + placeholderText="Filter Labels" open={open} onOpenChange={setOpen} items={filteredItems} selected={selected} - onSelectedChange={onSelectedChange} + onSelectedChange={setSelected} + onFilterChange={setFilter} + showItemDividers={true} + overlayProps={{width: 'small', height: 'xsmall'}} + /> +
+ 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 +
+ + ) +} + +export const HeightVariantionsAndScroll = () => { + const longItems = [...items, ...items, ...items, ...items, ...items, ...items, ...items, ...items] + const [selectedA, setSelectedA] = React.useState(longItems[0]) + const [selectedB, setSelectedB] = React.useState(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 ( + <> +

With height:medium

+ ( + + )} + placeholderText="Filter Labels" + open={openA} + onOpenChange={setOpenA} + items={filteredItems} + selected={selectedA} + onSelectedChange={setSelectedA} + onFilterChange={setFilter} + showItemDividers={true} + overlayProps={{height: 'medium'}} + /> +

With height:auto, maxheight:medium

+ ( + + )} + placeholderText="Filter Labels" + open={openB} + onOpenChange={setOpenB} + items={filteredItems} + selected={selectedB} + onSelectedChange={setSelectedB} onFilterChange={setFilter} showItemDividers={true} - overlayProps={{width: 'large', height: 'xlarge'}} + overlayProps={{ + height: 'auto', + maxHeight: 'medium', + }} /> ) diff --git a/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx b/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx index 99fd5052322..1aac1bf52ce 100644 --- a/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx @@ -1,12 +1,19 @@ -import React, {useState, useRef} from 'react' +import React, {useState, useRef, useMemo} from 'react' import type {Meta} from '@storybook/react' - import Box from '../Box' import {Button} from '../Button' -import type {ItemInput} from '../deprecated/ActionList/List' +import type {ItemInput, GroupedListProps} from '../deprecated/ActionList/List' import {SelectPanel} from './SelectPanel' -import {TriangleDownIcon} from '@primer/octicons-react' -import type {OverlayProps} from '../Overlay' +import { + FilterIcon, + GearIcon, + NoteIcon, + ProjectIcon, + SearchIcon, + TriangleDownIcon, + TypographyIcon, + VersionsIcon, +} from '@primer/octicons-react' const meta = { title: 'Components/SelectPanel/Features', @@ -42,143 +49,131 @@ const items = [ {leadingVisual: getColorCircle('#8dc6fc'), text: 'frontend', id: 7}, ] -export const SingleSelectStory = () => { - const [selected, setSelected] = React.useState(items[0]) +export const WithItemDividers = () => { + const [selected, setSelected] = React.useState([items[0], items[1]]) const [filter, setFilter] = React.useState('') const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())) const [open, setOpen] = useState(false) return ( - <> -

Single Select Panel

-
Please select a label that describe your issue:
- ( - - )} - placeholderText="Filter Labels" - open={open} - onOpenChange={setOpen} - items={filteredItems} - selected={selected} - onSelectedChange={setSelected} - onFilterChange={setFilter} - showItemDividers={true} - overlayProps={{width: 'small', height: 'xsmall'}} - /> - + ( + + )} + placeholderText="Filter labels" + open={open} + onOpenChange={setOpen} + items={filteredItems} + selected={selected} + onSelectedChange={setSelected} + onFilterChange={setFilter} + showItemDividers={true} + /> ) } -SingleSelectStory.storyName = 'Single Select' -export const ExternalAnchorStory = () => { - const [selected, setSelected] = React.useState(items[0]) +export const WithTitleAndSubtitle = () => { + const [selected, setSelected] = React.useState([items[0], items[1]]) const [filter, setFilter] = React.useState('') const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())) const [open, setOpen] = useState(false) - const buttonRef = useRef(null) return ( - <> -

Select Panel With External Anchor

- - - + ( + + )} + open={open} + onOpenChange={setOpen} + items={filteredItems} + selected={selected} + onSelectedChange={setSelected} + onFilterChange={setFilter} + /> ) } -ExternalAnchorStory.storyName = 'With External Anchor' -export const WithFooterStory = () => { - const [selected, setSelected] = React.useState(items[0]) +export const WithPlaceholderForSeachInput = () => { + const [selected, setSelected] = React.useState([items[0], items[1]]) const [filter, setFilter] = React.useState('') const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())) const [open, setOpen] = useState(false) - const buttonRef = useRef(null) return ( - <> -

Select Panel With Footer

- ( - - )} - anchorRef={buttonRef} - placeholderText="Filter Labels" - open={open} - onOpenChange={setOpen} - items={filteredItems} - selected={selected} - onSelectedChange={setSelected} - onFilterChange={setFilter} - showItemDividers={true} - overlayProps={{width: 'small', height: 'medium'}} - footer={ - - } - /> - + ( + + )} + placeholderText="Filter labels" + open={open} + onOpenChange={setOpen} + items={filteredItems} + selected={selected} + onSelectedChange={setSelected} + onFilterChange={setFilter} + /> ) } -WithFooterStory.storyName = 'With Footer' -export const MultiSelectWithFooterStory = () => { - const [selected, setSelected] = React.useState([items[0], items[1]]) +export const WithPlaceholderSelect = () => { + const [selected, setSelected] = React.useState([]) const [filter, setFilter] = React.useState('') const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())) const [open, setOpen] = useState(false) - const buttonRef = useRef(null) return ( - <> -

Multi Select Panel With Footer

- ( - - )} - anchorRef={buttonRef} - placeholderText="Filter Labels" - open={open} - onOpenChange={setOpen} - items={filteredItems} - selected={selected} - onSelectedChange={setSelected} - onFilterChange={setFilter} - showItemDividers={true} - overlayProps={{width: 'small', height: 'medium'}} - footer={ - - } - /> - + ( + + )} + placeholder="Please select options below" + open={open} + onOpenChange={setOpen} + items={filteredItems} + selected={selected} + onSelectedChange={setSelected} + onFilterChange={setFilter} + /> ) } -MultiSelectWithFooterStory.storyName = 'With Footer (Multi Select)' -export const SelectPanelHeightInitialWithOverflowingItemsStory = () => { +export const SingleSelect = () => { const [selected, setSelected] = React.useState(items[0]) const [filter, setFilter] = React.useState('') const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())) @@ -202,30 +197,29 @@ export const SelectPanelHeightInitialWithOverflowingItemsStory = () => { onSelectedChange={setSelected} onFilterChange={setFilter} showItemDividers={true} - overlayProps={{width: 'small', height: 'initial', maxHeight: 'xsmall'}} + overlayProps={{width: 'small', height: 'xsmall'}} /> ) } -SelectPanelHeightInitialWithOverflowingItemsStory.storyName = 'SelectPanel, Height: Initial, Overflowing Items' -export const SelectPanelHeightInitialWithUnderflowingItemsStory = () => { - const underflowingItems = [items[0], items[1]] - const [selected, setSelected] = React.useState(underflowingItems[0]) +export const MultiSelect = () => { + const [selected, setSelected] = React.useState([items[0], items[1]]) const [filter, setFilter] = React.useState('') - const filteredItems = underflowingItems.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())) + const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())) const [open, setOpen] = useState(false) + const buttonRef = useRef(null) return ( <> -

Single Select Panel

-
Please select a label that describe your issue:
+

Multi Select Panel With Footer

( )} + anchorRef={buttonRef} placeholderText="Filter Labels" open={open} onOpenChange={setOpen} @@ -234,75 +228,59 @@ export const SelectPanelHeightInitialWithUnderflowingItemsStory = () => { onSelectedChange={setSelected} onFilterChange={setFilter} showItemDividers={true} - overlayProps={{width: 'small', height: 'initial', maxHeight: 'xsmall'}} + overlayProps={{width: 'small', height: 'medium'}} /> ) } -SelectPanelHeightInitialWithUnderflowingItemsStory.storyName = 'SelectPanel, Height: Initial, Underflowing Items' -export const SelectPanelHeightInitialWithUnderflowingItemsAfterFetch = () => { +export const WithExternalAnchor = () => { const [selected, setSelected] = React.useState(items[0]) const [filter, setFilter] = React.useState('') - const [fetchedItems, setFetchedItems] = useState([]) - const filteredItems = React.useMemo( - () => fetchedItems.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())), - [fetchedItems, filter], - ) + const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())) const [open, setOpen] = useState(false) - const [height, setHeight] = useState('auto') - - const onOpenChange = () => { - setOpen(!open) - setTimeout(() => { - setFetchedItems([items[0], items[1]]) - setHeight('initial') - }, 1500) - } + const buttonRef = useRef(null) return ( <> -

Single Select Panel

-
Please select a label that describe your issue:
+

Select Panel With External Anchor

+ ( - - )} + renderAnchor={null} + anchorRef={buttonRef} placeholderText="Filter Labels" open={open} - onOpenChange={onOpenChange} - loading={filteredItems.length === 0} + onOpenChange={setOpen} items={filteredItems} selected={selected} onSelectedChange={setSelected} onFilterChange={setFilter} showItemDividers={true} - overlayProps={{width: 'small', height, maxHeight: 'xsmall'}} + overlayProps={{width: 'small', height: 'xsmall'}} /> ) } -SelectPanelHeightInitialWithUnderflowingItemsAfterFetch.storyName = - 'SelectPanel, Height: Initial, Underflowing Items (After Fetch)' -export const SelectPanelAboveTallBody = () => { +export const WithFooter = () => { const [selected, setSelected] = React.useState(items[0]) const [filter, setFilter] = React.useState('') const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())) const [open, setOpen] = useState(false) + const buttonRef = useRef(null) return ( <> -

Single Select Panel

-
Please select a label that describe your issue:
+

Select Panel With Footer

( )} + anchorRef={buttonRef} placeholderText="Filter Labels" open={open} onOpenChange={setOpen} @@ -311,71 +289,113 @@ export const SelectPanelAboveTallBody = () => { onSelectedChange={setSelected} onFilterChange={setFilter} showItemDividers={true} - overlayProps={{width: 'small', height: 'xsmall'}} + overlayProps={{width: 'small', height: 'medium'}} + footer={ + + } /> -
- 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 -
) } -SelectPanelAboveTallBody.storyName = 'SelectPanel, Above a Tall Body' -export const SelectPanelHeightAndScroll = () => { - const longItems = [...items, ...items, ...items, ...items, ...items, ...items, ...items, ...items] - const [selectedA, setSelectedA] = React.useState(longItems[0]) - const [selectedB, setSelectedB] = React.useState(longItems[0]) +const listOfItems: Array = [ + { + id: '1', + key: 1, + leadingVisual: SearchIcon, + text: 'item 1', + groupId: '1', + }, + { + id: '2', + key: 2, + leadingVisual: NoteIcon, + text: 'Item 2', + description: 'Some description', + descriptionVariant: 'block', + groupId: '1', + }, + { + id: '3', + key: 3, + leadingVisual: ProjectIcon, + text: 'Item 3', + description: 'Some description as well', + descriptionVariant: 'block', + groupId: '2', + }, + { + id: '4', + key: 4, + leadingVisual: FilterIcon, + text: 'Item 4', + groupId: '2', + }, + {id: '5', key: 5, leadingVisual: FilterIcon, text: 'Save sort and filters to new view', groupId: '1'}, + {id: '6', key: 6, leadingVisual: GearIcon, text: 'View settings', groupId: '0'}, + {id: '7', key: 7, leadingVisual: TypographyIcon, text: 'Rename', groupId: '0'}, + {id: '8', key: 8, leadingVisual: VersionsIcon, text: 'Duplicate', groupId: '0'}, +] + +const groupMetadata: GroupedListProps['groupMetadata'] = [ + {groupId: '0', header: {title: 'Repos', variant: 'filled'}}, + {groupId: '1', header: {title: 'Live query', variant: 'filled'}}, + {groupId: '2', header: {title: 'Layout', variant: 'filled'}}, +] + +export const WithGroups = () => { + const [selectedIDs, setSelectedIDs] = useState([]) 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) + const filteredItems = listOfItems.filter(item => item.text?.toLowerCase().startsWith(filter.toLowerCase())) + const [open, setOpen] = useState(false) + const buttonRef = useRef(null) + + const onSelectedChange = async (selections: ItemInput[]) => { + const _selectedIDs = selections.flatMap(item => { + if (item.id === undefined || typeof item.id !== 'string') { + return [] + } + return item.id + }) + + setSelectedIDs(_selectedIDs) + } + + const selectedObjects: ItemInput[] = useMemo(() => { + const selected: ItemInput[] = [] + + for (const selectedID of selectedIDs) { + const item = listOfItems.find(value => value.id === selectedID) + if (item) { + selected.push(item) + } + } + return selected + }, [selectedIDs]) return ( - <> -

With height:medium

- ( - - )} - placeholderText="Filter Labels" - open={openA} - onOpenChange={setOpenA} - items={filteredItems} - selected={selectedA} - onSelectedChange={setSelectedA} - onFilterChange={setFilter} - showItemDividers={true} - overlayProps={{height: 'medium'}} - /> -

With height:auto, maxheight:medium

- ( - - )} - placeholderText="Filter Labels" - open={openB} - onOpenChange={setOpenB} - items={filteredItems} - selected={selectedB} - onSelectedChange={setSelectedB} - onFilterChange={setFilter} - showItemDividers={true} - overlayProps={{ - height: 'auto', - maxHeight: 'medium', - }} - /> - + ( + + )} + anchorRef={buttonRef} + groupMetadata={groupMetadata} + placeholderText="Filter things" + open={open} + onOpenChange={setOpen} + items={filteredItems} + selected={selectedObjects} + onSelectedChange={onSelectedChange} + onFilterChange={setFilter} + showItemDividers={true} + overlayProps={{width: 'large', height: 'xlarge'}} + /> ) } -SelectPanelHeightAndScroll.storyName = 'SelectPanel, Height and Scroll' diff --git a/packages/react/src/SelectPanel/SelectPanel.stories.tsx b/packages/react/src/SelectPanel/SelectPanel.stories.tsx index acc3fe1683e..8b8ce50a166 100644 --- a/packages/react/src/SelectPanel/SelectPanel.stories.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.stories.tsx @@ -53,8 +53,6 @@ export const Default = () => { <>

Multi Select Panel

( )} - placeholderText="Filter labels" open={open} onOpenChange={setOpen} items={filteredItems} selected={selected} onSelectedChange={setSelected} onFilterChange={setFilter} - showItemDividers={true} - overlayProps={{width: 'small', height: 'xsmall'}} /> ) From a246981cc9f4d603c6d8720396246d4d8eca8016 Mon Sep 17 00:00:00 2001 From: Armagan Ersoz Date: Thu, 8 Aug 2024 12:56:39 +1000 Subject: [PATCH 04/11] fix linting --- .../SelectPanel/SelectPanel.examples.stories.tsx | 15 +++------------ packages/react/src/deprecated/ActionList/Item.tsx | 4 ---- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx b/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx index 3cd82317977..3dd388f5a77 100644 --- a/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.examples.stories.tsx @@ -1,20 +1,11 @@ -import React, {useState, useRef, useMemo} from 'react' +import React, {useState} from 'react' import Box from '../Box' import type {Meta} from '@storybook/react' import {Button} from '../Button' -import type {ItemInput, GroupedListProps} from '../deprecated/ActionList/List' +import type {ItemInput} from '../deprecated/ActionList/List' import {SelectPanel} from './SelectPanel' import type {OverlayProps} from '../Overlay' -import { - FilterIcon, - GearIcon, - NoteIcon, - ProjectIcon, - SearchIcon, - TriangleDownIcon, - TypographyIcon, - VersionsIcon, -} from '@primer/octicons-react' +import {TriangleDownIcon} from '@primer/octicons-react' const meta = { title: 'Components/SelectPanel/Examples', diff --git a/packages/react/src/deprecated/ActionList/Item.tsx b/packages/react/src/deprecated/ActionList/Item.tsx index cc10491053b..e2841e632d9 100644 --- a/packages/react/src/deprecated/ActionList/Item.tsx +++ b/packages/react/src/deprecated/ActionList/Item.tsx @@ -354,13 +354,9 @@ export const Item = React.forwardRef((itemProps, ref) => { ...props } = itemProps - console.log({selected}, {text}) - const labelId = useId() const descriptionId = useId() - console.log({selectionVariant}) - const keyPressHandler = useCallback( (event: React.KeyboardEvent) => { if (disabled) { From e209f472f65feea66bdefc7f7354576e0a6eb72d Mon Sep 17 00:00:00 2001 From: Armagan Ersoz Date: Thu, 8 Aug 2024 16:41:27 +1000 Subject: [PATCH 05/11] add tests for groups --- .../SelectPanel.features.stories.tsx | 2 +- .../src/SelectPanel/SelectPanel.test.tsx | 119 ++++++++++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) diff --git a/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx b/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx index 1aac1bf52ce..c4bec570a4a 100644 --- a/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx @@ -162,7 +162,7 @@ export const WithPlaceholderSelect = () => { {children ?? 'Select Labels'} )} - placeholder="Please select options below" + placeholder="Select issue labels" open={open} onOpenChange={setOpen} items={filteredItems} diff --git a/packages/react/src/SelectPanel/SelectPanel.test.tsx b/packages/react/src/SelectPanel/SelectPanel.test.tsx index a3a2d7d2576..636cd390973 100644 --- a/packages/react/src/SelectPanel/SelectPanel.test.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.test.tsx @@ -1,6 +1,7 @@ import {render, screen} from '@testing-library/react' import React from 'react' import {SelectPanel, type SelectPanelProps} from '../SelectPanel' +import type {ItemInput, GroupedListProps} from '../deprecated/ActionList/List' import {userEvent} from '@testing-library/user-event' import ThemeProvider from '../ThemeProvider' @@ -373,4 +374,122 @@ describe('SelectPanel', () => { expect(screen.getByText('test footer')).toBeVisible() }) }) + + const listOfItems: Array = [ + { + id: '1', + key: 1, + text: 'Item 1', + groupId: '1', + }, + { + id: '2', + key: 2, + text: 'Item 2', + groupId: '1', + }, + { + id: '3', + key: 3, + text: 'Item 3', + groupId: '2', + }, + { + id: '4', + key: 4, + text: 'Item 4', + groupId: '3', + }, + ] + + const groupMetadata: GroupedListProps['groupMetadata'] = [ + {groupId: '1', header: {title: 'Group title 1'}}, + {groupId: '2', header: {title: 'Group title 2'}}, + {groupId: '3', header: {title: 'Group title 3'}}, + ] + + function SelectPanelWithGroups() { + const [selectedItems, setSelectedItems] = React.useState([]) + const [open, setOpen] = React.useState(false) + const [filter, setFilter] = React.useState('') + + const onSelectedChange = (selections: ItemInput[]) => { + setSelectedItems(selections) + } + + return ( + + { + setOpen(isOpen) + }} + filterValue={filter} + onFilterChange={value => { + setFilter(value) + }} + /> + + ) + } + + describe('with groups', () => { + it('should render groups with items', async () => { + const user = userEvent.setup() + + render() + + await user.click(screen.getByText('Select items')) + const listbox = screen.getByRole('listbox') + expect(listbox).toBeVisible() + expect(listbox).toHaveAttribute('aria-multiselectable', 'true') + + // listbox should has 3 gorups and each have heading + const headings = screen.getAllByRole('heading') + + // The first heading is the h1 and it is the title of the dialog + expect(headings[1]).toHaveTextContent('Group title 1') + expect(headings[2]).toHaveTextContent('Group title 2') + expect(headings[3]).toHaveTextContent('Group title 3') + + expect(screen.getAllByRole('option')).toHaveLength(4) + }) + it('should select items within groups', async () => { + const user = userEvent.setup() + + render() + + await user.click(screen.getByText('Select items')) + + // Select the first item + await user.click(screen.getByRole('option', {name: 'Item 1'})) + expect( + screen.getByRole('option', { + name: 'Item 1', + }), + ).toHaveAttribute('aria-selected', 'true') + + await user.click(screen.getByRole('option', {name: 'Item 3'})) + expect( + screen.getByRole('option', { + name: 'Item 3', + }), + ).toHaveAttribute('aria-selected', 'true') + + await user.click(screen.getByRole('option', {name: 'Item 4'})) + expect( + screen.getByRole('option', { + name: 'Item 4', + }), + ).toHaveAttribute('aria-selected', 'true') + }) + }) }) From 67dc5c9f036f4d0ffde94ed4a350eae1d6a6eb72 Mon Sep 17 00:00:00 2001 From: Armagan Ersoz Date: Mon, 12 Aug 2024 11:22:28 +1000 Subject: [PATCH 06/11] Update story names for e2e tests --- e2e/components/SelectPanel.test.ts | 261 +++++++++++++++++- e2e/components/TabPages.test.ts | 33 +++ .../SelectPanel.features.stories.tsx | 30 -- .../src/SelectPanel/SelectPanel.stories.tsx | 2 + .../react/src/deprecated/ActionList/List.tsx | 1 + script/generate-e2e-tests.js | 40 ++- 6 files changed, 317 insertions(+), 50 deletions(-) create mode 100644 e2e/components/TabPages.test.ts diff --git a/e2e/components/SelectPanel.test.ts b/e2e/components/SelectPanel.test.ts index 8db0ed311f3..db1f076c22c 100644 --- a/e2e/components/SelectPanel.test.ts +++ b/e2e/components/SelectPanel.test.ts @@ -14,6 +14,9 @@ test.describe('SelectPanel', () => { }, }) + // Open select panel + await page.keyboard.press('Tab') + await page.keyboard.press('Enter') // Default state expect(await page.screenshot()).toMatchSnapshot(`SelectPanel.Default.${theme}.png`) }) @@ -42,6 +45,9 @@ test.describe('SelectPanel', () => { }, }) + // Open select panel + await page.keyboard.press('Tab') + await page.keyboard.press('Enter') // Default state expect(await page.screenshot()).toMatchSnapshot(`SelectPanel.Single Select.${theme}.png`) }) @@ -70,6 +76,9 @@ test.describe('SelectPanel', () => { }, }) + // Open select panel + await page.keyboard.press('Tab') + await page.keyboard.press('Enter') // Default state expect(await page.screenshot()).toMatchSnapshot(`SelectPanel.External Anchor.${theme}.png`) }) @@ -87,26 +96,246 @@ test.describe('SelectPanel', () => { } }) - test.describe('SelectPanel, Initial Height, Overflowing Items', () => { + test.describe('With Footer', () => { for (const theme of themes) { test.describe(theme, () => { test('default @vrt', async ({page}) => { await visit(page, { - id: 'components-selectpanel-features--select-panel-height-initial-with-overflowing-items-story', + id: 'components-selectpanel-features--with-footer', globals: { colorScheme: theme, }, }) + // Open select panel + await page.keyboard.press('Tab') + await page.keyboard.press('Enter') + // Default state + expect(await page.screenshot()).toMatchSnapshot(`SelectPanel.With Footer.${theme}.png`) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-selectpanel-features--with-footer', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) + + test.describe('With Groups', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-selectpanel-features--with-groups', + globals: { + colorScheme: theme, + }, + }) + + // Open select panel + await page.keyboard.press('Tab') + await page.keyboard.press('Enter') + // Default state + expect(await page.screenshot()).toMatchSnapshot(`SelectPanel.With Groups.${theme}.png`) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-selectpanel-features--with-groups', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) + + test.describe('With Item Dividers', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-selectpanel-features--with-item-dividers', + globals: { + colorScheme: theme, + }, + }) + + // Open select panel + await page.keyboard.press('Tab') + await page.keyboard.press('Enter') + // Default state + expect(await page.screenshot()).toMatchSnapshot(`SelectPanel.With Item Dividers.${theme}.png`) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-selectpanel-features--with-item-dividers', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) + + test.describe('With Placeholder for Search Input', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-selectpanel-features--with-placeholder-for-seach-input', + globals: { + colorScheme: theme, + }, + }) + + // Open select panel + await page.keyboard.press('Tab') + await page.keyboard.press('Enter') + // Default state + expect(await page.screenshot()).toMatchSnapshot(`SelectPanel.With Placeholder for Search Input.${theme}.png`) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-selectpanel-features--with-placeholder-for-seach-input', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) + + test.describe('With Placeholder Select', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-selectpanel-features--with-placeholder-select', + globals: { + colorScheme: theme, + }, + }) + + // Open select panel + await page.keyboard.press('Tab') + await page.keyboard.press('Enter') + // Default state + expect(await page.screenshot()).toMatchSnapshot(`SelectPanel.With Placeholder Select.${theme}.png`) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-selectpanel-features--with-placeholder-select', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) + + test.describe('Above Tall Body', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-selectpanel-examples--above-tall-body', + globals: { + colorScheme: theme, + }, + }) + + // Open select panel + await page.keyboard.press('Tab') + await page.keyboard.press('Enter') + // Default state + expect(await page.screenshot()).toMatchSnapshot(`SelectPanel.Above Tall Body.${theme}.png`) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-selectpanel-examples--above-tall-body', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) + + test.describe('Height Variantions and Scroll', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-selectpanel-examples--height-variantions-and-scroll', + globals: { + colorScheme: theme, + }, + }) + + // Open select panel + await page.keyboard.press('Tab') + await page.keyboard.press('Enter') + // Default state + expect(await page.screenshot()).toMatchSnapshot(`SelectPanel.Height Variantions and Scroll.${theme}.png`) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-selectpanel-examples--height-variantions-and-scroll', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) + + test.describe('Height Initial with Overflowing Items', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-selectpanel-examples--height-initial-with-overflowing-items-story', + globals: { + colorScheme: theme, + }, + }) + + // Open select panel + await page.keyboard.press('Tab') + await page.keyboard.press('Enter') // Default state expect(await page.screenshot()).toMatchSnapshot( - `SelectPanel.SelectPanel, Initial Height, Overflowing Items.${theme}.png`, + `SelectPanel.Height Initial with Overflowing Items.${theme}.png`, ) }) test('axe @aat', async ({page}) => { await visit(page, { - id: 'components-selectpanel-features--select-panel-height-initial-with-overflowing-items-story', + id: 'components-selectpanel-examples--height-initial-with-overflowing-items-story', globals: { colorScheme: theme, }, @@ -117,26 +346,29 @@ test.describe('SelectPanel', () => { } }) - test.describe('SelectPanel, Initial Height, Underflowing Items', () => { + test.describe('Height Initial with Underflowing Items', () => { for (const theme of themes) { test.describe(theme, () => { test('default @vrt', async ({page}) => { await visit(page, { - id: 'components-selectpanel-features--select-panel-height-initial-with-underflowing-items-story', + id: 'components-selectpanel-examples--height-initial-with-underflowing-items-story', globals: { colorScheme: theme, }, }) + // Open select panel + await page.keyboard.press('Tab') + await page.keyboard.press('Enter') // Default state expect(await page.screenshot()).toMatchSnapshot( - `SelectPanel.SelectPanel, Initial Height, Underflowing Items.${theme}.png`, + `SelectPanel.Height Initial with Underflowing Items.${theme}.png`, ) }) test('axe @aat', async ({page}) => { await visit(page, { - id: 'components-selectpanel-features--select-panel-height-initial-with-underflowing-items-story', + id: 'components-selectpanel-examples--height-initial-with-underflowing-items-story', globals: { colorScheme: theme, }, @@ -147,24 +379,29 @@ test.describe('SelectPanel', () => { } }) - test.describe('SelectPanel, Above a Tall Body', () => { + test.describe('Height Initial with Underflowing Items After Fetch', () => { for (const theme of themes) { test.describe(theme, () => { test('default @vrt', async ({page}) => { await visit(page, { - id: 'components-selectpanel-features--select-panel-above-tall-body', + id: 'components-selectpanel-examples--height-initial-with-underflowing-items-after-fetch', globals: { colorScheme: theme, }, }) + // Open select panel + await page.keyboard.press('Tab') + await page.keyboard.press('Enter') // Default state - expect(await page.screenshot()).toMatchSnapshot(`SelectPanel.SelectPanel, Above a Tall Body.${theme}.png`) + expect(await page.screenshot()).toMatchSnapshot( + `SelectPanel.Height Initial with Underflowing Items After Fetch.${theme}.png`, + ) }) test('axe @aat', async ({page}) => { await visit(page, { - id: 'components-selectpanel-features--select-panel-above-tall-body', + id: 'components-selectpanel-examples--height-initial-with-underflowing-items-after-fetch', globals: { colorScheme: theme, }, diff --git a/e2e/components/TabPages.test.ts b/e2e/components/TabPages.test.ts new file mode 100644 index 00000000000..65c665be111 --- /dev/null +++ b/e2e/components/TabPages.test.ts @@ -0,0 +1,33 @@ +import {test, expect} from '@playwright/test' +import {visit} from '../test-helpers/storybook' +import {themes} from '../test-helpers/themes' + +test.describe('TabPages', () => { + test.describe('Default', () => { + for (const theme of themes) { + test.describe(theme, () => { + test('default @vrt', async ({page}) => { + await visit(page, { + id: 'components-tabpages--default', + globals: { + colorScheme: theme, + }, + }) + + // Default state + expect(await page.screenshot()).toMatchSnapshot(`TabPages.Default.${theme}.png`) + }) + + test('axe @aat', async ({page}) => { + await visit(page, { + id: 'components-tabpages--default', + globals: { + colorScheme: theme, + }, + }) + await expect(page).toHaveNoViolations() + }) + }) + } + }) +}) diff --git a/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx b/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx index c4bec570a4a..da4bb2084be 100644 --- a/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx @@ -81,36 +81,6 @@ export const WithItemDividers = () => { ) } -export const WithTitleAndSubtitle = () => { - const [selected, setSelected] = React.useState([items[0], items[1]]) - const [filter, setFilter] = React.useState('') - const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())) - const [open, setOpen] = useState(false) - - return ( - ( - - )} - open={open} - onOpenChange={setOpen} - items={filteredItems} - selected={selected} - onSelectedChange={setSelected} - onFilterChange={setFilter} - /> - ) -} - export const WithPlaceholderForSeachInput = () => { const [selected, setSelected] = React.useState([items[0], items[1]]) const [filter, setFilter] = React.useState('') diff --git a/packages/react/src/SelectPanel/SelectPanel.stories.tsx b/packages/react/src/SelectPanel/SelectPanel.stories.tsx index 8b8ce50a166..a8951c1211a 100644 --- a/packages/react/src/SelectPanel/SelectPanel.stories.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.stories.tsx @@ -53,6 +53,8 @@ export const Default = () => { <>

Multi Select Panel

(