Skip to content

Commit

Permalink
docs: added/refactored Autocomplete
Browse files Browse the repository at this point in the history
  • Loading branch information
green6erry committed Oct 10, 2023
1 parent 953b609 commit 9329775
Show file tree
Hide file tree
Showing 3 changed files with 269 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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<typeof Autocomplete.Overlay>
type AutocompleteMenuArgs = ComponentProps<typeof Autocomplete.Menu>
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -219,54 +217,6 @@ const autocompleteStoryMeta: Meta = {
},
} as Meta

export const Default = (args: FormControlArgs<AutocompleteArgs>) => {
const {parentArgs, labelArgs, captionArgs, validationArgs} = getFormControlArgsByChildComponent(args)
const {menuArgs, overlayArgs, textInputArgs} = getArgsByChildComponent(args)
const isMultiselect = menuArgs.selectionVariant === 'multiple'
const [selectedItemIds, setSelectedItemIds] = useState<Array<string | number>>([])
const onSelectedChange = (newlySelectedItems: Datum | Datum[]) => {
if (!Array.isArray(newlySelectedItems)) {
return
}

setSelectedItemIds(newlySelectedItems.map(item => item.id))
}

return (
<Box as="form" sx={{p: 3}}>
<FormControl {...parentArgs}>
<FormControl.Label id="autocompleteLabel" {...labelArgs} />
<Autocomplete>
<Autocomplete.Input {...textInputArgs} size={textInputArgs.inputSize} data-testid="autocompleteInput" />
<Autocomplete.Overlay {...overlayArgs}>
<Autocomplete.Menu
items={items}
selectedItemIds={isMultiselect ? selectedItemIds : []}
onSelectedChange={isMultiselect ? onSelectedChange : undefined}
aria-labelledby="autocompleteLabel"
{...menuArgs}
/>
</Autocomplete.Overlay>
</Autocomplete>
{captionArgs.children && <FormControl.Caption {...captionArgs} />}
{validationArgs.children && validationArgs.variant && (
<FormControl.Validation {...validationArgs} variant={validationArgs.variant} />
)}
</FormControl>
</Box>
)
}

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<AutocompleteArgs>) => {
const {parentArgs, labelArgs, captionArgs, validationArgs} = getFormControlArgsByChildComponent(args)
const {menuArgs, overlayArgs, textInputWithTokensArgs} = getArgsByChildComponent(args)
Expand Down
266 changes: 266 additions & 0 deletions src/Autocomplete/Autocomplete.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof Autocomplete.Overlay>
type AutocompleteMenuArgs = ComponentProps<typeof Autocomplete.Menu>
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<HTMLDivElement>) => {
setLastKey(event.key)
}, [])

return (
<ThemeProvider>
<BaseStyles>
<Box onKeyDownCapture={reportKey}>
<Box position="absolute" right={5} top={2}>
Last key pressed: {lastKey}
</Box>
<Box paddingTop={5}>
<Story />
</Box>
</Box>
</BaseStyles>
</ThemeProvider>
)
},
],
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<AutocompleteArgs>) => {
const {parentArgs, labelArgs, captionArgs, validationArgs} = getFormControlArgsByChildComponent(args)
const {menuArgs, overlayArgs, textInputArgs} = getArgsByChildComponent(args)
const isMultiselect = menuArgs.selectionVariant === 'multiple'
const [selectedItemIds, setSelectedItemIds] = useState<Array<string | number>>([])
const onSelectedChange = (newlySelectedItems: Datum | Datum[]) => {
if (!Array.isArray(newlySelectedItems)) {
return
}

setSelectedItemIds(newlySelectedItems.map(item => item.id))
}

return (
<Box as="form" sx={{p: 3}}>
<FormControl {...parentArgs}>
<FormControl.Label id="autocompleteLabel" {...labelArgs} />
<Autocomplete>
<Autocomplete.Input {...textInputArgs} size={textInputArgs.inputSize} data-testid="autocompleteInput" />
<Autocomplete.Overlay {...overlayArgs}>
<Autocomplete.Menu
items={items}
selectedItemIds={isMultiselect ? selectedItemIds : []}
onSelectedChange={isMultiselect ? onSelectedChange : undefined}
aria-labelledby="autocompleteLabel"
{...menuArgs}
/>
</Autocomplete.Overlay>
</Autocomplete>
{captionArgs.children && <FormControl.Caption {...captionArgs} />}
{validationArgs.children && validationArgs.variant && (
<FormControl.Validation {...validationArgs} variant={validationArgs.variant} />
)}
</FormControl>
</Box>
)
}

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
1 change: 1 addition & 0 deletions src/__tests__/storybook.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const allowlist = [
'ActionList',
'ActionMenu',
'AnchoredOverlay',
'Autocomplete',
'Avatar',
'AvatarStack',
'AvatarPair',
Expand Down

0 comments on commit 9329775

Please sign in to comment.