diff --git a/src/stories/Autocomplete.stories.tsx b/src/Autocomplete/Autocomplete.features.stories.tsx similarity index 91% rename from src/stories/Autocomplete.stories.tsx rename to src/Autocomplete/Autocomplete.features.stories.tsx index 1e1a8faa66e..5aaae0ca32a 100644 --- a/src/stories/Autocomplete.stories.tsx +++ b/src/Autocomplete/Autocomplete.features.stories.tsx @@ -3,7 +3,7 @@ import {Meta} from '@storybook/react' import {BaseStyles, Box, Dialog, ThemeProvider, registerPortalRoot} from '..' import TextInputTokens from '../TextInputWithTokens' -import Autocomplete from '../Autocomplete/Autocomplete' +import Autocomplete from './Autocomplete' import {AnchoredOverlay} from '../AnchoredOverlay' import FormControl from '../FormControl' import {Button} from '../Button' @@ -16,8 +16,6 @@ import { getTextInputArgTypes, textInputWithTokensArgTypes, } from '../utils/story-helpers' -import {within, userEvent} from '@storybook/testing-library' -import {expect} from '@storybook/jest' type AutocompleteOverlayArgs = ComponentProps type AutocompleteMenuArgs = ComponentProps @@ -105,7 +103,7 @@ const items: Datum[] = [ const mockTokens: Datum[] = [...items].slice(0, 3) const autocompleteStoryMeta: Meta = { - title: 'Components/Forms/Autocomplete', + title: 'Components/Forms/Autocomplete/Features', decorators: [ Story => { const [lastKey, setLastKey] = useState('none') @@ -219,54 +217,6 @@ const autocompleteStoryMeta: Meta = { }, } as Meta -export const Default = (args: FormControlArgs) => { - const {parentArgs, labelArgs, captionArgs, validationArgs} = getFormControlArgsByChildComponent(args) - const {menuArgs, overlayArgs, textInputArgs} = getArgsByChildComponent(args) - const isMultiselect = menuArgs.selectionVariant === 'multiple' - const [selectedItemIds, setSelectedItemIds] = useState>([]) - const onSelectedChange = (newlySelectedItems: Datum | Datum[]) => { - if (!Array.isArray(newlySelectedItems)) { - return - } - - setSelectedItemIds(newlySelectedItems.map(item => item.id)) - } - - return ( - - - - - - - - - - {captionArgs.children && } - {validationArgs.children && validationArgs.variant && ( - - )} - - - ) -} - -Default.play = async ({canvasElement}: {canvasElement: HTMLElement}) => { - const canvas = within(canvasElement) - const inputBox = await canvas.getByTestId('autocompleteInput') - await userEvent.click(inputBox) - const firstAutoCompleteOption = canvas.getByText('css') - await expect(firstAutoCompleteOption).toBeInTheDocument() - await userEvent.type(firstAutoCompleteOption, '{enter}') - await expect(inputBox).toHaveValue('css') -} - export const WithTokenInput = (args: FormControlArgs) => { const {parentArgs, labelArgs, captionArgs, validationArgs} = getFormControlArgsByChildComponent(args) const {menuArgs, overlayArgs, textInputWithTokensArgs} = getArgsByChildComponent(args) diff --git a/src/Autocomplete/Autocomplete.stories.tsx b/src/Autocomplete/Autocomplete.stories.tsx new file mode 100644 index 00000000000..6076bd1d590 --- /dev/null +++ b/src/Autocomplete/Autocomplete.stories.tsx @@ -0,0 +1,266 @@ +import React, {useCallback, useState} from 'react' +import {Meta} from '@storybook/react' + +import {BaseStyles, Box, ThemeProvider} from '..' + +import Autocomplete from './Autocomplete' +import FormControl from '../FormControl' + +import {ComponentProps} from '../utils/types' +import { + FormControlArgs, + formControlArgs, + formControlArgTypes, + getFormControlArgsByChildComponent, + getTextInputArgTypes, +} from '../utils/story-helpers' +import {within, userEvent} from '@storybook/testing-library' +import {expect} from '@storybook/jest' + +type AutocompleteOverlayArgs = ComponentProps +type AutocompleteMenuArgs = ComponentProps +type AutocompleteArgs = AutocompleteOverlayArgs & AutocompleteMenuArgs + +const excludedControlKeys = ['id', 'sx'] + +const getArgsByChildComponent = ({ + // Autocomplete.Menu + emptyStateText, + menuLoading, + selectionVariant, + + // Autocomplete.Overlay + anchorSide, + height, + overlayMaxHeight, + width, + + // TextInput + block, + contrast, + disabled, + inputSize, + loading, + loaderPosition, + placeholder, + validationStatus, + + // TextInputWithTokens + hideTokenRemoveButtons, + maxHeight: textInputWithTokensMaxHeight, + preventTokenWrapping, + size: tokenSize, + visibleTokenCount, +}: // eslint-disable-next-line @typescript-eslint/no-explicit-any +any) => { + const textInputArgs = { + block, + contrast, + disabled, + inputSize, + loading, + loaderPosition, + placeholder, + validationStatus, + } + return { + menuArgs: {emptyStateText, loading: menuLoading, selectionVariant}, + overlayArgs: {anchorSide, height, maxHeight: overlayMaxHeight, width}, + textInputArgs, + textInputWithTokensArgs: { + hideTokenRemoveButtons, + maxHeight: textInputWithTokensMaxHeight, + preventTokenWrapping, + size: tokenSize, + visibleTokenCount, + ...textInputArgs, + // ...formControlArgTypes + }, + } +} + +type ItemMetadata = { + fillColor: React.CSSProperties['backgroundColor'] +} + +type Datum = { + id: string | number + text: string + selected?: boolean + metadata?: ItemMetadata +} + +const items: Datum[] = [ + {text: 'css', id: 0}, + {text: 'css-in-js', id: 1}, + {text: 'styled-system', id: 2}, + {text: 'javascript', id: 3}, + {text: 'typescript', id: 4}, + {text: 'react', id: 5}, + {text: 'design-systems', id: 6}, +] + +const autocompleteStoryMeta: Meta = { + title: 'Components/Forms/Autocomplete', + decorators: [ + Story => { + const [lastKey, setLastKey] = useState('none') + const reportKey = useCallback((event: React.KeyboardEvent) => { + setLastKey(event.key) + }, []) + + return ( + + + + + Last key pressed: {lastKey} + + + + + + + + ) + }, + ], + parameters: {controls: {exclude: excludedControlKeys}}, + args: { + ...formControlArgs, + emptyStateText: 'No selectable options', + menuLoading: false, + selectionVariant: 'single', + anchorSide: undefined, + height: 'auto', + overlayMaxHeight: undefined, + width: 'auto', + }, + argTypes: { + // Autocomplete.Menu + emptyStateText: { + control: {type: 'text'}, + table: { + category: 'Autocomplete.Menu', + }, + }, + menuLoading: { + name: 'loading', + control: {type: 'boolean'}, + table: { + category: 'Autocomplete.Menu', + }, + }, + selectionVariant: { + control: { + type: 'radio', + }, + options: ['single', 'multiple'], + table: { + category: 'Autocomplete.Menu', + }, + }, + + // Autocomplete.Overlay + anchorSide: { + control: { + type: 'select', + }, + options: [ + 'inside-top', + 'inside-bottom', + 'inside-left', + 'inside-right', + 'inside-center', + 'outside-top', + 'outside-bottom', + 'outside-left', + 'outside-right', + ], + table: { + category: 'Autocomplete.Overlay', + }, + }, + height: { + control: { + type: 'select', + }, + options: ['auto', 'initial', 'small', 'medium', 'large', 'xlarge', 'xsmall'], + table: { + category: 'Autocomplete.Overlay', + }, + }, + // needs a key other than 'maxHeight' because TextInputWithTokens also has a maxHeight prop + overlayMaxHeight: { + name: 'maxHeight', + control: { + type: 'select', + }, + options: ['small', 'medium', 'large', 'xlarge', 'xsmall', undefined], + table: { + category: 'Autocomplete.Overlay', + }, + }, + width: { + control: { + type: 'select', + }, + options: ['auto', 'small', 'medium', 'large', 'xlarge', 'xxlarge'], + table: { + category: 'Autocomplete.Overlay', + }, + }, + ...getTextInputArgTypes('TextInput props'), + ...formControlArgTypes, + }, +} as Meta + +export const Default = (args: FormControlArgs) => { + const {parentArgs, labelArgs, captionArgs, validationArgs} = getFormControlArgsByChildComponent(args) + const {menuArgs, overlayArgs, textInputArgs} = getArgsByChildComponent(args) + const isMultiselect = menuArgs.selectionVariant === 'multiple' + const [selectedItemIds, setSelectedItemIds] = useState>([]) + const onSelectedChange = (newlySelectedItems: Datum | Datum[]) => { + if (!Array.isArray(newlySelectedItems)) { + return + } + + setSelectedItemIds(newlySelectedItems.map(item => item.id)) + } + + return ( + + + + + + + + + + {captionArgs.children && } + {validationArgs.children && validationArgs.variant && ( + + )} + + + ) +} + +Default.play = async ({canvasElement}: {canvasElement: HTMLElement}) => { + const canvas = within(canvasElement) + const inputBox = await canvas.getByTestId('autocompleteInput') + await userEvent.click(inputBox) + const firstAutoCompleteOption = canvas.getByText('css') + await expect(firstAutoCompleteOption).toBeInTheDocument() + await userEvent.type(firstAutoCompleteOption, '{enter}') + await expect(inputBox).toHaveValue('css') +} + +export default autocompleteStoryMeta diff --git a/src/__tests__/storybook.test.tsx b/src/__tests__/storybook.test.tsx index eaf9ee5f257..ae297acf5e3 100644 --- a/src/__tests__/storybook.test.tsx +++ b/src/__tests__/storybook.test.tsx @@ -10,6 +10,7 @@ const allowlist = [ 'ActionList', 'ActionMenu', 'AnchoredOverlay', + 'Autocomplete', 'Avatar', 'AvatarStack', 'AvatarPair',