@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,304 @@
|
|
|
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
|
+
ComboboxInputGroup,
|
|
12
|
+
ComboboxInput,
|
|
13
|
+
ComboboxActionButtons,
|
|
14
|
+
ComboboxClear,
|
|
15
|
+
ComboboxTrigger,
|
|
16
|
+
ComboboxPositioner,
|
|
17
|
+
ComboboxPopup,
|
|
18
|
+
ComboboxList,
|
|
19
|
+
ComboboxEmpty,
|
|
20
|
+
ComboboxGroup,
|
|
21
|
+
ComboboxGroupLabel,
|
|
22
|
+
ComboboxSeparator,
|
|
23
|
+
} from "./Common/ComboboxStyles";
|
|
24
|
+
import { ClearIcon, StyledChevron, ChevronIcon } from "./Common/SelectIcons";
|
|
25
|
+
|
|
26
|
+
// ─── Props ────────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
interface SingleComboboxBaseProps {
|
|
29
|
+
id?: string;
|
|
30
|
+
placeholder?: string;
|
|
31
|
+
emptyText?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface SingleComboboxDataBaseProps<T extends DataWithId>
|
|
35
|
+
extends SingleComboboxBaseProps {
|
|
36
|
+
data: T[];
|
|
37
|
+
itemToString: (item: T | null) => string;
|
|
38
|
+
renderItem?: (item: T) => React.ReactNode;
|
|
39
|
+
inputType?: "icon" | "radio" | "checkbox" | "none";
|
|
40
|
+
selectedItemId?: T["id"] | null;
|
|
41
|
+
defaultSelectedItemId?: T["id"] | null;
|
|
42
|
+
onSelectedItemIdChange?: (id: T["id"] | null) => void;
|
|
43
|
+
children?: never;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface SingleComboboxDataProps<T extends DataWithId>
|
|
47
|
+
extends SingleComboboxDataBaseProps<T> {
|
|
48
|
+
isVirtualized?: false;
|
|
49
|
+
groupBy?: (item: T) => string;
|
|
50
|
+
renderGroupHeading?: (label: string) => React.ReactNode;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface SingleComboboxVirtualizedProps<T extends DataWithId>
|
|
54
|
+
extends SingleComboboxDataBaseProps<T> {
|
|
55
|
+
isVirtualized: true;
|
|
56
|
+
groupBy?: never;
|
|
57
|
+
renderGroupHeading?: never;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface SingleComboboxChildrenProps extends SingleComboboxBaseProps {
|
|
61
|
+
children: React.ReactNode;
|
|
62
|
+
selectedItemId?: string | number | null;
|
|
63
|
+
defaultSelectedItemId?: string | number | null;
|
|
64
|
+
onSelectedItemIdChange?: (id: string | number | null) => void;
|
|
65
|
+
data?: never;
|
|
66
|
+
itemToString?: never;
|
|
67
|
+
renderItem?: never;
|
|
68
|
+
inputType?: never;
|
|
69
|
+
groupBy?: never;
|
|
70
|
+
renderGroupHeading?: never;
|
|
71
|
+
isVirtualized?: never;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type SingleComboboxProps<T extends DataWithId> =
|
|
75
|
+
| SingleComboboxDataProps<T>
|
|
76
|
+
| SingleComboboxVirtualizedProps<T>
|
|
77
|
+
| SingleComboboxChildrenProps;
|
|
78
|
+
|
|
79
|
+
// ─── Component ────────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
export default function SingleCombobox<T extends DataWithId>(
|
|
82
|
+
props: SingleComboboxProps<T>
|
|
83
|
+
) {
|
|
84
|
+
const {
|
|
85
|
+
id,
|
|
86
|
+
placeholder = "Search...",
|
|
87
|
+
emptyText = "No results found.",
|
|
88
|
+
} = props;
|
|
89
|
+
|
|
90
|
+
const isChildrenMode = "children" in props && props.children != null;
|
|
91
|
+
|
|
92
|
+
type DataProps =
|
|
93
|
+
| SingleComboboxDataProps<T>
|
|
94
|
+
| SingleComboboxVirtualizedProps<T>;
|
|
95
|
+
|
|
96
|
+
const data = isChildrenMode ? ([] as T[]) : (props as DataProps).data;
|
|
97
|
+
const itemToString = isChildrenMode
|
|
98
|
+
? (_item: T | null) => ""
|
|
99
|
+
: (props as DataProps).itemToString;
|
|
100
|
+
const renderItem = isChildrenMode
|
|
101
|
+
? undefined
|
|
102
|
+
: (props as DataProps).renderItem;
|
|
103
|
+
const inputType = isChildrenMode
|
|
104
|
+
? "icon"
|
|
105
|
+
: (props as DataProps).inputType ?? "icon";
|
|
106
|
+
const isVirtualized = isChildrenMode
|
|
107
|
+
? false
|
|
108
|
+
: (props as DataProps).isVirtualized ?? false;
|
|
109
|
+
const groupBy = isChildrenMode
|
|
110
|
+
? undefined
|
|
111
|
+
: isVirtualized
|
|
112
|
+
? undefined
|
|
113
|
+
: (props as SingleComboboxDataProps<T>).groupBy;
|
|
114
|
+
const renderGroupHeading = isChildrenMode
|
|
115
|
+
? undefined
|
|
116
|
+
: isVirtualized
|
|
117
|
+
? undefined
|
|
118
|
+
: (props as SingleComboboxDataProps<T>).renderGroupHeading;
|
|
119
|
+
const children = isChildrenMode
|
|
120
|
+
? (props as SingleComboboxChildrenProps).children
|
|
121
|
+
: undefined;
|
|
122
|
+
|
|
123
|
+
const selectedItemId = props.selectedItemId;
|
|
124
|
+
const defaultSelectedItemId = props.defaultSelectedItemId;
|
|
125
|
+
const onSelectedItemIdChange = props.onSelectedItemIdChange;
|
|
126
|
+
|
|
127
|
+
const { containerRef, portalContainer } = useSeedsPortalContainer();
|
|
128
|
+
const [open, setOpen] = React.useState(false);
|
|
129
|
+
const virtualizerRef = React.useRef<ReturnType<
|
|
130
|
+
typeof useVirtualizer<HTMLDivElement, Element>
|
|
131
|
+
> | null>(null);
|
|
132
|
+
|
|
133
|
+
const isControlled = selectedItemId !== undefined;
|
|
134
|
+
|
|
135
|
+
const idToItem = React.useCallback(
|
|
136
|
+
(id: T["id"] | null | undefined): T | null => {
|
|
137
|
+
if (id == null) return null;
|
|
138
|
+
return data.find((d) => String(d.id) === String(id)) ?? null;
|
|
139
|
+
},
|
|
140
|
+
[data]
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const [internalValue, setInternalValue] = React.useState<T | null>(() =>
|
|
144
|
+
idToItem(defaultSelectedItemId as T["id"] | null)
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const value = isControlled
|
|
148
|
+
? idToItem(selectedItemId as T["id"] | null)
|
|
149
|
+
: internalValue;
|
|
150
|
+
|
|
151
|
+
function handleValueChange(newItem: T | null) {
|
|
152
|
+
if (!isControlled) {
|
|
153
|
+
setInternalValue(newItem);
|
|
154
|
+
}
|
|
155
|
+
if (onSelectedItemIdChange) {
|
|
156
|
+
onSelectedItemIdChange(newItem ? newItem.id : null);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const groups = React.useMemo(
|
|
161
|
+
() => (groupBy ? groupItems(data, groupBy) : null),
|
|
162
|
+
[data, groupBy]
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<Field>
|
|
167
|
+
<div ref={containerRef} style={{ position: "relative" }} />
|
|
168
|
+
<Combobox.Root
|
|
169
|
+
id={id}
|
|
170
|
+
value={value}
|
|
171
|
+
onValueChange={handleValueChange}
|
|
172
|
+
itemToStringLabel={itemToString}
|
|
173
|
+
isItemEqualToValue={(a, b) => a != null && b != null && a.id === b.id}
|
|
174
|
+
items={
|
|
175
|
+
isVirtualized ? data : groups ?? (isChildrenMode ? undefined : data)
|
|
176
|
+
}
|
|
177
|
+
virtualized={isVirtualized}
|
|
178
|
+
open={open}
|
|
179
|
+
onOpenChange={setOpen}
|
|
180
|
+
onItemHighlighted={
|
|
181
|
+
isVirtualized
|
|
182
|
+
? (item, { reason, index }) => {
|
|
183
|
+
const virt = virtualizerRef.current;
|
|
184
|
+
if (!item || !virt) return;
|
|
185
|
+
const isStart = index === 0;
|
|
186
|
+
const isEnd = index === virt.options.count - 1;
|
|
187
|
+
if (
|
|
188
|
+
reason === "none" ||
|
|
189
|
+
(reason === "keyboard" && (isStart || isEnd))
|
|
190
|
+
) {
|
|
191
|
+
queueMicrotask(() => {
|
|
192
|
+
virt.scrollToIndex(index, {
|
|
193
|
+
align: isEnd ? "start" : "end",
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
: undefined
|
|
199
|
+
}
|
|
200
|
+
>
|
|
201
|
+
<ComboboxInputGroup>
|
|
202
|
+
<ComboboxInput placeholder={placeholder} id={id} />
|
|
203
|
+
<ComboboxActionButtons>
|
|
204
|
+
<ComboboxClear aria-label="Clear selection">
|
|
205
|
+
<ClearIcon />
|
|
206
|
+
</ComboboxClear>
|
|
207
|
+
<ComboboxTrigger aria-label="Open popup">
|
|
208
|
+
<StyledChevron $isOpen={open}>
|
|
209
|
+
<ChevronIcon />
|
|
210
|
+
</StyledChevron>
|
|
211
|
+
</ComboboxTrigger>
|
|
212
|
+
</ComboboxActionButtons>
|
|
213
|
+
</ComboboxInputGroup>
|
|
214
|
+
|
|
215
|
+
<Combobox.Portal container={portalContainer}>
|
|
216
|
+
<ComboboxPositioner sideOffset={4}>
|
|
217
|
+
<ComboboxPopup
|
|
218
|
+
style={
|
|
219
|
+
isVirtualized
|
|
220
|
+
? { display: "flex", flexDirection: "column" }
|
|
221
|
+
: undefined
|
|
222
|
+
}
|
|
223
|
+
>
|
|
224
|
+
<ComboboxEmpty>{emptyText}</ComboboxEmpty>
|
|
225
|
+
<ComboboxList
|
|
226
|
+
style={
|
|
227
|
+
isVirtualized
|
|
228
|
+
? {
|
|
229
|
+
padding: 0,
|
|
230
|
+
flex: 1,
|
|
231
|
+
minHeight: 0,
|
|
232
|
+
display: "flex",
|
|
233
|
+
flexDirection: "column",
|
|
234
|
+
}
|
|
235
|
+
: undefined
|
|
236
|
+
}
|
|
237
|
+
>
|
|
238
|
+
{isVirtualized ? (
|
|
239
|
+
<VirtualizedComboboxList
|
|
240
|
+
open={open}
|
|
241
|
+
virtualizerRef={virtualizerRef}
|
|
242
|
+
value={value}
|
|
243
|
+
groups={groups}
|
|
244
|
+
data={data}
|
|
245
|
+
itemToString={itemToString}
|
|
246
|
+
inputType={inputType}
|
|
247
|
+
renderItem={renderItem}
|
|
248
|
+
renderGroupHeading={renderGroupHeading}
|
|
249
|
+
/>
|
|
250
|
+
) : children ? (
|
|
251
|
+
children
|
|
252
|
+
) : groups ? (
|
|
253
|
+
(group: { value: string; items: T[] }, index: number) => (
|
|
254
|
+
<React.Fragment key={group.value}>
|
|
255
|
+
<ComboboxGroup items={group.items}>
|
|
256
|
+
<ComboboxGroupLabel>
|
|
257
|
+
{renderGroupHeading
|
|
258
|
+
? renderGroupHeading(group.value)
|
|
259
|
+
: group.value}
|
|
260
|
+
</ComboboxGroupLabel>
|
|
261
|
+
<Combobox.Collection>
|
|
262
|
+
{(item: T) => (
|
|
263
|
+
<ComboboxItem
|
|
264
|
+
key={item.id}
|
|
265
|
+
value={item}
|
|
266
|
+
itemId={String(item.id)}
|
|
267
|
+
label={itemToString(item)}
|
|
268
|
+
inputType={inputType}
|
|
269
|
+
renderItem={
|
|
270
|
+
renderItem
|
|
271
|
+
? () => renderItem(item)
|
|
272
|
+
: () => itemToString(item)
|
|
273
|
+
}
|
|
274
|
+
/>
|
|
275
|
+
)}
|
|
276
|
+
</Combobox.Collection>
|
|
277
|
+
</ComboboxGroup>
|
|
278
|
+
{index < groups.length - 1 && <ComboboxSeparator />}
|
|
279
|
+
</React.Fragment>
|
|
280
|
+
)
|
|
281
|
+
) : (
|
|
282
|
+
(item: T) => (
|
|
283
|
+
<ComboboxItem
|
|
284
|
+
key={item.id}
|
|
285
|
+
value={item}
|
|
286
|
+
itemId={String(item.id)}
|
|
287
|
+
label={itemToString(item)}
|
|
288
|
+
inputType={inputType}
|
|
289
|
+
renderItem={
|
|
290
|
+
renderItem
|
|
291
|
+
? () => renderItem(item)
|
|
292
|
+
: () => itemToString(item)
|
|
293
|
+
}
|
|
294
|
+
/>
|
|
295
|
+
)
|
|
296
|
+
)}
|
|
297
|
+
</ComboboxList>
|
|
298
|
+
</ComboboxPopup>
|
|
299
|
+
</ComboboxPositioner>
|
|
300
|
+
</Combobox.Portal>
|
|
301
|
+
</Combobox.Root>
|
|
302
|
+
</Field>
|
|
303
|
+
);
|
|
304
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import { FormField } from "@sproutsocial/seeds-react-form-field";
|
|
4
|
+
import SingleSearchableSelect from "./SingleSearchableSelect";
|
|
5
|
+
import { books, largeBookSet, itemToString, renderItem } from "./storyData";
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof SingleSearchableSelect> = {
|
|
8
|
+
title: "Really Under Development/V3/Single Searchable Select",
|
|
9
|
+
component: SingleSearchableSelect,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
type Story = StoryObj<typeof SingleSearchableSelect>;
|
|
14
|
+
|
|
15
|
+
export const Basic: Story = {
|
|
16
|
+
name: "Basic",
|
|
17
|
+
render: () => (
|
|
18
|
+
<div style={{ width: "300px" }}>
|
|
19
|
+
<FormField label="Pick a book">
|
|
20
|
+
{({ id }) => (
|
|
21
|
+
<SingleSearchableSelect
|
|
22
|
+
id={id}
|
|
23
|
+
data={books}
|
|
24
|
+
itemToString={itemToString}
|
|
25
|
+
placeholder="Select a book"
|
|
26
|
+
searchPlaceholder="Search books..."
|
|
27
|
+
/>
|
|
28
|
+
)}
|
|
29
|
+
</FormField>
|
|
30
|
+
</div>
|
|
31
|
+
),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const CustomRenderItem: Story = {
|
|
35
|
+
name: "Custom render item",
|
|
36
|
+
render: () => (
|
|
37
|
+
<div style={{ width: "300px" }}>
|
|
38
|
+
<FormField label="Pick a book">
|
|
39
|
+
{({ id }) => (
|
|
40
|
+
<SingleSearchableSelect
|
|
41
|
+
id={id}
|
|
42
|
+
data={books}
|
|
43
|
+
itemToString={itemToString}
|
|
44
|
+
renderItem={renderItem}
|
|
45
|
+
placeholder="Select a book"
|
|
46
|
+
searchPlaceholder="Search books..."
|
|
47
|
+
/>
|
|
48
|
+
)}
|
|
49
|
+
</FormField>
|
|
50
|
+
</div>
|
|
51
|
+
),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const Grouped: Story = {
|
|
55
|
+
name: "Grouped",
|
|
56
|
+
render: () => (
|
|
57
|
+
<div style={{ width: "300px" }}>
|
|
58
|
+
<FormField label="Pick a book">
|
|
59
|
+
{({ id }) => (
|
|
60
|
+
<SingleSearchableSelect
|
|
61
|
+
id={id}
|
|
62
|
+
data={books}
|
|
63
|
+
itemToString={itemToString}
|
|
64
|
+
renderItem={renderItem}
|
|
65
|
+
placeholder="Select a book"
|
|
66
|
+
searchPlaceholder="Search books..."
|
|
67
|
+
groupBy={(book) => book.author}
|
|
68
|
+
/>
|
|
69
|
+
)}
|
|
70
|
+
</FormField>
|
|
71
|
+
</div>
|
|
72
|
+
),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const DefaultSelected: Story = {
|
|
76
|
+
name: "Default selected",
|
|
77
|
+
render: () => (
|
|
78
|
+
<div style={{ width: "300px" }}>
|
|
79
|
+
<FormField label="Pick a book">
|
|
80
|
+
{({ id }) => (
|
|
81
|
+
<SingleSearchableSelect
|
|
82
|
+
id={id}
|
|
83
|
+
data={books}
|
|
84
|
+
itemToString={itemToString}
|
|
85
|
+
renderItem={renderItem}
|
|
86
|
+
placeholder="Select a book"
|
|
87
|
+
searchPlaceholder="Search books..."
|
|
88
|
+
defaultSelectedItemId="book-3"
|
|
89
|
+
/>
|
|
90
|
+
)}
|
|
91
|
+
</FormField>
|
|
92
|
+
</div>
|
|
93
|
+
),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const Controlled: Story = {
|
|
97
|
+
name: "Controlled",
|
|
98
|
+
render: () => {
|
|
99
|
+
const [selectedId, setSelectedId] = React.useState<string | null>("book-1");
|
|
100
|
+
const selected = books.find((b) => b.id === selectedId);
|
|
101
|
+
return (
|
|
102
|
+
<div
|
|
103
|
+
style={{
|
|
104
|
+
width: "300px",
|
|
105
|
+
display: "flex",
|
|
106
|
+
flexDirection: "column",
|
|
107
|
+
gap: 16,
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
<FormField label="Pick a book">
|
|
111
|
+
{({ id }) => (
|
|
112
|
+
<SingleSearchableSelect
|
|
113
|
+
id={id}
|
|
114
|
+
data={books}
|
|
115
|
+
itemToString={itemToString}
|
|
116
|
+
renderItem={renderItem}
|
|
117
|
+
placeholder="Select a book"
|
|
118
|
+
searchPlaceholder="Search books..."
|
|
119
|
+
selectedItemId={selectedId}
|
|
120
|
+
onSelectedItemIdChange={(id: string | null) => setSelectedId(id)}
|
|
121
|
+
/>
|
|
122
|
+
)}
|
|
123
|
+
</FormField>
|
|
124
|
+
<div style={{ fontSize: 13 }}>
|
|
125
|
+
Selected: <strong>{selected?.title ?? "none"}</strong>
|
|
126
|
+
</div>
|
|
127
|
+
<div style={{ display: "flex", gap: 8 }}>
|
|
128
|
+
<button onClick={() => setSelectedId(null)}>Clear</button>
|
|
129
|
+
<button onClick={() => setSelectedId("book-5")}>Select 1984</button>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const CustomSelection: Story = {
|
|
137
|
+
name: "Custom renderSelection",
|
|
138
|
+
render: () => (
|
|
139
|
+
<div style={{ width: "300px" }}>
|
|
140
|
+
<FormField label="Pick a book">
|
|
141
|
+
{({ id }) => (
|
|
142
|
+
<SingleSearchableSelect
|
|
143
|
+
id={id}
|
|
144
|
+
data={books}
|
|
145
|
+
itemToString={itemToString}
|
|
146
|
+
renderItem={renderItem}
|
|
147
|
+
placeholder="Select a book"
|
|
148
|
+
searchPlaceholder="Search books..."
|
|
149
|
+
renderSelection={(item) => (
|
|
150
|
+
<span>
|
|
151
|
+
<span style={{ fontWeight: "bold" }}>{item.title}</span>
|
|
152
|
+
<span
|
|
153
|
+
style={{ fontSize: "11px", color: "#888", marginLeft: "6px" }}
|
|
154
|
+
>
|
|
155
|
+
by {item.author}
|
|
156
|
+
</span>
|
|
157
|
+
</span>
|
|
158
|
+
)}
|
|
159
|
+
/>
|
|
160
|
+
)}
|
|
161
|
+
</FormField>
|
|
162
|
+
</div>
|
|
163
|
+
),
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export const Virtualized: Story = {
|
|
167
|
+
name: "Virtualized",
|
|
168
|
+
render: () => (
|
|
169
|
+
<div style={{ width: "300px" }}>
|
|
170
|
+
<FormField label="Pick a book">
|
|
171
|
+
{({ id }) => (
|
|
172
|
+
<SingleSearchableSelect
|
|
173
|
+
id={id}
|
|
174
|
+
data={largeBookSet}
|
|
175
|
+
itemToString={itemToString}
|
|
176
|
+
renderItem={renderItem}
|
|
177
|
+
placeholder="Select a book"
|
|
178
|
+
searchPlaceholder="Search books..."
|
|
179
|
+
isVirtualized
|
|
180
|
+
/>
|
|
181
|
+
)}
|
|
182
|
+
</FormField>
|
|
183
|
+
</div>
|
|
184
|
+
),
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export const Loading: Story = {
|
|
188
|
+
name: "Loading",
|
|
189
|
+
render: () => (
|
|
190
|
+
<div style={{ width: "300px" }}>
|
|
191
|
+
<FormField label="Pick a book">
|
|
192
|
+
{({ id }) => (
|
|
193
|
+
<SingleSearchableSelect
|
|
194
|
+
id={id}
|
|
195
|
+
data={[]}
|
|
196
|
+
itemToString={itemToString}
|
|
197
|
+
placeholder="Select a book"
|
|
198
|
+
searchPlaceholder="Search books..."
|
|
199
|
+
isLoading
|
|
200
|
+
/>
|
|
201
|
+
)}
|
|
202
|
+
</FormField>
|
|
203
|
+
</div>
|
|
204
|
+
),
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export const LoadingWithToggle: Story = {
|
|
208
|
+
name: "Loading (toggle)",
|
|
209
|
+
render: () => {
|
|
210
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
211
|
+
const data = isLoading ? [] : books;
|
|
212
|
+
return (
|
|
213
|
+
<div
|
|
214
|
+
style={{
|
|
215
|
+
width: "300px",
|
|
216
|
+
display: "flex",
|
|
217
|
+
flexDirection: "column",
|
|
218
|
+
gap: 16,
|
|
219
|
+
}}
|
|
220
|
+
>
|
|
221
|
+
<FormField label="Pick a book">
|
|
222
|
+
{({ id }) => (
|
|
223
|
+
<SingleSearchableSelect
|
|
224
|
+
id={id}
|
|
225
|
+
data={data}
|
|
226
|
+
itemToString={itemToString}
|
|
227
|
+
placeholder="Select a book"
|
|
228
|
+
searchPlaceholder="Search books..."
|
|
229
|
+
isLoading={isLoading}
|
|
230
|
+
/>
|
|
231
|
+
)}
|
|
232
|
+
</FormField>
|
|
233
|
+
<button onClick={() => setIsLoading((v) => !v)}>
|
|
234
|
+
{isLoading ? "Finish loading" : "Set loading"}
|
|
235
|
+
</button>
|
|
236
|
+
</div>
|
|
237
|
+
);
|
|
238
|
+
},
|
|
239
|
+
};
|