@sproutsocial/seeds-react-menu 1.10.8 → 1.11.0
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/.turbo/turbo-build.log +21 -15
- package/CHANGELOG.md +22 -0
- package/dist/esm/index.js +45 -24
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/v2/index.js +1 -1
- package/dist/esm/v2/index.js.map +1 -1
- package/dist/esm/v3/index.js +800 -0
- package/dist/esm/v3/index.js.map +1 -0
- package/dist/index.d.mts +22 -22
- package/dist/index.d.ts +22 -22
- package/dist/index.js +35 -14
- package/dist/index.js.map +1 -1
- package/dist/v2/index.js +1 -1
- package/dist/v2/index.js.map +1 -1
- package/dist/v3/index.d.mts +295 -0
- package/dist/v3/index.d.ts +295 -0
- package/dist/v3/index.js +868 -0
- package/dist/v3/index.js.map +1 -0
- package/package.json +7 -1
- package/src/MenuGroup/MenuGroup.tsx +16 -3
- package/src/MenuGroup/__tests__/MenuGroup.test.tsx +19 -0
- package/src/MenuItem/MenuItem.tsx +21 -10
- package/src/MenuItem/__tests__/MenuItem.test.tsx +19 -0
- package/src/v2/Common-V2/ComboboxContent.tsx +1 -1
- package/src/v3/Common/ComboboxItem.tsx +82 -0
- package/src/v3/Common/ComboboxStyles.tsx +475 -0
- package/src/v3/Common/SelectGroup.tsx +28 -0
- package/src/v3/Common/SelectIcons.tsx +26 -0
- package/src/v3/Common/SelectItem.tsx +108 -0
- package/src/v3/Common/SelectStyles.tsx +126 -0
- package/src/v3/Common/VirtualizedComboboxList.tsx +200 -0
- package/src/v3/Common/groupItems.ts +12 -0
- package/src/v3/Common/types.ts +3 -0
- package/src/v3/ModalStories.stories.tsx +181 -0
- package/src/v3/MultiCombobox.stories.tsx +322 -0
- package/src/v3/MultiCombobox.tsx +562 -0
- package/src/v3/MultiSearchableSelect.stories.tsx +389 -0
- package/src/v3/MultiSearchableSelect.tsx +545 -0
- package/src/v3/MultiSelect.stories.tsx +300 -0
- package/src/v3/MultiSelect.tsx +498 -0
- package/src/v3/SeedsPortal.tsx +35 -0
- package/src/v3/SingleCombobox.stories.tsx +169 -0
- package/src/v3/SingleCombobox.tsx +304 -0
- package/src/v3/SingleSearchableSelect.stories.tsx +239 -0
- package/src/v3/SingleSearchableSelect.tsx +319 -0
- package/src/v3/SingleSelect.stories.tsx +227 -0
- package/src/v3/SingleSelect.tsx +245 -0
- package/src/v3/index.ts +7 -0
- package/src/v3/storyData.tsx +117 -0
- package/tsup.config.ts +1 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Combobox } from "@base-ui/react/combobox";
|
|
3
|
+
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
4
|
+
import { useSeedsPortalContainer } from "./SeedsPortal";
|
|
5
|
+
import ComboboxItem from "./Common/ComboboxItem";
|
|
6
|
+
import VirtualizedComboboxList from "./Common/VirtualizedComboboxList";
|
|
7
|
+
import type { DataWithId } from "./Common/types";
|
|
8
|
+
import { groupItems } from "./Common/groupItems";
|
|
9
|
+
import { Field } from "./Common/SelectStyles";
|
|
10
|
+
import {
|
|
11
|
+
ComboboxGroup,
|
|
12
|
+
ComboboxGroupLabel,
|
|
13
|
+
ComboboxSeparator,
|
|
14
|
+
ComboboxPositioner,
|
|
15
|
+
ComboboxEmpty,
|
|
16
|
+
ComboboxStatus,
|
|
17
|
+
SearchableSelectTrigger,
|
|
18
|
+
SearchableSelectTriggerIcon,
|
|
19
|
+
SearchableSelectPlaceholder,
|
|
20
|
+
SearchableSelectPopup,
|
|
21
|
+
SearchableSelectSearchContainer,
|
|
22
|
+
SearchableSelectInput,
|
|
23
|
+
SearchableSelectList,
|
|
24
|
+
} from "./Common/ComboboxStyles";
|
|
25
|
+
import { StyledChevron, ChevronIcon } from "./Common/SelectIcons";
|
|
26
|
+
|
|
27
|
+
// ─── Props ────────────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
interface SingleSearchableSelectBaseProps {
|
|
30
|
+
id?: string;
|
|
31
|
+
placeholder?: string;
|
|
32
|
+
searchPlaceholder?: string;
|
|
33
|
+
emptyText?: string;
|
|
34
|
+
isLoading?: boolean;
|
|
35
|
+
loadingText?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface SingleSearchableSelectDataBaseProps<T extends DataWithId>
|
|
39
|
+
extends SingleSearchableSelectBaseProps {
|
|
40
|
+
data: T[];
|
|
41
|
+
itemToString: (item: T | null) => string;
|
|
42
|
+
renderItem?: (item: T) => React.ReactNode;
|
|
43
|
+
renderSelection?: (item: T) => React.ReactNode;
|
|
44
|
+
inputType?: "icon" | "radio" | "checkbox" | "none";
|
|
45
|
+
selectedItemId?: T["id"] | null;
|
|
46
|
+
defaultSelectedItemId?: T["id"] | null;
|
|
47
|
+
onSelectedItemIdChange?: (id: T["id"] | null) => void;
|
|
48
|
+
children?: never;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface SingleSearchableSelectDataProps<T extends DataWithId>
|
|
52
|
+
extends SingleSearchableSelectDataBaseProps<T> {
|
|
53
|
+
isVirtualized?: false;
|
|
54
|
+
groupBy?: (item: T) => string;
|
|
55
|
+
renderGroupHeading?: (label: string) => React.ReactNode;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface SingleSearchableSelectVirtualizedProps<T extends DataWithId>
|
|
59
|
+
extends SingleSearchableSelectDataBaseProps<T> {
|
|
60
|
+
isVirtualized: true;
|
|
61
|
+
groupBy?: never;
|
|
62
|
+
renderGroupHeading?: never;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface SingleSearchableSelectChildrenProps
|
|
66
|
+
extends SingleSearchableSelectBaseProps {
|
|
67
|
+
children: React.ReactNode;
|
|
68
|
+
selectedItemId?: string | number | null;
|
|
69
|
+
defaultSelectedItemId?: string | number | null;
|
|
70
|
+
onSelectedItemIdChange?: (id: string | number | null) => void;
|
|
71
|
+
data?: never;
|
|
72
|
+
itemToString?: never;
|
|
73
|
+
renderItem?: never;
|
|
74
|
+
renderSelection?: never;
|
|
75
|
+
inputType?: never;
|
|
76
|
+
groupBy?: never;
|
|
77
|
+
renderGroupHeading?: never;
|
|
78
|
+
isVirtualized?: never;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type SingleSearchableSelectProps<T extends DataWithId> =
|
|
82
|
+
| SingleSearchableSelectDataProps<T>
|
|
83
|
+
| SingleSearchableSelectVirtualizedProps<T>
|
|
84
|
+
| SingleSearchableSelectChildrenProps;
|
|
85
|
+
|
|
86
|
+
// ─── Component ────────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
export default function SingleSearchableSelect<T extends DataWithId>(
|
|
89
|
+
props: SingleSearchableSelectProps<T>
|
|
90
|
+
) {
|
|
91
|
+
const {
|
|
92
|
+
id,
|
|
93
|
+
placeholder = "Select...",
|
|
94
|
+
searchPlaceholder = "Search...",
|
|
95
|
+
emptyText = "No results found.",
|
|
96
|
+
isLoading = false,
|
|
97
|
+
loadingText = "Loading...",
|
|
98
|
+
} = props;
|
|
99
|
+
|
|
100
|
+
const isChildrenMode = "children" in props && props.children != null;
|
|
101
|
+
|
|
102
|
+
type DataProps =
|
|
103
|
+
| SingleSearchableSelectDataProps<T>
|
|
104
|
+
| SingleSearchableSelectVirtualizedProps<T>;
|
|
105
|
+
|
|
106
|
+
const data = isChildrenMode ? ([] as T[]) : (props as DataProps).data;
|
|
107
|
+
const itemToString = isChildrenMode
|
|
108
|
+
? (_item: T | null) => ""
|
|
109
|
+
: (props as DataProps).itemToString;
|
|
110
|
+
const renderItem = isChildrenMode
|
|
111
|
+
? undefined
|
|
112
|
+
: (props as DataProps).renderItem;
|
|
113
|
+
const renderSelection = isChildrenMode
|
|
114
|
+
? undefined
|
|
115
|
+
: (props as DataProps).renderSelection;
|
|
116
|
+
const inputType = isChildrenMode
|
|
117
|
+
? "icon"
|
|
118
|
+
: (props as DataProps).inputType ?? "icon";
|
|
119
|
+
const isVirtualized = isChildrenMode
|
|
120
|
+
? false
|
|
121
|
+
: (props as DataProps).isVirtualized ?? false;
|
|
122
|
+
const groupBy = isChildrenMode
|
|
123
|
+
? undefined
|
|
124
|
+
: isVirtualized
|
|
125
|
+
? undefined
|
|
126
|
+
: (props as SingleSearchableSelectDataProps<T>).groupBy;
|
|
127
|
+
const renderGroupHeading = isChildrenMode
|
|
128
|
+
? undefined
|
|
129
|
+
: isVirtualized
|
|
130
|
+
? undefined
|
|
131
|
+
: (props as SingleSearchableSelectDataProps<T>).renderGroupHeading;
|
|
132
|
+
const children = isChildrenMode
|
|
133
|
+
? (props as SingleSearchableSelectChildrenProps).children
|
|
134
|
+
: undefined;
|
|
135
|
+
|
|
136
|
+
const selectedItemId = props.selectedItemId;
|
|
137
|
+
const defaultSelectedItemId = props.defaultSelectedItemId;
|
|
138
|
+
const onSelectedItemIdChange = props.onSelectedItemIdChange;
|
|
139
|
+
|
|
140
|
+
const { containerRef, portalContainer } = useSeedsPortalContainer();
|
|
141
|
+
const [open, setOpen] = React.useState(false);
|
|
142
|
+
const virtualizerRef = React.useRef<ReturnType<
|
|
143
|
+
typeof useVirtualizer<HTMLDivElement, Element>
|
|
144
|
+
> | null>(null);
|
|
145
|
+
|
|
146
|
+
const isControlled = selectedItemId !== undefined;
|
|
147
|
+
|
|
148
|
+
const idToItem = React.useCallback(
|
|
149
|
+
(id: T["id"] | null | undefined): T | null => {
|
|
150
|
+
if (id == null) return null;
|
|
151
|
+
return data.find((d) => String(d.id) === String(id)) ?? null;
|
|
152
|
+
},
|
|
153
|
+
[data]
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const [internalValue, setInternalValue] = React.useState<T | null>(() =>
|
|
157
|
+
idToItem(defaultSelectedItemId as T["id"] | null)
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const value = isControlled
|
|
161
|
+
? idToItem(selectedItemId as T["id"] | null)
|
|
162
|
+
: internalValue;
|
|
163
|
+
|
|
164
|
+
function handleValueChange(newItem: T | null) {
|
|
165
|
+
if (!isControlled) setInternalValue(newItem);
|
|
166
|
+
if (onSelectedItemIdChange) {
|
|
167
|
+
onSelectedItemIdChange(newItem ? newItem.id : null);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const groups = React.useMemo(
|
|
172
|
+
() => (groupBy ? groupItems(data, groupBy) : null),
|
|
173
|
+
[data, groupBy]
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<Field>
|
|
178
|
+
<div ref={containerRef} style={{ position: "relative" }} />
|
|
179
|
+
<Combobox.Root
|
|
180
|
+
id={id}
|
|
181
|
+
value={value}
|
|
182
|
+
onValueChange={handleValueChange}
|
|
183
|
+
itemToStringLabel={itemToString}
|
|
184
|
+
isItemEqualToValue={(a, b) => a != null && b != null && a.id === b.id}
|
|
185
|
+
items={
|
|
186
|
+
isVirtualized ? data : groups ?? (isChildrenMode ? undefined : data)
|
|
187
|
+
}
|
|
188
|
+
virtualized={isVirtualized}
|
|
189
|
+
open={open}
|
|
190
|
+
onOpenChange={setOpen}
|
|
191
|
+
onItemHighlighted={
|
|
192
|
+
isVirtualized
|
|
193
|
+
? (item, { reason, index }) => {
|
|
194
|
+
const virt = virtualizerRef.current;
|
|
195
|
+
if (!item || !virt) return;
|
|
196
|
+
const isStart = index === 0;
|
|
197
|
+
const isEnd = index === virt.options.count - 1;
|
|
198
|
+
if (
|
|
199
|
+
reason === "none" ||
|
|
200
|
+
(reason === "keyboard" && (isStart || isEnd))
|
|
201
|
+
) {
|
|
202
|
+
queueMicrotask(() => {
|
|
203
|
+
virt.scrollToIndex(index, {
|
|
204
|
+
align: isEnd ? "start" : "end",
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
: undefined
|
|
210
|
+
}
|
|
211
|
+
>
|
|
212
|
+
<SearchableSelectTrigger id={id}>
|
|
213
|
+
<SearchableSelectPlaceholder>
|
|
214
|
+
<Combobox.Value placeholder={placeholder}>
|
|
215
|
+
{value
|
|
216
|
+
? renderSelection
|
|
217
|
+
? renderSelection(value)
|
|
218
|
+
: itemToString(value)
|
|
219
|
+
: null}
|
|
220
|
+
</Combobox.Value>
|
|
221
|
+
</SearchableSelectPlaceholder>
|
|
222
|
+
<SearchableSelectTriggerIcon>
|
|
223
|
+
<StyledChevron data-chevron>
|
|
224
|
+
<ChevronIcon />
|
|
225
|
+
</StyledChevron>
|
|
226
|
+
</SearchableSelectTriggerIcon>
|
|
227
|
+
</SearchableSelectTrigger>
|
|
228
|
+
|
|
229
|
+
<Combobox.Portal container={portalContainer}>
|
|
230
|
+
<ComboboxPositioner sideOffset={4}>
|
|
231
|
+
<SearchableSelectPopup
|
|
232
|
+
aria-busy={isLoading || undefined}
|
|
233
|
+
style={{ display: "flex", flexDirection: "column" }}
|
|
234
|
+
>
|
|
235
|
+
<SearchableSelectSearchContainer>
|
|
236
|
+
<SearchableSelectInput
|
|
237
|
+
placeholder={searchPlaceholder}
|
|
238
|
+
autoFocus
|
|
239
|
+
/>
|
|
240
|
+
</SearchableSelectSearchContainer>
|
|
241
|
+
<ComboboxStatus>{isLoading ? loadingText : null}</ComboboxStatus>
|
|
242
|
+
<ComboboxEmpty>{isLoading ? null : emptyText}</ComboboxEmpty>
|
|
243
|
+
<SearchableSelectList
|
|
244
|
+
style={
|
|
245
|
+
isVirtualized
|
|
246
|
+
? {
|
|
247
|
+
padding: 0,
|
|
248
|
+
flex: 1,
|
|
249
|
+
minHeight: 0,
|
|
250
|
+
display: "flex",
|
|
251
|
+
flexDirection: "column",
|
|
252
|
+
}
|
|
253
|
+
: undefined
|
|
254
|
+
}
|
|
255
|
+
>
|
|
256
|
+
{isVirtualized ? (
|
|
257
|
+
<VirtualizedComboboxList
|
|
258
|
+
open={open}
|
|
259
|
+
virtualizerRef={virtualizerRef}
|
|
260
|
+
value={value}
|
|
261
|
+
groups={null}
|
|
262
|
+
data={data}
|
|
263
|
+
itemToString={itemToString}
|
|
264
|
+
inputType={inputType}
|
|
265
|
+
renderItem={renderItem}
|
|
266
|
+
/>
|
|
267
|
+
) : children ? (
|
|
268
|
+
children
|
|
269
|
+
) : groups ? (
|
|
270
|
+
(group: { value: string; items: T[] }, index: number) => (
|
|
271
|
+
<React.Fragment key={group.value}>
|
|
272
|
+
<ComboboxGroup items={group.items}>
|
|
273
|
+
<ComboboxGroupLabel>
|
|
274
|
+
{renderGroupHeading
|
|
275
|
+
? renderGroupHeading(group.value)
|
|
276
|
+
: group.value}
|
|
277
|
+
</ComboboxGroupLabel>
|
|
278
|
+
<Combobox.Collection>
|
|
279
|
+
{(item: T) => (
|
|
280
|
+
<ComboboxItem
|
|
281
|
+
key={item.id}
|
|
282
|
+
value={item}
|
|
283
|
+
itemId={String(item.id)}
|
|
284
|
+
label={itemToString(item)}
|
|
285
|
+
inputType={inputType}
|
|
286
|
+
renderItem={
|
|
287
|
+
renderItem
|
|
288
|
+
? () => renderItem(item)
|
|
289
|
+
: () => itemToString(item)
|
|
290
|
+
}
|
|
291
|
+
/>
|
|
292
|
+
)}
|
|
293
|
+
</Combobox.Collection>
|
|
294
|
+
</ComboboxGroup>
|
|
295
|
+
{index < groups.length - 1 && <ComboboxSeparator />}
|
|
296
|
+
</React.Fragment>
|
|
297
|
+
)
|
|
298
|
+
) : (
|
|
299
|
+
(item: T) => (
|
|
300
|
+
<ComboboxItem
|
|
301
|
+
key={item.id}
|
|
302
|
+
value={item}
|
|
303
|
+
itemId={String(item.id)}
|
|
304
|
+
label={itemToString(item)}
|
|
305
|
+
inputType={inputType}
|
|
306
|
+
renderItem={
|
|
307
|
+
renderItem ? () => renderItem(item) : undefined
|
|
308
|
+
}
|
|
309
|
+
/>
|
|
310
|
+
)
|
|
311
|
+
)}
|
|
312
|
+
</SearchableSelectList>
|
|
313
|
+
</SearchableSelectPopup>
|
|
314
|
+
</ComboboxPositioner>
|
|
315
|
+
</Combobox.Portal>
|
|
316
|
+
</Combobox.Root>
|
|
317
|
+
</Field>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import { FormField } from "@sproutsocial/seeds-react-form-field";
|
|
4
|
+
import SingleSelect from "./SingleSelect";
|
|
5
|
+
import SelectItem from "./Common/SelectItem";
|
|
6
|
+
import SelectGroup from "./Common/SelectGroup";
|
|
7
|
+
import { books, itemToString, renderItem } from "./storyData";
|
|
8
|
+
|
|
9
|
+
const meta: Meta<typeof SingleSelect> = {
|
|
10
|
+
title: "Really Under Development/V3/Single Select",
|
|
11
|
+
component: SingleSelect,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
type Story = StoryObj<typeof SingleSelect>;
|
|
16
|
+
|
|
17
|
+
export const Basic: Story = {
|
|
18
|
+
name: "Basic",
|
|
19
|
+
render: () => (
|
|
20
|
+
<div style={{ width: "300px" }}>
|
|
21
|
+
<FormField label="Pick a book">
|
|
22
|
+
{({ id }) => (
|
|
23
|
+
<SingleSelect
|
|
24
|
+
id={id}
|
|
25
|
+
data={books}
|
|
26
|
+
itemToString={itemToString}
|
|
27
|
+
placeholder="Select a book"
|
|
28
|
+
/>
|
|
29
|
+
)}
|
|
30
|
+
</FormField>
|
|
31
|
+
</div>
|
|
32
|
+
),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const IncludePlaceholderItem: Story = {
|
|
36
|
+
name: "Include placeholder item",
|
|
37
|
+
render: () => (
|
|
38
|
+
<div style={{ width: "300px" }}>
|
|
39
|
+
<FormField label="Pick a book">
|
|
40
|
+
{({ id }) => (
|
|
41
|
+
<SingleSelect
|
|
42
|
+
id={id}
|
|
43
|
+
data={books}
|
|
44
|
+
itemToString={itemToString}
|
|
45
|
+
placeholder="Select a book"
|
|
46
|
+
includePlaceholderItem
|
|
47
|
+
/>
|
|
48
|
+
)}
|
|
49
|
+
</FormField>
|
|
50
|
+
</div>
|
|
51
|
+
),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const CustomRenderItem: Story = {
|
|
55
|
+
name: "Custom render item",
|
|
56
|
+
render: () => (
|
|
57
|
+
<div style={{ width: "300px" }}>
|
|
58
|
+
<FormField label="Pick a book">
|
|
59
|
+
{({ id }) => (
|
|
60
|
+
<SingleSelect
|
|
61
|
+
id={id}
|
|
62
|
+
data={books}
|
|
63
|
+
itemToString={itemToString}
|
|
64
|
+
renderItem={renderItem}
|
|
65
|
+
placeholder="Select a book"
|
|
66
|
+
/>
|
|
67
|
+
)}
|
|
68
|
+
</FormField>
|
|
69
|
+
</div>
|
|
70
|
+
),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const Radio: Story = {
|
|
74
|
+
name: "Radio",
|
|
75
|
+
render: () => (
|
|
76
|
+
<div style={{ width: "300px" }}>
|
|
77
|
+
<FormField label="Pick a book">
|
|
78
|
+
{({ id }) => (
|
|
79
|
+
<SingleSelect
|
|
80
|
+
id={id}
|
|
81
|
+
data={books}
|
|
82
|
+
itemToString={itemToString}
|
|
83
|
+
renderItem={renderItem}
|
|
84
|
+
placeholder="Select a book"
|
|
85
|
+
inputType="radio"
|
|
86
|
+
/>
|
|
87
|
+
)}
|
|
88
|
+
</FormField>
|
|
89
|
+
</div>
|
|
90
|
+
),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const Grouped: Story = {
|
|
94
|
+
name: "Grouped",
|
|
95
|
+
render: () => (
|
|
96
|
+
<div style={{ width: "300px" }}>
|
|
97
|
+
<FormField label="Pick a book">
|
|
98
|
+
{({ id }) => (
|
|
99
|
+
<SingleSelect
|
|
100
|
+
id={id}
|
|
101
|
+
data={books}
|
|
102
|
+
itemToString={itemToString}
|
|
103
|
+
renderItem={renderItem}
|
|
104
|
+
placeholder="Select a book"
|
|
105
|
+
groupBy={(book) => book.author}
|
|
106
|
+
/>
|
|
107
|
+
)}
|
|
108
|
+
</FormField>
|
|
109
|
+
</div>
|
|
110
|
+
),
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const DefaultSelected: Story = {
|
|
114
|
+
name: "Default selected",
|
|
115
|
+
render: () => (
|
|
116
|
+
<div style={{ width: "300px" }}>
|
|
117
|
+
<FormField label="Pick a book">
|
|
118
|
+
{({ id }) => (
|
|
119
|
+
<SingleSelect
|
|
120
|
+
id={id}
|
|
121
|
+
data={books}
|
|
122
|
+
itemToString={itemToString}
|
|
123
|
+
renderItem={renderItem}
|
|
124
|
+
placeholder="Select a book"
|
|
125
|
+
defaultSelectedItemId="book-3"
|
|
126
|
+
/>
|
|
127
|
+
)}
|
|
128
|
+
</FormField>
|
|
129
|
+
</div>
|
|
130
|
+
),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const Controlled: Story = {
|
|
134
|
+
name: "Controlled",
|
|
135
|
+
render: () => {
|
|
136
|
+
const [selectedId, setSelectedId] = React.useState<string | null>("book-1");
|
|
137
|
+
const selected = books.find((b) => b.id === selectedId);
|
|
138
|
+
return (
|
|
139
|
+
<div
|
|
140
|
+
style={{
|
|
141
|
+
display: "flex",
|
|
142
|
+
flexDirection: "column",
|
|
143
|
+
gap: 16,
|
|
144
|
+
widows: "300px",
|
|
145
|
+
}}
|
|
146
|
+
>
|
|
147
|
+
<FormField label="Pick a book">
|
|
148
|
+
{({ id }) => (
|
|
149
|
+
<SingleSelect
|
|
150
|
+
id={id}
|
|
151
|
+
data={books}
|
|
152
|
+
itemToString={itemToString}
|
|
153
|
+
renderItem={renderItem}
|
|
154
|
+
placeholder="Select a book"
|
|
155
|
+
selectedItemId={selectedId}
|
|
156
|
+
onSelectedItemIdChange={(id: string | null) => setSelectedId(id)}
|
|
157
|
+
/>
|
|
158
|
+
)}
|
|
159
|
+
</FormField>
|
|
160
|
+
<div style={{ fontSize: 13 }}>
|
|
161
|
+
Selected: <strong>{selected?.title ?? "none"}</strong>
|
|
162
|
+
</div>
|
|
163
|
+
<div style={{ display: "flex", gap: 8 }}>
|
|
164
|
+
<button onClick={() => setSelectedId(null)}>Clear</button>
|
|
165
|
+
<button onClick={() => setSelectedId("book-5")}>Select 1984</button>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export const CustomSelection: Story = {
|
|
173
|
+
name: "Custom renderSelection",
|
|
174
|
+
render: () => (
|
|
175
|
+
<div style={{ width: "300px" }}>
|
|
176
|
+
<FormField label="Pick a book">
|
|
177
|
+
{({ id }) => (
|
|
178
|
+
<SingleSelect
|
|
179
|
+
id={id}
|
|
180
|
+
data={books}
|
|
181
|
+
itemToString={itemToString}
|
|
182
|
+
renderItem={renderItem}
|
|
183
|
+
placeholder="Select a book"
|
|
184
|
+
renderSelection={(item) => (
|
|
185
|
+
<span>
|
|
186
|
+
<span style={{ fontWeight: "bold" }}>{item.title}</span>
|
|
187
|
+
<span
|
|
188
|
+
style={{ fontSize: "11px", color: "#888", marginLeft: "6px" }}
|
|
189
|
+
>
|
|
190
|
+
by {item.author}
|
|
191
|
+
</span>
|
|
192
|
+
</span>
|
|
193
|
+
)}
|
|
194
|
+
/>
|
|
195
|
+
)}
|
|
196
|
+
</FormField>
|
|
197
|
+
</div>
|
|
198
|
+
),
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
export const Children: Story = {
|
|
202
|
+
name: "Children API",
|
|
203
|
+
render: () => (
|
|
204
|
+
<div style={{ width: "300px" }}>
|
|
205
|
+
<FormField label="Pick a book">
|
|
206
|
+
{({ id }) => (
|
|
207
|
+
<SingleSelect id={id} placeholder="Select a book">
|
|
208
|
+
<SelectGroup label="Tolstoy" showSeparator>
|
|
209
|
+
<SelectItem value="book-2" label="War and Peace" />
|
|
210
|
+
<SelectItem value="book-9" label="Anna Karenina" />
|
|
211
|
+
</SelectGroup>
|
|
212
|
+
<SelectGroup label="Dostoevsky" showSeparator>
|
|
213
|
+
<SelectItem value="book-3" label="The Idiot" />
|
|
214
|
+
<SelectItem value="book-8" label="The Brothers Karamazov" />
|
|
215
|
+
<SelectItem value="book-10" label="Crime and Punishment" />
|
|
216
|
+
</SelectGroup>
|
|
217
|
+
<SelectGroup label="Other">
|
|
218
|
+
<SelectItem value="book-1" label="To Kill a Mockingbird" />
|
|
219
|
+
<SelectItem value="book-5" label="1984" />
|
|
220
|
+
<SelectItem value="book-7" label="Meditations" />
|
|
221
|
+
</SelectGroup>
|
|
222
|
+
</SingleSelect>
|
|
223
|
+
)}
|
|
224
|
+
</FormField>
|
|
225
|
+
</div>
|
|
226
|
+
),
|
|
227
|
+
};
|