@sproutsocial/seeds-react-menu 1.12.0 → 1.12.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutsocial/seeds-react-menu",
3
- "version": "1.12.0",
3
+ "version": "1.12.2",
4
4
  "description": "Seeds React Menu",
5
5
  "author": "Sprout Social, Inc.",
6
6
  "license": "MIT",
@@ -37,17 +37,17 @@
37
37
  "dependencies": {
38
38
  "@radix-ui/react-popover": "^1.1.2",
39
39
  "@sproutsocial/seeds-react-box": "^1.1.16",
40
- "@sproutsocial/seeds-react-button": "^2.0.4",
41
- "@sproutsocial/seeds-react-checkbox": "^1.3.32",
42
- "@sproutsocial/seeds-react-icon": "^2.3.6",
43
- "@sproutsocial/seeds-react-input": "^1.5.15",
40
+ "@sproutsocial/seeds-react-button": "^2.0.5",
41
+ "@sproutsocial/seeds-react-checkbox": "^1.3.33",
42
+ "@sproutsocial/seeds-react-icon": "^2.3.7",
43
+ "@sproutsocial/seeds-react-input": "^1.5.16",
44
44
  "@sproutsocial/seeds-react-label": "^1.0.16",
45
- "@sproutsocial/seeds-react-popout": "^2.4.37",
45
+ "@sproutsocial/seeds-react-popout": "^2.4.38",
46
46
  "@sproutsocial/seeds-react-radio": "^1.3.16",
47
- "@sproutsocial/seeds-react-switch": "^1.2.31",
47
+ "@sproutsocial/seeds-react-switch": "^1.2.32",
48
48
  "@sproutsocial/seeds-react-system-props": "^3.0.2",
49
49
  "@sproutsocial/seeds-react-theme": "^3.6.2",
50
- "@sproutsocial/seeds-react-token-input": "^1.4.37",
50
+ "@sproutsocial/seeds-react-token-input": "^1.4.38",
51
51
  "@sproutsocial/seeds-react-utilities": "^4.3.0",
52
52
  "@base-ui/react": "^1.3.0",
53
53
  "@tanstack/react-virtual": "^3.13.12",
@@ -57,7 +57,7 @@
57
57
  "styled-system": "^5.1.5"
58
58
  },
59
59
  "devDependencies": {
60
- "@sproutsocial/seeds-react-tooltip": "^1.1.19",
60
+ "@sproutsocial/seeds-react-tooltip": "^1.1.20",
61
61
  "@sproutsocial/eslint-config-seeds": "*",
62
62
  "@sproutsocial/seeds-react-testing-library": "*",
63
63
  "@sproutsocial/seeds-testing": "*",
@@ -8,7 +8,11 @@ import {
8
8
  ComboboxSelectAllItem,
9
9
  } from "./ComboboxStyles";
10
10
  import type { DataWithId } from "./types";
11
- import { isPlaceholderSentinel } from "./sentinels";
11
+ import {
12
+ isPlaceholderSentinel,
13
+ isCreatableSentinel,
14
+ type CreatableSentinel,
15
+ } from "./sentinels";
12
16
 
13
17
  export const ITEM_HEIGHT = 32;
14
18
 
@@ -29,7 +33,11 @@ export function isSelectAllSentinel(v: unknown): v is SelectAllSentinel {
29
33
  return typeof v === "object" && v !== null && "__selectAll" in v;
30
34
  }
31
35
 
32
- export type FlatSentinelRow<T> = T | GroupHeaderSentinel | SelectAllSentinel;
36
+ export type FlatSentinelRow<T> =
37
+ | T
38
+ | GroupHeaderSentinel
39
+ | SelectAllSentinel
40
+ | CreatableSentinel;
33
41
 
34
42
  interface VirtualizedComboboxListProps<T extends DataWithId> {
35
43
  open: boolean;
@@ -47,6 +55,12 @@ interface VirtualizedComboboxListProps<T extends DataWithId> {
47
55
  inputType: "icon" | "radio" | "checkbox" | "none";
48
56
  renderItem?: (item: T) => React.ReactNode;
49
57
  renderGroupHeading?: (label: string) => React.ReactNode;
58
+ renderCreatableItem?: (
59
+ sentinel: CreatableSentinel,
60
+ style: React.CSSProperties,
61
+ index: number,
62
+ measureRef: (el: Element | null) => void
63
+ ) => React.ReactNode;
50
64
  }
51
65
 
52
66
  export default function VirtualizedComboboxList<T extends DataWithId>({
@@ -59,6 +73,7 @@ export default function VirtualizedComboboxList<T extends DataWithId>({
59
73
  inputType,
60
74
  renderItem,
61
75
  renderGroupHeading,
76
+ renderCreatableItem,
62
77
  }: VirtualizedComboboxListProps<T>) {
63
78
  type Row = FlatSentinelRow<T>;
64
79
 
@@ -197,6 +212,18 @@ export default function VirtualizedComboboxList<T extends DataWithId>({
197
212
  );
198
213
  }
199
214
 
215
+ if (isCreatableSentinel(row)) {
216
+ if (renderCreatableItem) {
217
+ return renderCreatableItem(
218
+ row,
219
+ baseStyle,
220
+ virtualItem.index,
221
+ virtualizer.measureElement
222
+ );
223
+ }
224
+ return null;
225
+ }
226
+
200
227
  const item = row as T;
201
228
  return (
202
229
  <ComboboxItem
@@ -15,3 +15,16 @@ export function makePlaceholderSentinel(label: string): PlaceholderSentinel {
15
15
  export function isPlaceholderSentinel(v: unknown): v is PlaceholderSentinel {
16
16
  return typeof v === "object" && v !== null && "__placeholder" in v;
17
17
  }
18
+
19
+ export interface CreatableSentinel {
20
+ __creatable: true;
21
+ label: string;
22
+ }
23
+
24
+ export function makeCreatableSentinel(label: string): CreatableSentinel {
25
+ return { __creatable: true, label };
26
+ }
27
+
28
+ export function isCreatableSentinel(v: unknown): v is CreatableSentinel {
29
+ return typeof v === "object" && v !== null && "__creatable" in v;
30
+ }
@@ -8,6 +8,7 @@ import {
8
8
  largeBookSet,
9
9
  itemToString,
10
10
  renderItem,
11
+ type Book,
11
12
  } from "./storyData";
12
13
 
13
14
  const meta: Meta<typeof MultiCombobox> = {
@@ -99,6 +100,7 @@ export const Controlled: Story = {
99
100
  name: "Controlled",
100
101
  render: () => {
101
102
  const [selectedIds, setSelectedIds] = React.useState<string[]>(["book-2"]);
103
+ const [inputValue, setInputValue] = React.useState("");
102
104
  const selected = books.filter((b) => selectedIds.includes(b.id));
103
105
  return (
104
106
  <div
@@ -119,9 +121,14 @@ export const Controlled: Story = {
119
121
  placeholder="Search books..."
120
122
  selectedItemIds={selectedIds}
121
123
  onSelectedItemIdsChange={(ids: string[]) => setSelectedIds(ids)}
124
+ inputValue={inputValue}
125
+ onInputValueChange={setInputValue}
122
126
  />
123
127
  )}
124
128
  </FormField>
129
+ <div style={{ fontSize: 13 }}>
130
+ Input: <strong>"{inputValue}"</strong>
131
+ </div>
125
132
  <div style={{ fontSize: 13 }}>
126
133
  Selected:{" "}
127
134
  <strong>
@@ -130,11 +137,14 @@ export const Controlled: Story = {
130
137
  : "none"}
131
138
  </strong>
132
139
  </div>
133
- <div style={{ display: "flex", gap: 8 }}>
140
+ <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
134
141
  <button onClick={() => setSelectedIds([])}>Clear all</button>
135
142
  <button onClick={() => setSelectedIds(["book-5", "book-7"])}>
136
143
  Select 1984 &amp; Meditations
137
144
  </button>
145
+ <button onClick={() => setInputValue("War")}>
146
+ Set input to "War"
147
+ </button>
138
148
  </div>
139
149
  </div>
140
150
  );
@@ -182,6 +192,27 @@ export const SelectableHeadings: Story = {
182
192
 
183
193
  export const SelectAll: Story = {
184
194
  name: "Select all",
195
+ render: () => (
196
+ <div style={{ width: "600px" }}>
197
+ <FormField label="Pick books">
198
+ {({ id }) => (
199
+ <MultiCombobox
200
+ id={id}
201
+ data={books}
202
+ itemToString={itemToString}
203
+ renderItem={renderItem}
204
+ placeholder="Search books..."
205
+ selectHeadings
206
+ selectAll
207
+ />
208
+ )}
209
+ </FormField>
210
+ </div>
211
+ ),
212
+ };
213
+
214
+ export const SelectAllWithGrouping: Story = {
215
+ name: "Select all with grouping",
185
216
  render: () => (
186
217
  <div style={{ width: "600px" }}>
187
218
  <FormField label="Pick books">
@@ -202,6 +233,26 @@ export const SelectAll: Story = {
202
233
  ),
203
234
  };
204
235
 
236
+ export const SelectAllLocalized: Story = {
237
+ name: "Select all (localized label)",
238
+ render: () => (
239
+ <div style={{ width: "600px" }}>
240
+ <FormField label="Pick books">
241
+ {({ id }) => (
242
+ <MultiCombobox
243
+ id={id}
244
+ data={books}
245
+ itemToString={itemToString}
246
+ renderItem={renderItem}
247
+ placeholder="Search books..."
248
+ selectAll="Tout sélectionner"
249
+ />
250
+ )}
251
+ </FormField>
252
+ </div>
253
+ ),
254
+ };
255
+
205
256
  export const Virtualized: Story = {
206
257
  name: "Virtualized",
207
258
  render: () => (
@@ -371,3 +422,36 @@ export const CustomSearch: Story = {
371
422
  </div>
372
423
  ),
373
424
  };
425
+
426
+ export const Creatable: Story = {
427
+ name: "Creatable",
428
+ render: () => {
429
+ const [bookList, setBookList] = React.useState<Book[]>(books);
430
+
431
+ return (
432
+ <div style={{ width: "300px" }}>
433
+ <FormField label="Pick or create books">
434
+ {({ id }) => (
435
+ <MultiCombobox
436
+ id={id}
437
+ data={bookList}
438
+ itemToString={itemToString}
439
+ placeholder="Search or create..."
440
+ isVirtualized
441
+ creatable
442
+ onCreate={(value) => {
443
+ const newBook: Book = {
444
+ id: `book-custom-${value.toLowerCase().replace(/\s+/g, "-")}`,
445
+ title: value,
446
+ author: "Unknown",
447
+ };
448
+ setBookList((prev) => [...prev, newBook]);
449
+ return newBook;
450
+ }}
451
+ />
452
+ )}
453
+ </FormField>
454
+ </div>
455
+ );
456
+ },
457
+ };