import React from 'react' import { type Meta, type StoryObj } from '@storybook/react' import { expect, userEvent, waitFor, within } from '@storybook/test' import { Button } from '~components/Button' import { FieldMessage } from '~components/FieldMessage' import { ContextModal } from '~components/Modal' import { RadioField, RadioGroup } from '~components/Radio' import { SingleSelect } from '../SingleSelect' import { SelectToggle } from '../subcomponents' import { type SingleSelectOption } from '../types' import { groupedMockItems, mixedMockItemsDisabled, singleMockItems } from './mockData' const meta = { title: 'Components/SingleSelect', component: SingleSelect, argTypes: { items: { options: ['Single', 'Grouped'], control: { type: 'radio' }, mapping: { Single: singleMockItems, Grouped: groupedMockItems, }, }, description: { type: 'string' }, validationMessage: { type: 'string' }, }, args: { label: 'Label', items: singleMockItems, onFocus: undefined, onFocusChange: undefined, onOpenChange: undefined, onSelectionChange: undefined, }, parameters: { actions: { argTypesRegex: '^on.*', }, }, } satisfies Meta export default meta type Story = StoryObj export const Playground: Story = { parameters: { docs: { canvas: { sourceState: 'shown', }, }, }, } export const SingleItems: Story = { args: { items: singleMockItems, }, } export const GroupedItems: Story = { args: { items: groupedMockItems, }, } export const DisabledItems: Story = { args: { items: mixedMockItemsDisabled, }, } export const SectionDivider: Story = { args: { items: [{ label: 'Customise...', value: 'custom' }, ...singleMockItems], children: ({ items }): JSX.Element[] => items.map((item) => { if (item.type === 'item' && item.key === 'custom') { return ( ) } return }), }, parameters: { docs: { source: { type: 'code' } } }, } export const AdditionalProperties: Story = { render: (args) => ( {...args} label="Custom" items={[ { label: 'Bubblegum', value: 'bubblegum', isFruit: false }, { label: 'Strawberry', value: 'strawberry', isFruit: true }, { label: 'Chocolate', value: 'chocolate', isFruit: false }, { label: 'Apple', value: 'apple', isFruit: true }, { label: 'Lemon', value: 'lemon', isFruit: true }, ]} > {({ items }): JSX.Element[] => items.map((item) => item.type === 'item' ? ( ) : ( ), ) } ), parameters: { docs: { source: { type: 'code' } } }, } const sourceCodeCustomiseTrigger = ` import { SelectToggle } from '@kaizen/components/subcomponents' } /> ` export const CustomiseTrigger: Story = { args: { trigger: (props) => , }, parameters: { docs: { source: { code: sourceCodeCustomiseTrigger, }, }, }, } export const Validation: Story = { render: (args) => (
), } export const FullWidth: Story = { args: { isFullWidth: true }, } export const PortalContainer: Story = { render: (args) => { const portalContainerId = 'id--portal-container' const [isOpen, setIsOpen] = React.useState(false) const handleOpen = (): void => setIsOpen(true) const handleClose = (): void => setIsOpen(false) return ( <>
Page content
) }, parameters: { docs: { source: { type: 'code' } } }, } export const TouchDeviceTest: Story = { name: 'Touch Device Pointer Event (Manual Test)', render: (args) => { const [selected, setSelected] = React.useState('radio-1') return (

On touch devices, the radios below were changing when selecting an option sitting above it.
At this time, we could not automate this test, so this story exists for manual testing.

setSelected('radio-1')} selectedStatus={selected === 'radio-1'} /> setSelected('radio-2')} selectedStatus={selected === 'radio-2'} /> setSelected('radio-3')} selectedStatus={selected === 'radio-3'} />
) }, } export const RequiredSelect: Story = { args: { label: 'Required Select', isRequired: true, validationBehavior: 'native', }, } export const ReversedSelect: Story = { args: { label: 'Reversed Select', isReversed: true, }, decorators: [ (Story) => (
), ], parameters: { backgrounds: { default: 'Purple 700' } }, } export const SelectNativeValidationBehavior: Story = { parameters: { name: 'Required Select with native form validation', }, args: { label: 'Required Select', isRequired: true, validationBehavior: 'native', }, render: (args) => { const [hasSubmitted, setHasSubmitted] = React.useState(false) return (
{ e.preventDefault() setHasSubmitted(true) }} >
{hasSubmitted && ( )}
) }, } export const NativeFormValidationWithoutSelectedVal: Story = { ...SelectNativeValidationBehavior, play: async ({ canvasElement, step }) => { const canvas = within(canvasElement.parentElement!) const submitButton = canvas.getByRole('button', { name: 'Submit' }) const requiredSelect = canvas.getByRole('combobox', { name: 'Required Select' }) const form = await canvas.findByRole('form') await step('Select has aria-required attribute', async () => { expect(requiredSelect).toHaveAttribute('aria-required', 'true') }) await step('Submit will not call onSubmit without a selected value', async () => { await userEvent.click(submitButton) await waitFor(() => { expect(form).toHaveAccessibleDescription('') }) }) }, } export const NativeFormValidationWithSelectedVal: Story = { ...SelectNativeValidationBehavior, args: { selectedKey: 'short-black', }, play: async ({ canvasElement, step }) => { const canvas = within(canvasElement.parentElement!) const submitButton = canvas.getByRole('button', { name: 'Submit' }) const form = await canvas.findByRole('form') await step('Submit will call onSubmit with a selected value', async () => { await userEvent.click(submitButton) await waitFor(() => { expect(form).toHaveAccessibleDescription('Form submitted!') }) }) }, }