Skip to content

Commit

Permalink
docs: MultiComboBox の Story を見直し (#5180)
Browse files Browse the repository at this point in the history
  • Loading branch information
misako0927 authored Jan 8, 2025
1 parent 5b5df41 commit 631ad61
Show file tree
Hide file tree
Showing 11 changed files with 288 additions and 375 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import { userEvent } from '@storybook/test'
import { render, screen } from '@testing-library/react'
import React, { ComponentProps, act } from 'react'

import { FormControl } from '../FormControl'
import { FormControl } from '../../FormControl'

import { MultiComboBox } from './MultiComboBox'
import { SingleComboBox } from './SingleComboBox'

describe('SingleComboBox', () => {
beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@ import { useId } from 'react'
import innerText from 'react-innertext'
import { tv } from 'tailwind-variants'

import { useOuterClick } from '../../hooks/useOuterClick'
import { genericsForwardRef } from '../../libs/util'
import { textColor } from '../../themes'
import { FaCaretDownIcon } from '../Icon'
import { useOuterClick } from '../../../hooks/useOuterClick'
import { genericsForwardRef } from '../../../libs/util'
import { textColor } from '../../../themes'
import { FaCaretDownIcon } from '../../Icon'
import { useFocusControl } from '../useFocusControl'
import { useListBox } from '../useListBox'
import { useOptions } from '../useOptions'

import { MultiSelectedItem } from './MultiSelectedItem'
import { hasParentElementByClassName } from './multiComboBoxHelper'
import { useFocusControl } from './useFocusControl'
import { useListBox } from './useListBox'
import { useOptions } from './useOptions'

import type { BaseProps, ComboBoxItem } from './types'
import type { DecoratorsType } from '../../types'
import type { DecoratorsType } from '../../../types'
import type { BaseProps, ComboBoxItem } from '../types'

type Props<T> = BaseProps<T> & {
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { tv } from 'tailwind-variants'

import { UnstyledButton } from '../Button'
import { Chip } from '../Chip'
import { FaTimesCircleIcon } from '../Icon'
import { UnstyledButton } from '../../Button'
import { Chip } from '../../Chip'
import { FaTimesCircleIcon } from '../../Icon'
import { ComboBoxItem } from '../types'

import { MultiSelectedItemTooltip } from './MultiSelectedItemTooltip'
import { ComboBoxItem } from './types'

export type Props<T> = {
item: ComboBoxItem<T> & { deletable?: boolean }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { FC, PropsWithChildren, ReactNode } from 'react'

import { Tooltip } from '../Tooltip'
import { Tooltip } from '../../Tooltip'

type Props = PropsWithChildren<{
needsTooltip: boolean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { MultiComboBox } from './MultiComboBox'
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/* eslint-disable smarthr/a11y-input-in-form-control */
import { useArgs } from '@storybook/preview-api'
import { Meta, StoryObj } from '@storybook/react'
import React from 'react'

import { Stack } from '../../../Layout'
import { Text } from '../../../Text'
import { MultiComboBox } from '../MultiComboBox'

// eslint-disable-next-line storybook/prefer-pascal-case
export const defaultItems = {
'option 1': {
label: 'option 1',
value: 'value-1',
data: {
name: 'test',
age: 23,
},
},
'option 2': {
label: 'option 2',
value: 'value-2',
data: {
name: 'test 2',
age: 34,
},
},
'option 3': {
label: 'option 3',
value: 'value-3',
disabled: true,
},
'option 4': {
label: 'option 4',
value: 'value-4',
},
'option 5': {
label: 'option 5',
value: 'value-5',
},
'アイテムのラベルが長い場合(ダミーテキストダミーテキストダミーテキストダミーテキスト)': {
label: 'アイテムのラベルが長い場合(ダミーテキストダミーテキストダミーテキストダミーテキスト)',
value: 'value-6',
},
アイテムのラベルがReactNodeの場合: {
label: (
<Stack as="span" gap={0.25}>
<span>アイテムのラベルがReactNodeの場合</span>
<span>(ダミーテキストダミーテキストダミーテキストダミーテキスト)</span>
</Stack>
),
value: 'value-7',
},
}

export default {
title: 'Forms(フォーム)/MultiComboBox',
component: MultiComboBox,
render: (args) => {
const [_, setArgs] = useArgs()
return (
<MultiComboBox
{...args}
onDelete={(item) =>
setArgs({
selectedItems: args.selectedItems.filter(
(selectedItem) => selectedItem.value !== item.value,
),
})
}
onSelect={(item) =>
setArgs({
selectedItems: [...args.selectedItems, item],
})
}
/>
)
},
args: {
items: Object.values(defaultItems),
selectedItems: [],
},
argTypes: {
items: { control: 'object' },
selectedItems: { control: 'object' },
dropdownHelpMessage: {
control: { type: 'select' },
options: ['文字列', 'ReactNode'],
mapping: {
文字列: 'ヘルプメッセージ',
ReactNode: <Text className="shr-text-danger">React Nodeを渡したメッセージ</Text>,
},
},
},
parameters: {
chromatic: { disableSnapshot: true },
},
excludeStories: ['defaultItems'],
} as Meta<typeof MultiComboBox<{ option: string }>>

export const Playground: StoryObj<typeof MultiComboBox> = {}

export const SelectedItems: StoryObj<typeof MultiComboBox> = {
name: 'selectedItems',
args: {
selectedItems: [defaultItems['option 1'], defaultItems['option 4']],
},
}

export const Disabled: StoryObj<typeof MultiComboBox> = {
name: 'disabled',
args: {
disabled: true,
},
}

export const Error: StoryObj<typeof MultiComboBox> = {
name: 'error',
args: {
error: true,
},
}

export const Creatable: StoryObj<typeof MultiComboBox> = {
name: 'creatable',
args: {
creatable: true,
dropdownHelpMessage: '新しいアイテムを追加できます。',
},
}

export const IsLoading: StoryObj<typeof MultiComboBox> = {
name: 'isLoading',
args: {
isLoading: true,
},
}

export const Width: StoryObj<typeof MultiComboBox> = {
name: 'width',
args: {
width: '20rem',
},
}

export const DropdownHelpMessage: StoryObj<typeof MultiComboBox> = {
name: 'dropdownHelpMessage',
args: {
dropdownHelpMessage: 'ヘルプメッセージ',
},
}

export const DropdownWidth: StoryObj<typeof MultiComboBox> = {
name: 'dropdownWidth',
args: {
dropdownWidth: '30rem',
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/* eslint-disable smarthr/a11y-input-in-form-control */
import { Meta, StoryObj } from '@storybook/react'
import { userEvent, within } from '@storybook/test'
import React, { useState } from 'react'

import { Stack } from '../../../Layout'
import { ComboBoxItem } from '../../types'
import { MultiComboBox } from '../MultiComboBox'

import { defaultItems } from './MultiComboBox.stories'

/*
* pict multiComboBox.pict
* disabled error width selectedItems
* false true なし なし
* false false あり 複数
* true true あり 一つ
* false false なし 一つ
* true true なし 複数
* true false あり なし
*/

const _cases: Array<Omit<Parameters<typeof MultiComboBox>[0], 'items'>> = [
{ disabled: false, error: true, width: undefined, selectedItems: [] },
{
disabled: false,
error: false,
width: '15em',
selectedItems: [defaultItems['option 1'], defaultItems['option 4']],
},
{ disabled: true, error: true, width: '15em', selectedItems: [defaultItems['option 3']] },
{
disabled: false,
error: false,
width: undefined,
selectedItems: [defaultItems['アイテムのラベルがReactNodeの場合']],
},
{
disabled: true,
error: true,
width: undefined,
selectedItems: [
defaultItems['option 2'],
defaultItems[
'アイテムのラベルが長い場合(ダミーテキストダミーテキストダミーテキストダミーテキスト)'
],
],
},
{ disabled: true, error: false, width: '15em', selectedItems: [] },
]

const waitForRAF = () =>
new Promise<void>((resolve) => {
requestAnimationFrame(() => {
resolve()
})
})
const playMulti = async ({ canvasElement }: { canvasElement: HTMLElement }) => {
const canvas = within(canvasElement)
const comboboxes = await canvas.findAllByRole('combobox')
comboboxes[comboboxes.length - 1].focus()
const body = canvasElement.ownerDocument.body
const option1 = await within(body).findByRole('option', { name: 'option 1' })
await userEvent.click(option1)
await waitForRAF()
const option2 = await within(body).findByRole('option', { name: 'option 2' })
await userEvent.click(option2)
await waitForRAF()
const helpMessage = await within(body).findAllByText('入力でフィルタリングできます。')
await userEvent.click(helpMessage[0]) // カーソルの点滅によるVRTのフレーキーを避けるためにフォーカスを移動する
}

export default {
title: 'Forms(フォーム)/MultiComboBox/VRT',
component: MultiComboBox,
render: (args) => {
const items = Object.values(defaultItems)
const [selectedItems, setSelectedItems] = useState<Array<ComboBoxItem<unknown>>>([])
return (
<Stack align="flex-start" gap={2} className="shr-h-screen">
{_cases.map((props, i) => (
<MultiComboBox {...args} {...props} items={items} key={i} />
))}
<MultiComboBox
{...args}
name="default"
items={items}
dropdownHelpMessage="入力でフィルタリングできます。"
selectedItems={selectedItems}
onChangeSelected={(its) => setSelectedItems(its)}
/>
</Stack>
)
},
play: playMulti,
parameters: {
withTheming: true,
chromatic: { disableSnapshot: false },
},
tags: ['!autodocs', 'skip-test-runner'],
} as Meta<typeof MultiComboBox>

export const VRT: StoryObj<typeof MultiComboBox> = {}

export const VRTForcedColors: StoryObj<typeof MultiComboBox> = {
...VRT,
parameters: {
chromatic: { forcedColors: 'active' },
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
disabled: true, false
error: true, false
width: あり, なし
selectedItems: 一つ, 複数, なし
Loading

0 comments on commit 631ad61

Please sign in to comment.