diff --git a/packages/@react-spectrum/s2/chromatic/SelectBoxGroup.stories.tsx b/packages/@react-spectrum/s2/chromatic/SelectBoxGroup.stories.tsx index 21f10c0ec40..8b180832471 100644 --- a/packages/@react-spectrum/s2/chromatic/SelectBoxGroup.stories.tsx +++ b/packages/@react-spectrum/s2/chromatic/SelectBoxGroup.stories.tsx @@ -31,17 +31,18 @@ type Story = StoryObj; export const VerticalOrientation: Story = { render: () => (
- - + V: Text Only - + V: Illustration + Text - + @@ -52,21 +53,22 @@ export const VerticalOrientation: Story = { export const HorizontalOrientation: Story = { render: () => (
- - + Title Only - + Illustration + Title - + Title + Description Additional description - + Illustration + Title + Description Full horizontal layout with all elements diff --git a/packages/@react-spectrum/s2/src/SelectBoxGroup.tsx b/packages/@react-spectrum/s2/src/SelectBoxGroup.tsx index 213af07bf79..6bdadab9128 100644 --- a/packages/@react-spectrum/s2/src/SelectBoxGroup.tsx +++ b/packages/@react-spectrum/s2/src/SelectBoxGroup.tsx @@ -9,6 +9,7 @@ * governing permissions and limitations under the License. */ +import {baseColor, focusRing, style} from '../style' with {type: 'macro'}; import {box, iconStyles} from './Checkbox'; import Checkmark from '../ui-icons/Checkmark'; import { @@ -19,8 +20,7 @@ import { ListBoxProps, Provider } from 'react-aria-components'; -import {DOMRef, DOMRefValue, GlobalDOMAttributes, Orientation} from '@react-types/shared'; -import {focusRing, style} from '../style' with {type: 'macro'}; +import {DOMRef, DOMRefValue, GlobalDOMAttributes, Key, Orientation} from '@react-types/shared'; import {getAllowedOverrides, StyleProps} from './style-utils' with {type: 'macro'}; import {IllustrationContext} from '../src/Icon'; import {pressScale} from './pressScale'; @@ -28,11 +28,16 @@ import React, {createContext, forwardRef, ReactNode, useContext, useMemo, useRef import {TextContext} from './Content'; import {useSpectrumContextProps} from './useSpectrumContextProps'; -export interface SelectBoxGroupProps extends StyleProps, Omit, keyof GlobalDOMAttributes | 'layout' | 'dragAndDropHooks' | 'dependencies' | 'renderEmptyState' | 'children' | 'onAction' | 'shouldFocusOnHover' | 'selectionBehavior' | 'style' | 'className'> { +export interface SelectBoxGroupProps extends StyleProps, Omit, keyof GlobalDOMAttributes | 'layout' | 'dragAndDropHooks' | 'dependencies' | 'renderEmptyState' | 'children' | 'onAction' | 'shouldFocusOnHover' | 'selectionBehavior' | 'shouldSelectOnPressUp' | 'shouldFocusWrap' | 'style' | 'className'> { /** * The SelectBox elements contained within the SelectBoxGroup. */ children: ReactNode, + /** + * The layout direction of the content in each SelectBox. + * @default 'vertical' + */ + orientation?: Orientation, /** * The selection mode for the SelectBoxGroup. * @default 'single' @@ -45,12 +50,14 @@ export interface SelectBoxGroupProps extends StyleProps, Omit } export interface SelectBoxProps extends StyleProps { + /** The unique id of the SelectBox. */ + id?: Key, + /** A string representation of the SelectBox's contents, used for features like typeahead. */ + textValue?: string, + /** An accessibility label for this item. */ + 'aria-label'?: string, /** - * The value of the SelectBox. - */ - value: string, - /** - * The label for the element. + * The contents of the SelectBox. */ children: ReactNode, /** @@ -76,6 +83,7 @@ const selectBoxStyles = style({ gridAutoRows: '1fr', position: 'relative', font: 'ui', + cursor: 'default', boxSizing: 'border-box', overflow: 'hidden', width: { @@ -176,7 +184,7 @@ const selectBoxStyles = style({ }, backgroundColor: { default: 'layer-2', - isDisabled: 'layer-1' + isDisabled: 'disabled' }, color: { isDisabled: 'disabled' @@ -196,12 +204,12 @@ const illustrationContainer = style({ alignSelf: 'center', justifySelf: 'center', minSize: 48, - color: { - isDisabled: 'disabled', - isHovered: 'gray-900' - }, - opacity: { - isDisabled: 0.4 + '--iconPrimary': { + type: 'color', + value: { + default: baseColor('neutral'), + isDisabled: 'disabled' + } } }); @@ -222,8 +230,7 @@ const descriptionText = style({ } }, color: { - default: 'neutral', - isHovered: 'gray-900', + default: baseColor('neutral'), isDisabled: 'disabled' } }); @@ -254,8 +261,7 @@ const labelText = style({ } }, color: { - default: 'neutral', - isHovered: 'gray-900', + default: baseColor('neutral'), isDisabled: 'disabled' } }); @@ -263,11 +269,21 @@ const labelText = style({ const gridStyles = style<{orientation?: Orientation}>({ display: 'grid', gridAutoRows: '1fr', - gap: 16, + gap: 24, + justifyContent: 'center', + '--size': { + type: 'width', + value: { + orientation: { + horizontal: 368, + vertical: 170 + } + } + }, gridTemplateColumns: { orientation: { - horizontal: 'repeat(auto-fit, minmax(368px, 1fr))', - vertical: 'repeat(auto-fit, minmax(170px, 1fr))' + horizontal: 'repeat(auto-fit, var(--size))', + vertical: 'repeat(auto-fit, var(--size))' } } }, getAllowedOverrides()); @@ -276,7 +292,7 @@ const gridStyles = style<{orientation?: Orientation}>({ * SelectBox is a single selectable item in a SelectBoxGroup. */ export function SelectBox(props: SelectBoxProps): ReactNode { - let {children, value, isDisabled: individualDisabled = false, UNSAFE_style, UNSAFE_className, styles, ...otherProps} = props; + let {children, isDisabled: individualDisabled = false, UNSAFE_style, UNSAFE_className, styles, ...otherProps} = props; let { orientation = 'vertical', @@ -288,8 +304,6 @@ export function SelectBox(props: SelectBoxProps): ReactNode { return ( (UNSAFE_className || '') + selectBoxStyles({ @@ -364,6 +378,7 @@ export const SelectBoxGroup = /*#__PURE__*/ forwardRef(function SelectBoxGroup diff --git a/packages/@react-spectrum/s2/stories/SelectBoxGroup.stories.tsx b/packages/@react-spectrum/s2/stories/SelectBoxGroup.stories.tsx index 60288865f4d..c2f9889cd5c 100644 --- a/packages/@react-spectrum/s2/stories/SelectBoxGroup.stories.tsx +++ b/packages/@react-spectrum/s2/stories/SelectBoxGroup.stories.tsx @@ -17,7 +17,7 @@ import PaperAirplane from '../spectrum-illustrations/linear/Paperairplane'; import React from 'react'; import {SelectBox, SelectBoxGroup, Text} from '../src'; import Server from '../spectrum-illustrations/linear/Server'; -import StarFilled1 from '../spectrum-illustrations/gradient/generic1/Star'; +import StarFilled1 from '../spectrum-illustrations/linear/Star'; import {style} from '../style' with {type: 'macro'}; const headingStyles = style({ @@ -42,11 +42,8 @@ const sectionHeadingStyles = style({ }); const meta: Meta = { - title: 'SelectBoxGroup', + title: 'SelectBoxGroup (alpha)', component: SelectBoxGroup, - parameters: { - layout: 'centered' - }, tags: ['autodocs'], argTypes: { selectionMode: { @@ -73,28 +70,26 @@ type Story = StoryObj; export const Default: Story = { render: (args) => { return ( -
- - - - Amazon Web Services - Reliable cloud infrastructure - - - - Microsoft Azure - - - - Google Cloud Platform - - - - IBM Cloud - Hybrid cloud solutions - - -
+ + + + Amazon Web Services + Reliable cloud infrastructure + + + + Microsoft Azure + + + + Google Cloud Platform + + + + IBM Cloud + Hybrid cloud solutions + + ); } }; @@ -113,9 +108,10 @@ export const AllSlotCombinations: Story = {

Text Only

- + Simple Text @@ -125,9 +121,10 @@ export const AllSlotCombinations: Story = {

Illustration + Text

- + With Illustration @@ -137,10 +134,11 @@ export const AllSlotCombinations: Story = { {/* Text + Description */}

Text + Description

- - + Main Text Additional description @@ -151,9 +149,10 @@ export const AllSlotCombinations: Story = {

Illustration + Description

- + Only description text @@ -164,9 +163,10 @@ export const AllSlotCombinations: Story = {

Illustration + Text + Description

- + Full Vertical Complete description @@ -186,9 +186,10 @@ export const AllSlotCombinations: Story = {

Text Only

- + Simple Horizontal Text @@ -197,10 +198,11 @@ export const AllSlotCombinations: Story = { {/* Illustration + Text */}

Illustration + Text

- - + Horizontal with Illustration @@ -210,10 +212,11 @@ export const AllSlotCombinations: Story = { {/* Text + Description */}

Text + Description

- - + Main Horizontal Text Horizontal description text @@ -224,9 +227,10 @@ export const AllSlotCombinations: Story = {

Illustration + Text + Description

- + Complete Horizontal Full horizontal layout with all elements @@ -241,29 +245,30 @@ export const AllSlotCombinations: Story = {

Side-by-Side Comparison

{/* Vertical examples */} - + V: Text Only - + V: Illustration + Text - + V: Text + Desc Vertical description - + V: Illustration + Desc - + V: All Elements Complete vertical @@ -272,31 +277,32 @@ export const AllSlotCombinations: Story = {
- {/* Horizontal examples */} - + H: Text Only - + H: Illustration + Text - + H: Text + Description Horizontal description - + H: Illustration + Desc - + H: All Elements Complete horizontal layout diff --git a/packages/@react-spectrum/s2/test/SelectBoxGroup.test.tsx b/packages/@react-spectrum/s2/test/SelectBoxGroup.test.tsx index 8767055bf3d..de029ab0b41 100644 --- a/packages/@react-spectrum/s2/test/SelectBoxGroup.test.tsx +++ b/packages/@react-spectrum/s2/test/SelectBoxGroup.test.tsx @@ -14,13 +14,13 @@ function ControlledSingleSelectBox() { selectionMode="single" onSelectionChange={setSelectedKeys} selectedKeys={selectedKeys}> - + Option 1 - + Option 2 - + Option 3 @@ -35,13 +35,13 @@ function ControlledMultiSelectBox() { selectionMode="multiple" onSelectionChange={setSelectedKeys} selectedKeys={selectedKeys}> - + Option 1 - + Option 2 - + Option 3 @@ -53,13 +53,13 @@ function UncontrolledSelectBox({selectionMode = 'single'}: {selectionMode?: 'sin - + Option 1 - + Option 2 - + Option 3 @@ -74,10 +74,10 @@ function DisabledSelectBox() { onSelectionChange={() => {}} selectedKeys={new Set()} isDisabled> - + Option 1 - + Option 2 @@ -122,7 +122,7 @@ describe('SelectBoxGroup', () => { it('handles uncontrolled click selection in single mode', async () => { render(); let listboxTester = testUtilUser.createTester('ListBox', {root: screen.getByRole('listbox')}); - + await listboxTester.toggleOptionSelection({option: 0}); expect(listboxTester.options()[0]).toHaveAttribute('aria-selected', 'true'); }); @@ -130,10 +130,10 @@ describe('SelectBoxGroup', () => { it('handles uncontrolled click selection in multiple mode', async () => { render(); let listboxTester = testUtilUser.createTester('ListBox', {root: screen.getByRole('listbox')}); - + await listboxTester.toggleOptionSelection({option: 0}); await listboxTester.toggleOptionSelection({option: 1}); - + expect(listboxTester.options()[0]).toHaveAttribute('aria-selected', 'true'); expect(listboxTester.options()[1]).toHaveAttribute('aria-selected', 'true'); }); @@ -141,10 +141,10 @@ describe('SelectBoxGroup', () => { it('handles uncontrolled selection toggle', async () => { render(); let listboxTester = testUtilUser.createTester('ListBox', {root: screen.getByRole('listbox')}); - + await listboxTester.toggleOptionSelection({option: 0}); expect(listboxTester.options()[0]).toHaveAttribute('aria-selected', 'true'); - + // Toggle off in single mode by selecting another await listboxTester.toggleOptionSelection({option: 1}); expect(listboxTester.options()[0]).toHaveAttribute('aria-selected', 'false'); @@ -154,15 +154,15 @@ describe('SelectBoxGroup', () => { it('handles uncontrolled keyboard selection', async () => { render(); const listbox = screen.getByRole('listbox'); - + await act(async () => { listbox.focus(); }); - + await act(async () => { await user.keyboard(' '); }); - + const option1 = screen.getByRole('option', {name: 'Option 1'}); expect(option1).toHaveAttribute('aria-selected', 'true'); }); @@ -172,7 +172,7 @@ describe('SelectBoxGroup', () => { it('handles controlled selection in single mode', async () => { render(); let listboxTester = testUtilUser.createTester('ListBox', {root: screen.getByRole('listbox')}); - + await listboxTester.toggleOptionSelection({option: 0}); expect(listboxTester.options()[0]).toHaveAttribute('aria-selected', 'true'); }); @@ -180,10 +180,10 @@ describe('SelectBoxGroup', () => { it('handles controlled multiple selection', async () => { render(); let listboxTester = testUtilUser.createTester('ListBox', {root: screen.getByRole('listbox')}); - + await listboxTester.toggleOptionSelection({option: 0}); await listboxTester.toggleOptionSelection({option: 1}); - + expect(listboxTester.options()[0]).toHaveAttribute('aria-selected', 'true'); expect(listboxTester.options()[1]).toHaveAttribute('aria-selected', 'true'); }); @@ -201,10 +201,10 @@ describe('SelectBoxGroup', () => { onSelectionChange(keys); }} selectedKeys={selectedKeys}> - + Option 1 - + Option 2 @@ -214,7 +214,7 @@ describe('SelectBoxGroup', () => { render(); const option1 = screen.getByRole('option', {name: 'Option 1'}); await user.click(option1); - + expect(onSelectionChange).toHaveBeenCalledTimes(1); const receivedSelection = onSelectionChange.mock.calls[0][0]; expect(Array.from(receivedSelection)).toEqual(['option1']); @@ -233,10 +233,10 @@ describe('SelectBoxGroup', () => { onSelectionChange(keys); }} selectedKeys={selectedKeys}> - + Option 1 - + Option 2 @@ -246,7 +246,7 @@ describe('SelectBoxGroup', () => { render(); const option1 = screen.getByRole('option', {name: 'Option 1'}); await user.click(option1); - + expect(onSelectionChange).toHaveBeenCalledTimes(1); const receivedSelection = onSelectionChange.mock.calls[0][0]; expect(Array.from(receivedSelection)).toEqual(['option1']); @@ -258,7 +258,7 @@ describe('SelectBoxGroup', () => { render(); const listbox = screen.getByRole('listbox'); expect(listbox).toBeInTheDocument(); - + const options = screen.getAllByRole('option'); expect(options.length).toBeGreaterThan(0); }); @@ -272,10 +272,10 @@ describe('SelectBoxGroup', () => { onSelectionChange={onSelectionChange} selectedKeys={new Set()} isDisabled> - + Option 1 - + Option 2 @@ -286,7 +286,7 @@ describe('SelectBoxGroup', () => { await user.click(option1); await user.click(option2); expect(onSelectionChange).not.toHaveBeenCalled(); - + expect(option1).toHaveAttribute('aria-disabled', 'true'); expect(option2).toHaveAttribute('aria-disabled', 'true'); }); @@ -297,10 +297,10 @@ describe('SelectBoxGroup', () => { aria-label="Uncontrolled disabled test" selectionMode="single" isDisabled> - + Option 1 - + Option 2 @@ -308,10 +308,10 @@ describe('SelectBoxGroup', () => { const option1 = screen.getByRole('option', {name: 'Option 1'}); const option2 = screen.getByRole('option', {name: 'Option 2'}); - + await user.click(option1); await user.click(option2); - + // should have disabled attributes and no selection expect(option1).toHaveAttribute('aria-disabled', 'true'); expect(option2).toHaveAttribute('aria-disabled', 'true'); @@ -323,15 +323,15 @@ describe('SelectBoxGroup', () => { describe('Checkbox functionality', () => { it('shows checkbox when item is selected in controlled mode', async () => { render( - {}} + selectionMode="single" + onSelectionChange={() => {}} selectedKeys={new Set(['option1'])}> - + Option 1 - + Option 2 @@ -339,17 +339,17 @@ describe('SelectBoxGroup', () => { const selectedRow = screen.getByRole('option', {name: 'Option 1'}); expect(selectedRow).toHaveAttribute('aria-selected', 'true'); - + const checkboxDiv = selectedRow.querySelector('[aria-hidden="true"]'); expect(checkboxDiv).toBeInTheDocument(); }); it('shows checkbox when item is selected in uncontrolled mode', async () => { render(); - + const option1 = screen.getByRole('option', {name: 'Option 1'}); await user.click(option1); - + expect(option1).toHaveAttribute('aria-selected', 'true'); const checkboxDiv = option1.querySelector('[aria-hidden="true"]'); expect(checkboxDiv).toBeInTheDocument(); @@ -357,19 +357,19 @@ describe('SelectBoxGroup', () => { it('shows checkbox on hover for non-disabled items in controlled mode', async () => { render( - {}} + selectionMode="single" + onSelectionChange={() => {}} selectedKeys={new Set()}> - + Option 1 ); const row = screen.getByRole('option', {name: 'Option 1'}); - + await user.hover(row); await waitFor(() => { const checkboxDiv = row.querySelector('[aria-hidden="true"]'); @@ -381,7 +381,7 @@ describe('SelectBoxGroup', () => { render(); const row = screen.getByRole('option', {name: 'Option 1'}); - + await user.hover(row); await waitFor(() => { const checkboxDiv = row.querySelector('[aria-hidden="true"]'); @@ -391,38 +391,38 @@ describe('SelectBoxGroup', () => { it('shows checkbox for disabled but selected items', () => { render( - {}} + selectionMode="single" + onSelectionChange={() => {}} defaultSelectedKeys={new Set(['option1'])}> - + Option 1 ); const row = screen.getByRole('option', {name: 'Option 1'}); - + const checkboxDiv = row.querySelector('[aria-hidden="true"]'); expect(checkboxDiv).toBeInTheDocument(); }); it('shows checkbox for disabled items (always show checkboxes)', async () => { render( - {}} + selectionMode="single" + onSelectionChange={() => {}} selectedKeys={new Set()}> - + Option 1 ); const row = screen.getByRole('option', {name: 'Option 1'}); - + // checkbox always present const checkboxDiv = row.querySelector('[aria-hidden="true"]'); expect(checkboxDiv).toBeInTheDocument(); @@ -432,13 +432,13 @@ describe('SelectBoxGroup', () => { describe('Props and configuration', () => { it('supports different orientations', () => { render( - {}} + selectionMode="single" + onSelectionChange={() => {}} selectedKeys={new Set()} orientation="horizontal"> - + Option 1 @@ -449,25 +449,25 @@ describe('SelectBoxGroup', () => { it('auto-fits columns based on orientation (vertical)', () => { render(
- {}} + selectionMode="single" + onSelectionChange={() => {}} selectedKeys={new Set()} orientation="vertical"> - + Option 1 - + Option 2 - + Option 3
); - + const listbox = screen.getByRole('listbox'); expect(listbox).toBeInTheDocument(); }); @@ -475,39 +475,39 @@ describe('SelectBoxGroup', () => { it('auto-fits columns based on orientation (horizontal)', () => { render(
- {}} + selectionMode="single" + onSelectionChange={() => {}} selectedKeys={new Set()} orientation="horizontal"> - + Option 1 - + Option 2
); - + const listbox = screen.getByRole('listbox'); expect(listbox).toBeInTheDocument(); }); }); describe('Controlled behavior', () => { - it('handles initial value selection', () => { + it('handles initial id selection', () => { render( - {}} + selectionMode="single" + onSelectionChange={() => {}} selectedKeys={new Set(['option1'])}> - + Option 1 - + Option 2 @@ -515,25 +515,25 @@ describe('SelectBoxGroup', () => { const option1 = screen.getByRole('option', {name: 'Option 1'}); const option2 = screen.getByRole('option', {name: 'Option 2'}); - + expect(option1).toHaveAttribute('aria-selected', 'true'); expect(option2).toHaveAttribute('aria-selected', 'false'); }); - it('handles multiple selection with initial values', () => { + it('handles multiple selection with initial ids', () => { render( - {}} selectedKeys={new Set(['option1', 'option2'])}> - + Option 1 - + Option 2 - + Option 3 @@ -542,7 +542,7 @@ describe('SelectBoxGroup', () => { const option1 = screen.getByRole('option', {name: 'Option 1'}); const option2 = screen.getByRole('option', {name: 'Option 2'}); const option3 = screen.getByRole('option', {name: 'Option 3'}); - + expect(option1).toHaveAttribute('aria-selected', 'true'); expect(option2).toHaveAttribute('aria-selected', 'true'); expect(option3).toHaveAttribute('aria-selected', 'false'); @@ -550,25 +550,25 @@ describe('SelectBoxGroup', () => { it('handles uncontrolled selection with defaultSelectedKeys', async () => { render( - - + Option 1 - + Option 2 ); - + const option1 = screen.getByRole('option', {name: 'Option 1'}); const option2 = screen.getByRole('option', {name: 'Option 2'}); - + expect(option1).toHaveAttribute('aria-selected', 'true'); expect(option2).toHaveAttribute('aria-selected', 'false'); - + // click should update selection await user.click(option2); expect(option1).toHaveAttribute('aria-selected', 'false'); @@ -577,35 +577,35 @@ describe('SelectBoxGroup', () => { it('handles uncontrolled multiple selection with defaultSelectedKeys', async () => { render( - - + Option 1 - + Option 2 - + Option 3 ); - + const option1 = screen.getByRole('option', {name: 'Option 1'}); const option2 = screen.getByRole('option', {name: 'Option 2'}); const option3 = screen.getByRole('option', {name: 'Option 3'}); - + expect(option1).toHaveAttribute('aria-selected', 'true'); expect(option2).toHaveAttribute('aria-selected', 'true'); expect(option3).toHaveAttribute('aria-selected', 'false'); - + await user.click(option3); expect(option1).toHaveAttribute('aria-selected', 'true'); expect(option2).toHaveAttribute('aria-selected', 'true'); expect(option3).toHaveAttribute('aria-selected', 'true'); - + // click should remove from selection await user.click(option1); expect(option1).toHaveAttribute('aria-selected', 'false'); @@ -616,21 +616,21 @@ describe('SelectBoxGroup', () => { it('handles controlled component updates', async () => { function ControlledTest() { const [selectedKeys, setSelectedKeys] = React.useState(new Set()); - + return (
- - + Option 1 - + Option 2 @@ -639,7 +639,7 @@ describe('SelectBoxGroup', () => { } render(); - + const button = screen.getByRole('button', {name: 'Select Option 2'}); await user.click(button); @@ -649,23 +649,23 @@ describe('SelectBoxGroup', () => { it('handles "all" selection', () => { render( - {}} + selectionMode="multiple" + onSelectionChange={() => {}} selectedKeys="all"> - + Option 1 - + Option 2 ); - + const option1 = screen.getByRole('option', {name: 'Option 1'}); const option2 = screen.getByRole('option', {name: 'Option 2'}); - + expect(option1).toHaveAttribute('aria-selected', 'true'); expect(option2).toHaveAttribute('aria-selected', 'true'); }); @@ -679,16 +679,16 @@ describe('SelectBoxGroup', () => { selectionMode="single" onSelectionChange={() => {}} selectedKeys={new Set()}> - + Option 1 - + Option 2 - + Option 3 - + Option 4 @@ -696,7 +696,7 @@ describe('SelectBoxGroup', () => { const listbox = screen.getByRole('listbox'); const options = screen.getAllByRole('option'); - + expect(listbox).toBeInTheDocument(); expect(options).toHaveLength(4); }); @@ -705,12 +705,12 @@ describe('SelectBoxGroup', () => { render(); const listbox = screen.getByRole('listbox'); const option1 = screen.getByRole('option', {name: 'Option 1'}); - + await act(async () => { listbox.focus(); await user.keyboard(' '); }); - + expect(option1).toHaveAttribute('aria-selected', 'true'); }); @@ -721,10 +721,10 @@ describe('SelectBoxGroup', () => { selectionMode="single" onSelectionChange={() => {}} selectedKeys={new Set()}> - + Option 1 - + Option 2 @@ -734,9 +734,9 @@ describe('SelectBoxGroup', () => { await act(async () => { listbox.focus(); }); - + await user.keyboard('{ArrowDown}'); - + // check that navigation works by verifying an option has focus const option1 = screen.getByRole('option', {name: 'Option 1'}); expect(option1).toHaveFocus(); @@ -746,20 +746,20 @@ describe('SelectBoxGroup', () => { describe('Accessibility', () => { it('has proper listbox structure', () => { render( - {}} + selectionMode="single" + onSelectionChange={() => {}} selectedKeys={new Set()}> - + Option 1 - + Option 2 ); - + expect(screen.getByRole('listbox')).toBeInTheDocument(); expect(screen.getAllByRole('option')).toHaveLength(2); }); @@ -768,20 +768,20 @@ describe('SelectBoxGroup', () => { render(

My SelectBoxGroup

- {}} + selectionMode="single" + onSelectionChange={() => {}} selectedKeys={new Set()}> - + Option 1
); - + const listbox = screen.getByRole('listbox'); - // verify the listbox has an aria-labelledby attribute + // verify the listbox has an aria-labelledby attribute expect(listbox).toHaveAttribute('aria-labelledby'); expect(listbox.getAttribute('aria-labelledby')).toBeTruthy(); }); @@ -790,40 +790,40 @@ describe('SelectBoxGroup', () => { describe('Edge cases', () => { it('handles complex children with slots', () => { render( - {}} + selectionMode="single" + onSelectionChange={() => {}} selectedKeys={new Set()} orientation="horizontal"> - + Complex Option With description ); - + expect(screen.getByText('Complex Option')).toBeInTheDocument(); expect(screen.getByText('With description')).toBeInTheDocument(); }); - it('handles different value types', () => { + it('handles different id types', () => { render( - {}} + {}} selectedKeys={new Set()}> - + Option 1 - + Option 2 ); - + const option1 = screen.getByRole('option', {name: 'Option 1'}); const option2 = screen.getByRole('option', {name: 'Option 2'}); expect(option1).toBeInTheDocument(); @@ -832,20 +832,20 @@ describe('SelectBoxGroup', () => { it('handles empty children gracefully', () => { render( - {}} + selectionMode="single" + onSelectionChange={() => {}} selectedKeys={new Set()}> {null} {undefined} - + Valid Option {false} ); - + expect(screen.getByRole('listbox')).toBeInTheDocument(); expect(screen.getAllByRole('option')).toHaveLength(1); expect(screen.getByText('Valid Option')).toBeInTheDocument(); @@ -858,10 +858,10 @@ describe('SelectBoxGroup', () => { selectionMode="single" onSelectionChange={() => {}} selectedKeys={new Set()}> - + Option 1 - + Option 2 @@ -869,7 +869,7 @@ describe('SelectBoxGroup', () => { const rows = screen.getAllByRole('option'); expect(rows.length).toBe(2); - + const option1 = screen.getByRole('option', {name: 'Option 1'}); expect(option1).toHaveAttribute('aria-disabled', 'true'); }); @@ -882,7 +882,7 @@ describe('SelectBoxGroup', () => { selectionMode="single" onSelectionChange={onSelectionChange} selectedKeys={new Set()}> - + Option 1 @@ -890,7 +890,7 @@ describe('SelectBoxGroup', () => { const option1 = screen.getByRole('option', {name: 'Option 1'}); await user.click(option1); - + expect(onSelectionChange).not.toHaveBeenCalled(); }); @@ -899,10 +899,10 @@ describe('SelectBoxGroup', () => { - + Option 1 - + Option 2 @@ -910,11 +910,11 @@ describe('SelectBoxGroup', () => { const option1 = screen.getByRole('option', {name: 'Option 1'}); const option2 = screen.getByRole('option', {name: 'Option 2'}); - + await user.click(option1); expect(option1).toHaveAttribute('aria-disabled', 'true'); expect(option1).toHaveAttribute('aria-selected', 'false'); - + // clicking enabled item should still work await user.click(option2); expect(option2).toHaveAttribute('aria-selected', 'true');