@bronzelabs/oakma-ui 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierrc.cjs +25 -0
- package/.storybook/components/ActionButton.tsx +44 -0
- package/.storybook/components/DummyIcons.tsx +47 -0
- package/.storybook/components/index.ts +2 -0
- package/.storybook/docs/blocks/ImportStatement.tsx +52 -0
- package/.storybook/docs/blocks/index.ts +1 -0
- package/.storybook/docs/page.tsx +41 -0
- package/.storybook/main.ts +21 -0
- package/.storybook/postcss.config.cjs +8 -0
- package/.storybook/preview-body.html +20 -0
- package/.storybook/preview-head.html +6 -0
- package/.storybook/preview.tsx +30 -0
- package/.storybook/tailwind.css +6 -0
- package/.storybook/utils/index.ts +2 -0
- package/.storybook/utils/renderAsReact.tsx +30 -0
- package/.storybook/utils/renderDocsWithProps.tsx +22 -0
- package/@types/markdown.d.ts +4 -0
- package/README.md +3 -0
- package/eslint.config.js +91 -0
- package/package.json +63 -0
- package/postcss.config.cjs +8 -0
- package/scripts/release.sh +76 -0
- package/src/components/Button/Button.stories.tsx +314 -0
- package/src/components/Button/Button.tsx +132 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/Button/types.ts +19 -0
- package/src/components/Checkbox/Checkbox.stories.tsx +152 -0
- package/src/components/Checkbox/Checkbox.tsx +90 -0
- package/src/components/Checkbox/index.ts +2 -0
- package/src/components/Checkbox/types.ts +6 -0
- package/src/components/Chip/Chip.stories.tsx +146 -0
- package/src/components/Chip/Chip.tsx +59 -0
- package/src/components/Chip/index.ts +2 -0
- package/src/components/Chip/types.ts +6 -0
- package/src/components/Drawer/Drawer.docs.md +88 -0
- package/src/components/Drawer/Drawer.stories.tsx +239 -0
- package/src/components/Drawer/Drawer.tsx +194 -0
- package/src/components/Drawer/index.ts +3 -0
- package/src/components/Drawer/types.ts +3 -0
- package/src/components/Dropdown/AsyncDropdown.tsx +105 -0
- package/src/components/Dropdown/Dropdown.docs.md +33 -0
- package/src/components/Dropdown/Dropdown.stories.tsx +419 -0
- package/src/components/Dropdown/Dropdown.tsx +104 -0
- package/src/components/Dropdown/MultiValue.tsx +19 -0
- package/src/components/Dropdown/ValueContainer.tsx +114 -0
- package/src/components/Dropdown/index.ts +4 -0
- package/src/components/Dropdown/types.ts +29 -0
- package/src/components/Dropdown/useDropdown.tsx +257 -0
- package/src/components/Logo/Logo.stories.tsx +130 -0
- package/src/components/Logo/Logo.tsx +80 -0
- package/src/components/Logo/index.ts +2 -0
- package/src/components/Modal/Modal.docs.md +94 -0
- package/src/components/Modal/Modal.stories.tsx +318 -0
- package/src/components/Modal/Modal.tsx +217 -0
- package/src/components/Modal/index.ts +1 -0
- package/src/components/MultiSelect/AsyncMultiSelect.tsx +47 -0
- package/src/components/MultiSelect/MultiSelect.docs.md +37 -0
- package/src/components/MultiSelect/MultiSelect.stories.tsx +493 -0
- package/src/components/MultiSelect/MultiSelect.tsx +81 -0
- package/src/components/MultiSelect/index.ts +2 -0
- package/src/components/Notification/Notification.stories.tsx +158 -0
- package/src/components/Notification/Notification.tsx +110 -0
- package/src/components/Notification/index.ts +1 -0
- package/src/components/Notification/types.ts +11 -0
- package/src/components/Notifications/Notifications.docs.md +103 -0
- package/src/components/Notifications/Notifications.stories.tsx +159 -0
- package/src/components/Notifications/Notifications.tsx +90 -0
- package/src/components/Notifications/NotificationsContext.tsx +90 -0
- package/src/components/Notifications/index.ts +7 -0
- package/src/components/Select/Select.stories.tsx +234 -0
- package/src/components/Select/Select.tsx +129 -0
- package/src/components/Select/index.ts +2 -0
- package/src/components/Select/types.ts +1 -0
- package/src/components/Spinner/Spinner.stories.tsx +55 -0
- package/src/components/Spinner/Spinner.tsx +48 -0
- package/src/components/Spinner/index.ts +2 -0
- package/src/components/Spinner/types.ts +8 -0
- package/src/components/TextArea/TextArea.stories.tsx +243 -0
- package/src/components/TextArea/TextArea.tsx +133 -0
- package/src/components/TextArea/index.ts +2 -0
- package/src/components/TextArea/types.ts +4 -0
- package/src/components/TextField/Container.tsx +68 -0
- package/src/components/TextField/ErrorMessage.tsx +37 -0
- package/src/components/TextField/Icon.tsx +77 -0
- package/src/components/TextField/Label.tsx +56 -0
- package/src/components/TextField/NotchBorder.tsx +67 -0
- package/src/components/TextField/index.ts +14 -0
- package/src/components/TextField/types.ts +15 -0
- package/src/components/TextField/useInputKeyboardFocus.tsx +63 -0
- package/src/components/TextInput/TextInput.stories.tsx +384 -0
- package/src/components/TextInput/TextInput.tsx +255 -0
- package/src/components/TextInput/index.ts +2 -0
- package/src/components/TextInput/types.ts +4 -0
- package/src/components/Toggle/Toggle.stories.tsx +142 -0
- package/src/components/Toggle/Toggle.tsx +69 -0
- package/src/components/Toggle/index.ts +1 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/useCombinedRefs.ts +37 -0
- package/src/hooks/useEventListener.ts +87 -0
- package/src/hooks/useFocusTrap/createAriaHider.ts +62 -0
- package/src/hooks/useFocusTrap/index.ts +1 -0
- package/src/hooks/useFocusTrap/scopeTab.ts +46 -0
- package/src/hooks/useFocusTrap/tabbable.ts +107 -0
- package/src/hooks/useFocusTrap/useFocusTrap.ts +97 -0
- package/src/hooks/useIsomorphicLayoutEffect.ts +14 -0
- package/src/hooks/useLockBodyScroll.ts +24 -0
- package/src/hooks/useOnClickOutside.ts +53 -0
- package/src/index.ts +22 -0
- package/src/tailwind.css +4 -0
- package/src/types/helpers.ts +11 -0
- package/src/types/polymorphic.ts +39 -0
- package/src/utils/animation/variants.ts +21 -0
- package/src/utils/array/index.ts +1 -0
- package/src/utils/array/uniqBy.ts +12 -0
- package/src/utils/common/index.ts +1 -0
- package/src/utils/common/isFunction.ts +17 -0
- package/src/utils/react/extractDisplayName.ts +15 -0
- package/src/utils/react/index.ts +1 -0
- package/tsconfig.json +16 -0
- package/tsconfig.production.json +19 -0
- package/tsup.config.ts +16 -0
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
|
|
3
|
+
// Components
|
|
4
|
+
import MultiSelect from "./MultiSelect"
|
|
5
|
+
import AsyncMultiSelect from "./AsyncMultiSelect"
|
|
6
|
+
import { DummyPhoneIcon, StorybookActionButton } from "../../../.storybook/components"
|
|
7
|
+
|
|
8
|
+
// Types
|
|
9
|
+
import type { StoryFn, Meta } from "@storybook/react-webpack5"
|
|
10
|
+
|
|
11
|
+
// Utils
|
|
12
|
+
import extraDocs from "./MultiSelect.docs.md"
|
|
13
|
+
import { renderDocsWithProps } from "../../../.storybook/utils"
|
|
14
|
+
import { TEXT_FIELD_SIZES } from "../TextField"
|
|
15
|
+
|
|
16
|
+
// Data
|
|
17
|
+
const mockOptions = {
|
|
18
|
+
default: [
|
|
19
|
+
{
|
|
20
|
+
label: "Item 1",
|
|
21
|
+
value: "item-1",
|
|
22
|
+
color: "blue",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
label: "Item 2",
|
|
26
|
+
value: "item-2",
|
|
27
|
+
isDisabled: true,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
label: "Item 3",
|
|
31
|
+
value: "item-3",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
label: "Item 4",
|
|
35
|
+
value: "item-4",
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
long: [
|
|
39
|
+
{
|
|
40
|
+
label: "This is a very long first item to have in a multiselect dropdown",
|
|
41
|
+
value: "item-1",
|
|
42
|
+
color: "blue",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
label: "Long Item 2",
|
|
46
|
+
value: "item-2",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
label:
|
|
50
|
+
"The third long multiselect item that should wrap onto multiple lines if the width of the dropdown is too narrow",
|
|
51
|
+
value: "item-3",
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
subTitle: [
|
|
55
|
+
{
|
|
56
|
+
label: "Item 1",
|
|
57
|
+
value: "item-1",
|
|
58
|
+
subTitle: "Sub text for item 1",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
label: "Item 2",
|
|
62
|
+
value: "item-2",
|
|
63
|
+
subTitle: "Sub text for item 2",
|
|
64
|
+
isDisabled: true,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
label: "Item 3",
|
|
68
|
+
value: "item-3",
|
|
69
|
+
subTitle: "Sub text for item 3",
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
icons: [
|
|
73
|
+
{
|
|
74
|
+
label: "Item 1",
|
|
75
|
+
value: "item-1",
|
|
76
|
+
icon: DummyPhoneIcon,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
label: "Item 2",
|
|
80
|
+
value: "item-2",
|
|
81
|
+
icon: DummyPhoneIcon,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
label: "Item 3",
|
|
85
|
+
value: "item-3",
|
|
86
|
+
icon: DummyPhoneIcon,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
iconsSubTitle: [
|
|
90
|
+
{
|
|
91
|
+
label: "Item 1",
|
|
92
|
+
value: "item-1",
|
|
93
|
+
icon: DummyPhoneIcon,
|
|
94
|
+
subTitle: "Sub text for item 1",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
label: "Item 2",
|
|
98
|
+
value: "item-2",
|
|
99
|
+
icon: DummyPhoneIcon,
|
|
100
|
+
subTitle: "Sub text for item 2",
|
|
101
|
+
isDisabled: true,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
label: "Item 3",
|
|
105
|
+
value: "item-3",
|
|
106
|
+
icon: DummyPhoneIcon,
|
|
107
|
+
subTitle: "Sub text for item 3",
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
extra: [
|
|
111
|
+
{
|
|
112
|
+
label: "Item 1",
|
|
113
|
+
value: "item-1",
|
|
114
|
+
color: "blue",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
label: "Item 2",
|
|
118
|
+
value: "item-2",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
label: "Item 3",
|
|
122
|
+
value: "item-3",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
label: "Item 4",
|
|
126
|
+
value: "item-4",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
label: "Item 5",
|
|
130
|
+
value: "item-5",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
label: "Item 6",
|
|
134
|
+
value: "item-6",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
label: "Item 7",
|
|
138
|
+
value: "item-7",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
label: "Item 8",
|
|
142
|
+
value: "item-8",
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
users: [
|
|
146
|
+
{ label: "John Doe", value: "john-doe" },
|
|
147
|
+
{ label: "Jane Smith", value: "jane-smith" },
|
|
148
|
+
{ label: "Bob Johnson", value: "bob-johnson" },
|
|
149
|
+
],
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/*
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
const meta: Meta<typeof MultiSelect> = {
|
|
160
|
+
title: "Components/MultiSelect",
|
|
161
|
+
component: MultiSelect,
|
|
162
|
+
parameters: {
|
|
163
|
+
docs: {
|
|
164
|
+
description: {
|
|
165
|
+
component:
|
|
166
|
+
"MultiSelect is a fancy select/combobox built on top of [react-select](https://react-select.com), that can be used to select multiple values from a list.\n\nThe main (and Oakma specific) props can be seen below, however, a full list of accepted props can be found in the react-select [docs](https://react-select.com/props). For details on how to use the component with `react-hook-form` see [here](#usage-with-react-hook-form).",
|
|
167
|
+
},
|
|
168
|
+
page: renderDocsWithProps({ extraDocs }),
|
|
169
|
+
},
|
|
170
|
+
controls: {
|
|
171
|
+
exclude: ["className", "ref", "children", "name"],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
argTypes: {
|
|
175
|
+
options: {
|
|
176
|
+
type: {
|
|
177
|
+
name: "other",
|
|
178
|
+
value: "(Option | Group)[]",
|
|
179
|
+
},
|
|
180
|
+
description: "Array of options that populate the MultiSelect menu.",
|
|
181
|
+
table: {
|
|
182
|
+
type: {
|
|
183
|
+
summary: "(Option | Group)[]",
|
|
184
|
+
},
|
|
185
|
+
defaultValue: {
|
|
186
|
+
summary: "undefined",
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
label: {
|
|
191
|
+
type: "string",
|
|
192
|
+
description: "Visible label rendered above the MultiSelect.",
|
|
193
|
+
table: {
|
|
194
|
+
type: { summary: "string" },
|
|
195
|
+
defaultValue: { summary: "undefined" },
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
size: {
|
|
199
|
+
type: "string",
|
|
200
|
+
description: "Determines the size of the MultiSelect field.",
|
|
201
|
+
table: {
|
|
202
|
+
type: { summary: TEXT_FIELD_SIZES.map(size => `"${size}"`).join(" | ") },
|
|
203
|
+
defaultValue: { summary: '"md"' },
|
|
204
|
+
},
|
|
205
|
+
options: TEXT_FIELD_SIZES,
|
|
206
|
+
control: { type: "select" },
|
|
207
|
+
},
|
|
208
|
+
showOptional: {
|
|
209
|
+
type: "boolean",
|
|
210
|
+
description:
|
|
211
|
+
'When `true`, and if the `required` prop is not set, an "Optional" tag will be displayed next to the label.',
|
|
212
|
+
table: {
|
|
213
|
+
type: { summary: "boolean" },
|
|
214
|
+
defaultValue: { summary: "true" },
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
leadingIcon: {
|
|
218
|
+
type: {
|
|
219
|
+
name: "other",
|
|
220
|
+
value: "{ icon: ReactNode }",
|
|
221
|
+
},
|
|
222
|
+
description:
|
|
223
|
+
"An object with an `icon` property containing a React node to render inside the field, aligned to the left.",
|
|
224
|
+
table: {
|
|
225
|
+
type: { summary: "{ icon: ReactNode }" },
|
|
226
|
+
defaultValue: { summary: "undefined" },
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
isLoading: {
|
|
230
|
+
type: "boolean",
|
|
231
|
+
description: "Is the MultiSelect in a state of loading (async).",
|
|
232
|
+
table: {
|
|
233
|
+
type: { summary: "boolean" },
|
|
234
|
+
defaultValue: { summary: "false" },
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
isDisabled: {
|
|
238
|
+
type: "boolean",
|
|
239
|
+
description: "Is the MultiSelect disabled.",
|
|
240
|
+
table: {
|
|
241
|
+
type: { summary: "boolean" },
|
|
242
|
+
defaultValue: { summary: "false" },
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
isClearable: {
|
|
246
|
+
type: "boolean",
|
|
247
|
+
description: "Is the MultiSelect value clearable.",
|
|
248
|
+
table: {
|
|
249
|
+
type: { summary: "boolean" },
|
|
250
|
+
defaultValue: { summary: "false" },
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
required: {
|
|
254
|
+
type: "boolean",
|
|
255
|
+
description: "Whether the Dropdown is required.",
|
|
256
|
+
table: {
|
|
257
|
+
type: { summary: "boolean" },
|
|
258
|
+
defaultValue: { summary: "false" },
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
tabSelectsValue: {
|
|
262
|
+
type: "boolean",
|
|
263
|
+
description: "Select the currently focused option when the user presses tab",
|
|
264
|
+
table: {
|
|
265
|
+
type: { summary: "boolean" },
|
|
266
|
+
defaultValue: { summary: "false" },
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
closeMenuOnSelect: {
|
|
270
|
+
type: "boolean",
|
|
271
|
+
description: "Close the dropdown menu when an option is selected.",
|
|
272
|
+
table: {
|
|
273
|
+
type: { summary: "boolean" },
|
|
274
|
+
defaultValue: { summary: "false" },
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
hideSelectedOptions: {
|
|
278
|
+
type: "boolean",
|
|
279
|
+
description: "Hide options that have already been selected from the dropdown menu.",
|
|
280
|
+
table: {
|
|
281
|
+
type: { summary: "boolean" },
|
|
282
|
+
defaultValue: { summary: "false" },
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
args: {
|
|
287
|
+
name: "multi-select",
|
|
288
|
+
label: "Label",
|
|
289
|
+
size: "md",
|
|
290
|
+
showOptional: true,
|
|
291
|
+
options: mockOptions.default,
|
|
292
|
+
required: false,
|
|
293
|
+
tabSelectsValue: false,
|
|
294
|
+
closeMenuOnSelect: false,
|
|
295
|
+
hideSelectedOptions: false,
|
|
296
|
+
isLoading: false,
|
|
297
|
+
isDisabled: false,
|
|
298
|
+
isClearable: false,
|
|
299
|
+
},
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const mockLoadOptions = (inputValue: string) =>
|
|
303
|
+
new Promise<(typeof mockOptions.users)[number][]>(resolve => {
|
|
304
|
+
setTimeout(() => {
|
|
305
|
+
resolve(
|
|
306
|
+
mockOptions.users.filter(opt => opt.label.toLowerCase().includes(inputValue.toLowerCase())),
|
|
307
|
+
)
|
|
308
|
+
}, 600)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
// Templates
|
|
312
|
+
const Template: StoryFn<typeof MultiSelect> = args => <MultiSelect {...args} />
|
|
313
|
+
|
|
314
|
+
const FormTemplate: StoryFn<typeof MultiSelect> = args => {
|
|
315
|
+
return (
|
|
316
|
+
<form
|
|
317
|
+
className="flex flex-col items-stretch gap-3"
|
|
318
|
+
onSubmit={e => {
|
|
319
|
+
e.preventDefault()
|
|
320
|
+
const formData = new FormData(e.currentTarget)
|
|
321
|
+
alert(`Selected values: ${JSON.stringify(formData.getAll("multi-select"))}`)
|
|
322
|
+
}}
|
|
323
|
+
>
|
|
324
|
+
<MultiSelect {...args} />
|
|
325
|
+
<StorybookActionButton type="submit">Submit</StorybookActionButton>
|
|
326
|
+
</form>
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const ShowAllSelectedTemplate: StoryFn<typeof MultiSelect> = args => {
|
|
331
|
+
return (
|
|
332
|
+
<>
|
|
333
|
+
<MultiSelect {...args} />
|
|
334
|
+
<MultiSelect {...args} showSelectedCount={false} />
|
|
335
|
+
</>
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const AsyncTemplate: StoryFn<typeof MultiSelect> = args => (
|
|
340
|
+
<AsyncMultiSelect
|
|
341
|
+
id="async-multi-select"
|
|
342
|
+
label={args.label}
|
|
343
|
+
showOptional={args.showOptional}
|
|
344
|
+
size={args.size}
|
|
345
|
+
loadOptions={mockLoadOptions}
|
|
346
|
+
defaultOptions={mockOptions.users}
|
|
347
|
+
placeholder="Search users…"
|
|
348
|
+
/>
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
// Stories
|
|
352
|
+
const Default = Template.bind({})
|
|
353
|
+
const WithSubtitles = Template.bind({})
|
|
354
|
+
const WithIcons = Template.bind({})
|
|
355
|
+
const WithIconsAndSubtitles = Template.bind({})
|
|
356
|
+
const WithLongOptions = Template.bind({})
|
|
357
|
+
const Required = FormTemplate.bind({})
|
|
358
|
+
const ShowAllSelected = ShowAllSelectedTemplate.bind({})
|
|
359
|
+
const Async = AsyncTemplate.bind({})
|
|
360
|
+
|
|
361
|
+
// Args
|
|
362
|
+
WithSubtitles.args = {
|
|
363
|
+
options: mockOptions.subTitle,
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
WithIcons.args = {
|
|
367
|
+
options: mockOptions.icons,
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
WithIconsAndSubtitles.args = {
|
|
371
|
+
options: mockOptions.iconsSubTitle,
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
WithLongOptions.args = {
|
|
375
|
+
options: mockOptions.long,
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
Required.args = {
|
|
379
|
+
required: true,
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
ShowAllSelected.args = {
|
|
383
|
+
options: mockOptions.extra,
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Decorators
|
|
387
|
+
Default.decorators = [
|
|
388
|
+
Story => (
|
|
389
|
+
<div className="flex flex-col" style={{ width: 450, height: 300 }}>
|
|
390
|
+
<Story />
|
|
391
|
+
</div>
|
|
392
|
+
),
|
|
393
|
+
]
|
|
394
|
+
|
|
395
|
+
WithSubtitles.decorators = Default.decorators
|
|
396
|
+
|
|
397
|
+
WithIcons.decorators = Default.decorators
|
|
398
|
+
|
|
399
|
+
WithIconsAndSubtitles.decorators = Default.decorators
|
|
400
|
+
|
|
401
|
+
WithLongOptions.decorators = Default.decorators
|
|
402
|
+
|
|
403
|
+
Required.decorators = Default.decorators
|
|
404
|
+
|
|
405
|
+
ShowAllSelected.decorators = [
|
|
406
|
+
Story => (
|
|
407
|
+
<div className="flex flex-col gap-6" style={{ width: 450, height: 450 }}>
|
|
408
|
+
<Story />
|
|
409
|
+
</div>
|
|
410
|
+
),
|
|
411
|
+
]
|
|
412
|
+
|
|
413
|
+
Async.decorators = Default.decorators
|
|
414
|
+
|
|
415
|
+
Default.parameters = {
|
|
416
|
+
docs: {
|
|
417
|
+
description: {
|
|
418
|
+
story: "A basic MultiSelect with a flat list of options.",
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
WithSubtitles.parameters = {
|
|
424
|
+
docs: {
|
|
425
|
+
description: {
|
|
426
|
+
story:
|
|
427
|
+
"Options can include a `subTitle` string to provide additional context below the label.",
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
WithIcons.parameters = {
|
|
433
|
+
docs: {
|
|
434
|
+
description: {
|
|
435
|
+
story: "Options can include an `icon` component to render a visual alongside each label.",
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
WithIconsAndSubtitles.parameters = {
|
|
441
|
+
docs: {
|
|
442
|
+
description: {
|
|
443
|
+
story: "Icons and subtitles can be combined to provide richer option content.",
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
WithLongOptions.parameters = {
|
|
449
|
+
docs: {
|
|
450
|
+
description: {
|
|
451
|
+
story: "Long option labels wrap naturally within the menu without truncation.",
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
Required.parameters = {
|
|
457
|
+
docs: {
|
|
458
|
+
description: {
|
|
459
|
+
story:
|
|
460
|
+
'Setting `required` hides the "Optional" tag and marks the field as required. The form will not submit until at least one value is selected.',
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
ShowAllSelected.parameters = {
|
|
466
|
+
docs: {
|
|
467
|
+
description: {
|
|
468
|
+
story:
|
|
469
|
+
"By default, selected options are summarised as a count badge once there are too many to display inline. Set `showSelectedCount={false}` to always show every selected option as an individual tag.",
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
Async.parameters = {
|
|
475
|
+
docs: {
|
|
476
|
+
description: {
|
|
477
|
+
story:
|
|
478
|
+
"Use `AsyncMultiSelect` when options are fetched remotely. Pass a `loadOptions` function that takes the current input string and returns a promise of options. `defaultOptions` populates the list before the user types.",
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export {
|
|
484
|
+
Default,
|
|
485
|
+
WithSubtitles,
|
|
486
|
+
WithIcons,
|
|
487
|
+
WithIconsAndSubtitles,
|
|
488
|
+
WithLongOptions,
|
|
489
|
+
Required,
|
|
490
|
+
ShowAllSelected,
|
|
491
|
+
Async,
|
|
492
|
+
}
|
|
493
|
+
export default meta
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React from "react"
|
|
4
|
+
|
|
5
|
+
// Components
|
|
6
|
+
import Dropdown, { type DropdownProps } from "../Dropdown"
|
|
7
|
+
import Checkbox from "../Checkbox"
|
|
8
|
+
|
|
9
|
+
// Utils
|
|
10
|
+
import { clsx } from "clsx"
|
|
11
|
+
import { components, type OptionProps } from "react-select"
|
|
12
|
+
|
|
13
|
+
// Props
|
|
14
|
+
interface MultiSelectProps extends Omit<DropdownProps, "isMulti"> {
|
|
15
|
+
cy?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const dataHasIcon = (check: any): check is { icon: React.ElementType } => {
|
|
19
|
+
return !!check?.icon && check?.icon !== null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const MultiSelectOption = (props: OptionProps<any>) => {
|
|
23
|
+
const Icon = dataHasIcon(props?.data) ? props.data.icon : undefined
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<components.Option
|
|
27
|
+
{...props}
|
|
28
|
+
className={clsx(props?.className, "flex! w-full items-center justify-between gap-3")}
|
|
29
|
+
>
|
|
30
|
+
{Icon && <Icon className="size-5" />}
|
|
31
|
+
<div className="flex grow flex-col">
|
|
32
|
+
<span>{props?.data?.label}</span>
|
|
33
|
+
{props?.data?.subTitle && <span>{props.data.subTitle}</span>}
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<Checkbox checked={props?.isSelected} readOnly />
|
|
37
|
+
</components.Option>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const multiSelectComponents = { Option: MultiSelectOption }
|
|
42
|
+
|
|
43
|
+
/*
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
const MultiSelect: React.FC<MultiSelectProps> = ({
|
|
52
|
+
tabSelectsValue = false,
|
|
53
|
+
closeMenuOnSelect = false,
|
|
54
|
+
hideSelectedOptions = false,
|
|
55
|
+
isClearable = false,
|
|
56
|
+
...rest
|
|
57
|
+
}) => {
|
|
58
|
+
// Functions
|
|
59
|
+
// const handleChange: typeof onChange = (newValue, data): void => {
|
|
60
|
+
// onChange?.(newValue, data)
|
|
61
|
+
// setTimeout(() => combinedRef.current?.focusInput(), 0)
|
|
62
|
+
// }
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Dropdown
|
|
66
|
+
closeMenuOnSelect={closeMenuOnSelect}
|
|
67
|
+
hideSelectedOptions={hideSelectedOptions}
|
|
68
|
+
tabSelectsValue={tabSelectsValue}
|
|
69
|
+
// @ts-expect-error - The `isMulti` prop is not supported when used by external consumers, however it is required internally to render the correct components and behavior.
|
|
70
|
+
isMulti
|
|
71
|
+
isClearable={isClearable}
|
|
72
|
+
components={multiSelectComponents}
|
|
73
|
+
{...rest}
|
|
74
|
+
/>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
MultiSelect.displayName = "MultiSelect"
|
|
79
|
+
export default MultiSelect
|
|
80
|
+
export { MultiSelectOption }
|
|
81
|
+
export type { MultiSelectProps }
|