Skip to content

Commit 631ad61

Browse files
authored
docs: MultiComboBox の Story を見直し (#5180)
1 parent 5b5df41 commit 631ad61

11 files changed

+288
-375
lines changed

packages/smarthr-ui/src/components/ComboBox/MultiComboBox.test.tsx packages/smarthr-ui/src/components/ComboBox/MultiComboBox/MultiComboBox.test.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ import { userEvent } from '@storybook/test'
22
import { render, screen } from '@testing-library/react'
33
import React, { ComponentProps, act } from 'react'
44

5-
import { FormControl } from '../FormControl'
5+
import { FormControl } from '../../FormControl'
66

77
import { MultiComboBox } from './MultiComboBox'
8-
import { SingleComboBox } from './SingleComboBox'
98

109
describe('SingleComboBox', () => {
1110
beforeEach(() => {

packages/smarthr-ui/src/components/ComboBox/MultiComboBox.tsx packages/smarthr-ui/src/components/ComboBox/MultiComboBox/MultiComboBox.tsx

+9-9
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,19 @@ import { useId } from 'react'
1515
import innerText from 'react-innertext'
1616
import { tv } from 'tailwind-variants'
1717

18-
import { useOuterClick } from '../../hooks/useOuterClick'
19-
import { genericsForwardRef } from '../../libs/util'
20-
import { textColor } from '../../themes'
21-
import { FaCaretDownIcon } from '../Icon'
18+
import { useOuterClick } from '../../../hooks/useOuterClick'
19+
import { genericsForwardRef } from '../../../libs/util'
20+
import { textColor } from '../../../themes'
21+
import { FaCaretDownIcon } from '../../Icon'
22+
import { useFocusControl } from '../useFocusControl'
23+
import { useListBox } from '../useListBox'
24+
import { useOptions } from '../useOptions'
2225

2326
import { MultiSelectedItem } from './MultiSelectedItem'
2427
import { hasParentElementByClassName } from './multiComboBoxHelper'
25-
import { useFocusControl } from './useFocusControl'
26-
import { useListBox } from './useListBox'
27-
import { useOptions } from './useOptions'
2828

29-
import type { BaseProps, ComboBoxItem } from './types'
30-
import type { DecoratorsType } from '../../types'
29+
import type { DecoratorsType } from '../../../types'
30+
import type { BaseProps, ComboBoxItem } from '../types'
3131

3232
type Props<T> = BaseProps<T> & {
3333
/**

packages/smarthr-ui/src/components/ComboBox/MultiSelectedItem.tsx packages/smarthr-ui/src/components/ComboBox/MultiComboBox/MultiSelectedItem.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import React, { RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
22
import { tv } from 'tailwind-variants'
33

4-
import { UnstyledButton } from '../Button'
5-
import { Chip } from '../Chip'
6-
import { FaTimesCircleIcon } from '../Icon'
4+
import { UnstyledButton } from '../../Button'
5+
import { Chip } from '../../Chip'
6+
import { FaTimesCircleIcon } from '../../Icon'
7+
import { ComboBoxItem } from '../types'
78

89
import { MultiSelectedItemTooltip } from './MultiSelectedItemTooltip'
9-
import { ComboBoxItem } from './types'
1010

1111
export type Props<T> = {
1212
item: ComboBoxItem<T> & { deletable?: boolean }

packages/smarthr-ui/src/components/ComboBox/MultiSelectedItemTooltip.tsx packages/smarthr-ui/src/components/ComboBox/MultiComboBox/MultiSelectedItemTooltip.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { FC, PropsWithChildren, ReactNode } from 'react'
22

3-
import { Tooltip } from '../Tooltip'
3+
import { Tooltip } from '../../Tooltip'
44

55
type Props = PropsWithChildren<{
66
needsTooltip: boolean
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { MultiComboBox } from './MultiComboBox'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/* eslint-disable smarthr/a11y-input-in-form-control */
2+
import { useArgs } from '@storybook/preview-api'
3+
import { Meta, StoryObj } from '@storybook/react'
4+
import React from 'react'
5+
6+
import { Stack } from '../../../Layout'
7+
import { Text } from '../../../Text'
8+
import { MultiComboBox } from '../MultiComboBox'
9+
10+
// eslint-disable-next-line storybook/prefer-pascal-case
11+
export const defaultItems = {
12+
'option 1': {
13+
label: 'option 1',
14+
value: 'value-1',
15+
data: {
16+
name: 'test',
17+
age: 23,
18+
},
19+
},
20+
'option 2': {
21+
label: 'option 2',
22+
value: 'value-2',
23+
data: {
24+
name: 'test 2',
25+
age: 34,
26+
},
27+
},
28+
'option 3': {
29+
label: 'option 3',
30+
value: 'value-3',
31+
disabled: true,
32+
},
33+
'option 4': {
34+
label: 'option 4',
35+
value: 'value-4',
36+
},
37+
'option 5': {
38+
label: 'option 5',
39+
value: 'value-5',
40+
},
41+
'アイテムのラベルが長い場合(ダミーテキストダミーテキストダミーテキストダミーテキスト)': {
42+
label: 'アイテムのラベルが長い場合(ダミーテキストダミーテキストダミーテキストダミーテキスト)',
43+
value: 'value-6',
44+
},
45+
アイテムのラベルがReactNodeの場合: {
46+
label: (
47+
<Stack as="span" gap={0.25}>
48+
<span>アイテムのラベルがReactNodeの場合</span>
49+
<span>(ダミーテキストダミーテキストダミーテキストダミーテキスト)</span>
50+
</Stack>
51+
),
52+
value: 'value-7',
53+
},
54+
}
55+
56+
export default {
57+
title: 'Forms(フォーム)/MultiComboBox',
58+
component: MultiComboBox,
59+
render: (args) => {
60+
const [_, setArgs] = useArgs()
61+
return (
62+
<MultiComboBox
63+
{...args}
64+
onDelete={(item) =>
65+
setArgs({
66+
selectedItems: args.selectedItems.filter(
67+
(selectedItem) => selectedItem.value !== item.value,
68+
),
69+
})
70+
}
71+
onSelect={(item) =>
72+
setArgs({
73+
selectedItems: [...args.selectedItems, item],
74+
})
75+
}
76+
/>
77+
)
78+
},
79+
args: {
80+
items: Object.values(defaultItems),
81+
selectedItems: [],
82+
},
83+
argTypes: {
84+
items: { control: 'object' },
85+
selectedItems: { control: 'object' },
86+
dropdownHelpMessage: {
87+
control: { type: 'select' },
88+
options: ['文字列', 'ReactNode'],
89+
mapping: {
90+
文字列: 'ヘルプメッセージ',
91+
ReactNode: <Text className="shr-text-danger">React Nodeを渡したメッセージ</Text>,
92+
},
93+
},
94+
},
95+
parameters: {
96+
chromatic: { disableSnapshot: true },
97+
},
98+
excludeStories: ['defaultItems'],
99+
} as Meta<typeof MultiComboBox<{ option: string }>>
100+
101+
export const Playground: StoryObj<typeof MultiComboBox> = {}
102+
103+
export const SelectedItems: StoryObj<typeof MultiComboBox> = {
104+
name: 'selectedItems',
105+
args: {
106+
selectedItems: [defaultItems['option 1'], defaultItems['option 4']],
107+
},
108+
}
109+
110+
export const Disabled: StoryObj<typeof MultiComboBox> = {
111+
name: 'disabled',
112+
args: {
113+
disabled: true,
114+
},
115+
}
116+
117+
export const Error: StoryObj<typeof MultiComboBox> = {
118+
name: 'error',
119+
args: {
120+
error: true,
121+
},
122+
}
123+
124+
export const Creatable: StoryObj<typeof MultiComboBox> = {
125+
name: 'creatable',
126+
args: {
127+
creatable: true,
128+
dropdownHelpMessage: '新しいアイテムを追加できます。',
129+
},
130+
}
131+
132+
export const IsLoading: StoryObj<typeof MultiComboBox> = {
133+
name: 'isLoading',
134+
args: {
135+
isLoading: true,
136+
},
137+
}
138+
139+
export const Width: StoryObj<typeof MultiComboBox> = {
140+
name: 'width',
141+
args: {
142+
width: '20rem',
143+
},
144+
}
145+
146+
export const DropdownHelpMessage: StoryObj<typeof MultiComboBox> = {
147+
name: 'dropdownHelpMessage',
148+
args: {
149+
dropdownHelpMessage: 'ヘルプメッセージ',
150+
},
151+
}
152+
153+
export const DropdownWidth: StoryObj<typeof MultiComboBox> = {
154+
name: 'dropdownWidth',
155+
args: {
156+
dropdownWidth: '30rem',
157+
},
158+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/* eslint-disable smarthr/a11y-input-in-form-control */
2+
import { Meta, StoryObj } from '@storybook/react'
3+
import { userEvent, within } from '@storybook/test'
4+
import React, { useState } from 'react'
5+
6+
import { Stack } from '../../../Layout'
7+
import { ComboBoxItem } from '../../types'
8+
import { MultiComboBox } from '../MultiComboBox'
9+
10+
import { defaultItems } from './MultiComboBox.stories'
11+
12+
/*
13+
* pict multiComboBox.pict
14+
* disabled error width selectedItems
15+
* false true なし なし
16+
* false false あり 複数
17+
* true true あり 一つ
18+
* false false なし 一つ
19+
* true true なし 複数
20+
* true false あり なし
21+
*/
22+
23+
const _cases: Array<Omit<Parameters<typeof MultiComboBox>[0], 'items'>> = [
24+
{ disabled: false, error: true, width: undefined, selectedItems: [] },
25+
{
26+
disabled: false,
27+
error: false,
28+
width: '15em',
29+
selectedItems: [defaultItems['option 1'], defaultItems['option 4']],
30+
},
31+
{ disabled: true, error: true, width: '15em', selectedItems: [defaultItems['option 3']] },
32+
{
33+
disabled: false,
34+
error: false,
35+
width: undefined,
36+
selectedItems: [defaultItems['アイテムのラベルがReactNodeの場合']],
37+
},
38+
{
39+
disabled: true,
40+
error: true,
41+
width: undefined,
42+
selectedItems: [
43+
defaultItems['option 2'],
44+
defaultItems[
45+
'アイテムのラベルが長い場合(ダミーテキストダミーテキストダミーテキストダミーテキスト)'
46+
],
47+
],
48+
},
49+
{ disabled: true, error: false, width: '15em', selectedItems: [] },
50+
]
51+
52+
const waitForRAF = () =>
53+
new Promise<void>((resolve) => {
54+
requestAnimationFrame(() => {
55+
resolve()
56+
})
57+
})
58+
const playMulti = async ({ canvasElement }: { canvasElement: HTMLElement }) => {
59+
const canvas = within(canvasElement)
60+
const comboboxes = await canvas.findAllByRole('combobox')
61+
comboboxes[comboboxes.length - 1].focus()
62+
const body = canvasElement.ownerDocument.body
63+
const option1 = await within(body).findByRole('option', { name: 'option 1' })
64+
await userEvent.click(option1)
65+
await waitForRAF()
66+
const option2 = await within(body).findByRole('option', { name: 'option 2' })
67+
await userEvent.click(option2)
68+
await waitForRAF()
69+
const helpMessage = await within(body).findAllByText('入力でフィルタリングできます。')
70+
await userEvent.click(helpMessage[0]) // カーソルの点滅によるVRTのフレーキーを避けるためにフォーカスを移動する
71+
}
72+
73+
export default {
74+
title: 'Forms(フォーム)/MultiComboBox/VRT',
75+
component: MultiComboBox,
76+
render: (args) => {
77+
const items = Object.values(defaultItems)
78+
const [selectedItems, setSelectedItems] = useState<Array<ComboBoxItem<unknown>>>([])
79+
return (
80+
<Stack align="flex-start" gap={2} className="shr-h-screen">
81+
{_cases.map((props, i) => (
82+
<MultiComboBox {...args} {...props} items={items} key={i} />
83+
))}
84+
<MultiComboBox
85+
{...args}
86+
name="default"
87+
items={items}
88+
dropdownHelpMessage="入力でフィルタリングできます。"
89+
selectedItems={selectedItems}
90+
onChangeSelected={(its) => setSelectedItems(its)}
91+
/>
92+
</Stack>
93+
)
94+
},
95+
play: playMulti,
96+
parameters: {
97+
withTheming: true,
98+
chromatic: { disableSnapshot: false },
99+
},
100+
tags: ['!autodocs', 'skip-test-runner'],
101+
} as Meta<typeof MultiComboBox>
102+
103+
export const VRT: StoryObj<typeof MultiComboBox> = {}
104+
105+
export const VRTForcedColors: StoryObj<typeof MultiComboBox> = {
106+
...VRT,
107+
parameters: {
108+
chromatic: { forcedColors: 'active' },
109+
},
110+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
disabled: true, false
2+
error: true, false
3+
width: あり, なし
4+
selectedItems: 一つ, 複数, なし

0 commit comments

Comments
 (0)