@sproutsocial/seeds-react-menu 1.11.1 → 1.11.3

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.
@@ -42,6 +42,7 @@ const Placeholder = styled.div`
42
42
  interface MultiSelectBaseProps {
43
43
  id?: string;
44
44
  placeholder?: string;
45
+ disabled?: boolean;
45
46
  }
46
47
 
47
48
  interface MultiSelectDataProps<T extends DataWithId>
@@ -79,234 +80,13 @@ interface MultiSelectChildrenProps extends MultiSelectBaseProps {
79
80
  removeSelectedItems?: never;
80
81
  }
81
82
 
82
- /*
83
- 'use client';
84
- import * as React from 'react';
85
- import { Combobox } from '@base-ui/react/combobox';
86
-
87
- export default function ExampleAsyncMultipleCombobox() {
88
- const id = React.useId();
89
-
90
- const [searchResults, setSearchResults] = React.useState<DirectoryUser[]>([]);
91
- const [selectedValues, setSelectedValues] = React.useState<DirectoryUser[]>([]);
92
- const [searchValue, setSearchValue] = React.useState('');
93
- const [error, setError] = React.useState<string | null>(null);
94
- const [blockStartStatus, setBlockStartStatus] = React.useState(false);
95
-
96
- const [isPending, startTransition] = React.useTransition();
97
-
98
- const { contains } = Combobox.useFilter();
99
-
100
- const abortControllerRef = React.useRef<AbortController | null>(null);
101
- const selectedValuesRef = React.useRef<DirectoryUser[]>([]);
102
-
103
- const trimmedSearchValue = searchValue.trim();
104
-
105
- const items = React.useMemo(() => {
106
- if (selectedValues.length === 0) {
107
- return searchResults;
108
- }
109
-
110
- const merged = [...searchResults];
111
-
112
- selectedValues.forEach((user) => {
113
- if (!searchResults.some((result) => result.id === user.id)) {
114
- merged.push(user);
115
- }
116
- });
117
-
118
- return merged;
119
- }, [searchResults, selectedValues]);
120
-
121
- function getStatus() {
122
- if (isPending) {
123
- return (
124
- <React.Fragment>
125
- <span
126
- aria-hidden
127
- className="inline-block size-3 animate-[spin_0.75s_linear_infinite] rounded-full border border-current border-r-transparent rtl:border-r-current rtl:border-l-transparent"
128
- />
129
- Searching…
130
- </React.Fragment>
131
- );
132
- }
133
-
134
- if (error) {
135
- return error;
136
- }
137
-
138
- if (trimmedSearchValue === '' && !blockStartStatus) {
139
- return selectedValues.length > 0 ? null : 'Start typing to search people…';
140
- }
141
-
142
- if (searchResults.length === 0 && !blockStartStatus) {
143
- return `No matches for "${trimmedSearchValue}".`;
144
- }
145
-
146
- return null;
147
- }
148
-
149
- function getEmptyMessage() {
150
- if (trimmedSearchValue === '' || isPending || searchResults.length > 0 || error) {
151
- return null;
152
- }
153
-
154
- return 'Try a different search term.';
155
- }
156
-
157
- const status = getStatus();
158
- const emptyMessage = getEmptyMessage();
159
-
160
- return (
161
- <Combobox.Root
162
- items={items}
163
- itemToStringLabel={(user: DirectoryUser) => user.name}
164
- multiple
165
- filter={null}
166
- onOpenChangeComplete={(open) => {
167
- if (!open) {
168
- setSearchResults(selectedValuesRef.current);
169
- setBlockStartStatus(false);
170
- }
171
- }}
172
- onValueChange={(nextSelectedValues) => {
173
- selectedValuesRef.current = nextSelectedValues;
174
- setSelectedValues(nextSelectedValues);
175
- setSearchValue('');
176
- setError(null);
177
-
178
- if (nextSelectedValues.length === 0) {
179
- setSearchResults([]);
180
- setBlockStartStatus(false);
181
- } else {
182
- setBlockStartStatus(true);
183
- }
184
- }}
185
- onInputValueChange={(nextSearchValue, { reason }) => {
186
- setSearchValue(nextSearchValue);
187
-
188
- const controller = new AbortController();
189
- abortControllerRef.current?.abort();
190
- abortControllerRef.current = controller;
191
-
192
- if (nextSearchValue === '') {
193
- setSearchResults(selectedValuesRef.current);
194
- setError(null);
195
- setBlockStartStatus(false);
196
- return;
197
- }
198
-
199
- if (reason === 'item-press') {
200
- return;
201
- }
202
- startTransition(async () => {
203
- setError(null);
204
-
205
- const result = await searchUsers(nextSearchValue, contains);
206
-
207
- if (controller.signal.aborted) {
208
- return;
209
- }
210
-
211
- startTransition(() => {
212
- setSearchResults(result.users);
213
- setError(result.error);
214
- });
215
- });
216
- }}
217
- >
218
- <div className="flex flex-col gap-1 text-sm text-gray-900">
219
- <label className="inline-flex text-inherit font-bold" htmlFor={id}>
220
- Assign reviewers
221
- </label>
222
- <Combobox.InputGroup className="relative flex min-h-10 w-[16rem] cursor-text rounded-md border border-gray-200 bg-[canvas] px-1.5 py-1 focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-blue-800 md:w-[20rem]">
223
- <Combobox.Chips className="flex w-full flex-wrap items-center gap-1">
224
- <Combobox.Value>
225
- {(value: DirectoryUser[]) => (
226
- <React.Fragment>
227
- {value.map((user) => (
228
- <Combobox.Chip
229
- key={user.id}
230
- className="flex cursor-default items-center gap-1 rounded-md bg-gray-100 py-1 pl-2 pr-1 text-sm text-gray-900 outline-none focus-within:bg-blue-800 focus-within:text-gray-50 [@media(hover:hover)]:[&[data-highlighted]]:bg-blue-800 [@media(hover:hover)]:[&[data-highlighted]]:text-gray-50"
231
- aria-label={user.name}
232
- >
233
- {user.name}
234
- <Combobox.ChipRemove
235
- className="inline-flex items-center justify-center rounded-md border-none bg-transparent p-[0.2rem] text-inherit hover:bg-gray-200"
236
- aria-label={`Remove ${user.name}`}
237
- >
238
- <XIcon />
239
- </Combobox.ChipRemove>
240
- </Combobox.Chip>
241
- ))}
242
- <Combobox.Input
243
- id={id}
244
- placeholder={value.length > 0 ? '' : 'e.g. Michael'}
245
- className="h-8 min-w-24 flex-1 rounded-md border-0 bg-transparent pl-2 text-base font-normal text-gray-900 outline-none placeholder:font-normal"
246
- />
247
- </React.Fragment>
248
- )}
249
- </Combobox.Value>
250
- </Combobox.Chips>
251
- </Combobox.InputGroup>
252
- </div>
253
-
254
- <Combobox.Portal>
255
- <Combobox.Positioner className="outline-none" sideOffset={4}>
256
- <Combobox.Popup
257
- className="box-border w-[var(--anchor-width)] max-h-[min(var(--available-height),23rem)] max-w-[var(--available-width)] origin-[var(--transform-origin)] overflow-y-auto scroll-pb-2 scroll-pt-2 overscroll-contain rounded-md bg-[canvas] py-2 text-gray-900 shadow-[0_10px_15px_-3px_var(--color-gray-200),0_4px_6px_-4px_var(--color-gray-200)] outline outline-1 outline-gray-200 transition-[opacity,transform,scale] duration-100 data-[ending-style]:transition-none data-[starting-style]:scale-95 data-[starting-style]:opacity-0 dark:-outline-offset-1 dark:shadow-none dark:outline-gray-300"
258
- aria-busy={isPending || undefined}
259
- >
260
- <Combobox.Status>
261
- {status ? (
262
- <div className="flex items-center gap-2 py-1 pl-4 pr-5 text-sm text-gray-600">
263
- {status}
264
- </div>
265
- ) : null}
266
- </Combobox.Status>
267
- <Combobox.Empty>
268
- {emptyMessage ? (
269
- <div className="box-border px-4 py-2 text-sm leading-4 text-gray-600">
270
- {emptyMessage}
271
- </div>
272
- ) : null}
273
- </Combobox.Empty>
274
- <Combobox.List>
275
- {(user: DirectoryUser) => (
276
- <Combobox.Item
277
- key={user.id}
278
- value={user}
279
- className="grid cursor-default select-none grid-cols-[0.75rem_1fr] items-start gap-2 py-2 pl-4 pr-5 text-base leading-[1.2rem] outline-none [@media(hover:hover)]:[&[data-highlighted]]:relative [@media(hover:hover)]:[&[data-highlighted]]:z-0 [@media(hover:hover)]:[&[data-highlighted]]:text-gray-900 [@media(hover:hover)]:[&[data-highlighted]]:before:absolute [@media(hover:hover)]:[&[data-highlighted]]:before:inset-y-0 [@media(hover:hover)]:[&[data-highlighted]]:before:inset-x-2 [@media(hover:hover)]:[&[data-highlighted]]:before:z-[-1] [@media(hover:hover)]:[&[data-highlighted]]:before:rounded [@media(hover:hover)]:[&[data-highlighted]]:before:bg-gray-100 [@media(hover:hover)]:[&[data-highlighted]]:before:content-['']"
280
- >
281
- <Combobox.ItemIndicator className="col-start-1 mt-1">
282
- <CheckIcon className="size-3" />
283
- </Combobox.ItemIndicator>
284
- <span className="col-start-2 flex flex-col gap-1">
285
- <span className="text-[0.95rem] font-bold">{user.name}</span>
286
- <span className="flex flex-wrap gap-2 text-[0.8125rem] text-gray-600">
287
- <span className="opacity-80">@{user.username}</span>
288
- <span>{user.title}</span>
289
- </span>
290
- <span className="text-xs text-gray-500">{user.email}</span>
291
- </span>
292
- </Combobox.Item>
293
- )}
294
- </Combobox.List>
295
- </Combobox.Popup>
296
- </Combobox.Positioner>
297
- </Combobox.Portal>
298
- </Combobox.Root>
299
- */
300
-
301
83
  export type MultiSelectProps<T extends DataWithId> =
302
84
  | MultiSelectDataProps<T>
303
85
  | MultiSelectChildrenProps;
304
86
 
305
87
  // ─── Component ────────────────────────────────────────────────────────────────
306
88
 
307
- export default function MultiSelect<T extends DataWithId>(
308
- props: MultiSelectProps<T>
309
- ) {
89
+ export function MultiSelect<T extends DataWithId>(props: MultiSelectProps<T>) {
310
90
  const { id, placeholder = "Select..." } = props;
311
91
 
312
92
  const isChildrenMode = "children" in props && props.children != null;
@@ -396,6 +176,7 @@ export default function MultiSelect<T extends DataWithId>(
396
176
  items={items}
397
177
  value={value}
398
178
  onValueChange={handleValueChange}
179
+ disabled={props.disabled}
399
180
  id={id}
400
181
  >
401
182
  <SelectTrigger>
@@ -457,6 +238,7 @@ export default function MultiSelect<T extends DataWithId>(
457
238
  key={item.id}
458
239
  value={String(item.id)}
459
240
  label={itemToString(item)}
241
+ disabled={item.disabled}
460
242
  inputType={inputType}
461
243
  hidden={
462
244
  removeSelectedItems &&
@@ -476,6 +258,7 @@ export default function MultiSelect<T extends DataWithId>(
476
258
  key={item.id}
477
259
  value={String(item.id)}
478
260
  label={itemToString(item)}
261
+ disabled={item.disabled}
479
262
  inputType={inputType}
480
263
  hidden={
481
264
  removeSelectedItems && value.includes(String(item.id))
@@ -1,8 +1,14 @@
1
1
  import React from "react";
2
2
  import type { Meta, StoryObj } from "@storybook/react";
3
3
  import { FormField } from "@sproutsocial/seeds-react-form-field";
4
- import SingleCombobox from "./SingleCombobox";
5
- import { books, largeBookSet, itemToString, renderItem } from "./storyData";
4
+ import { SingleCombobox } from "./SingleCombobox";
5
+ import {
6
+ books,
7
+ booksWithDisabled,
8
+ largeBookSet,
9
+ itemToString,
10
+ renderItem,
11
+ } from "./storyData";
6
12
 
7
13
  const meta: Meta<typeof SingleCombobox> = {
8
14
  title: "Really Under Development/V3/Single Combobox",
@@ -148,6 +154,24 @@ export const Controlled: Story = {
148
154
  },
149
155
  };
150
156
 
157
+ export const DisabledItems: Story = {
158
+ name: "Disabled items",
159
+ render: () => (
160
+ <div style={{ width: "300px" }}>
161
+ <FormField label="Pick a book">
162
+ {({ id }) => (
163
+ <SingleCombobox
164
+ id={id}
165
+ data={booksWithDisabled}
166
+ itemToString={itemToString}
167
+ placeholder="Search books..."
168
+ />
169
+ )}
170
+ </FormField>
171
+ </div>
172
+ ),
173
+ };
174
+
151
175
  export const Virtualized: Story = {
152
176
  name: "Virtualized",
153
177
  render: () => (
@@ -29,6 +29,7 @@ interface SingleComboboxBaseProps {
29
29
  id?: string;
30
30
  placeholder?: string;
31
31
  emptyText?: string;
32
+ disabled?: boolean;
32
33
  }
33
34
 
34
35
  interface SingleComboboxDataBaseProps<T extends DataWithId>
@@ -78,7 +79,7 @@ export type SingleComboboxProps<T extends DataWithId> =
78
79
 
79
80
  // ─── Component ────────────────────────────────────────────────────────────────
80
81
 
81
- export default function SingleCombobox<T extends DataWithId>(
82
+ export function SingleCombobox<T extends DataWithId>(
82
83
  props: SingleComboboxProps<T>
83
84
  ) {
84
85
  const {
@@ -175,6 +176,7 @@ export default function SingleCombobox<T extends DataWithId>(
175
176
  isVirtualized ? data : groups ?? (isChildrenMode ? undefined : data)
176
177
  }
177
178
  virtualized={isVirtualized}
179
+ disabled={props.disabled}
178
180
  open={open}
179
181
  onOpenChange={setOpen}
180
182
  onItemHighlighted={
@@ -265,6 +267,7 @@ export default function SingleCombobox<T extends DataWithId>(
265
267
  value={item}
266
268
  itemId={String(item.id)}
267
269
  label={itemToString(item)}
270
+ disabled={item.disabled}
268
271
  inputType={inputType}
269
272
  renderItem={
270
273
  renderItem
@@ -285,6 +288,7 @@ export default function SingleCombobox<T extends DataWithId>(
285
288
  value={item}
286
289
  itemId={String(item.id)}
287
290
  label={itemToString(item)}
291
+ disabled={item.disabled}
288
292
  inputType={inputType}
289
293
  renderItem={
290
294
  renderItem
@@ -1,8 +1,14 @@
1
1
  import React from "react";
2
2
  import type { Meta, StoryObj } from "@storybook/react";
3
3
  import { FormField } from "@sproutsocial/seeds-react-form-field";
4
- import SingleSearchableSelect from "./SingleSearchableSelect";
5
- import { books, largeBookSet, itemToString, renderItem } from "./storyData";
4
+ import { SingleSearchableSelect } from "./SingleSearchableSelect";
5
+ import {
6
+ books,
7
+ booksWithDisabled,
8
+ largeBookSet,
9
+ itemToString,
10
+ renderItem,
11
+ } from "./storyData";
6
12
 
7
13
  const meta: Meta<typeof SingleSearchableSelect> = {
8
14
  title: "Really Under Development/V3/Single Searchable Select",
@@ -163,6 +169,45 @@ export const CustomSelection: Story = {
163
169
  ),
164
170
  };
165
171
 
172
+ export const DisabledItems: Story = {
173
+ name: "Disabled items",
174
+ render: () => (
175
+ <div style={{ width: "300px" }}>
176
+ <FormField label="Pick a book">
177
+ {({ id }) => (
178
+ <SingleSearchableSelect
179
+ id={id}
180
+ data={booksWithDisabled}
181
+ itemToString={itemToString}
182
+ placeholder="Select a book"
183
+ searchPlaceholder="Search books..."
184
+ />
185
+ )}
186
+ </FormField>
187
+ </div>
188
+ ),
189
+ };
190
+
191
+ export const IncludePlaceholderItem: Story = {
192
+ name: "Include placeholder item",
193
+ render: () => (
194
+ <div style={{ width: "300px" }}>
195
+ <FormField label="Pick a book">
196
+ {({ id }) => (
197
+ <SingleSearchableSelect
198
+ id={id}
199
+ data={books}
200
+ itemToString={itemToString}
201
+ placeholder="Select a book"
202
+ searchPlaceholder="Search books..."
203
+ includePlaceholderItem
204
+ />
205
+ )}
206
+ </FormField>
207
+ </div>
208
+ ),
209
+ };
210
+
166
211
  export const Virtualized: Story = {
167
212
  name: "Virtualized",
168
213
  render: () => (
@@ -29,10 +29,12 @@ import { StyledChevron, ChevronIcon } from "./Common/SelectIcons";
29
29
  interface SingleSearchableSelectBaseProps {
30
30
  id?: string;
31
31
  placeholder?: string;
32
+ includePlaceholderItem?: boolean;
32
33
  searchPlaceholder?: string;
33
34
  emptyText?: string;
34
35
  isLoading?: boolean;
35
36
  loadingText?: string;
37
+ disabled?: boolean;
36
38
  }
37
39
 
38
40
  interface SingleSearchableSelectDataBaseProps<T extends DataWithId>
@@ -85,12 +87,13 @@ export type SingleSearchableSelectProps<T extends DataWithId> =
85
87
 
86
88
  // ─── Component ────────────────────────────────────────────────────────────────
87
89
 
88
- export default function SingleSearchableSelect<T extends DataWithId>(
90
+ export function SingleSearchableSelect<T extends DataWithId>(
89
91
  props: SingleSearchableSelectProps<T>
90
92
  ) {
91
93
  const {
92
94
  id,
93
95
  placeholder = "Select...",
96
+ includePlaceholderItem,
94
97
  searchPlaceholder = "Search...",
95
98
  emptyText = "No results found.",
96
99
  isLoading = false,
@@ -179,6 +182,7 @@ export default function SingleSearchableSelect<T extends DataWithId>(
179
182
  <Combobox.Root
180
183
  id={id}
181
184
  value={value}
185
+ disabled={props.disabled}
182
186
  onValueChange={handleValueChange}
183
187
  itemToStringLabel={itemToString}
184
188
  isItemEqualToValue={(a, b) => a != null && b != null && a.id === b.id}
@@ -282,6 +286,7 @@ export default function SingleSearchableSelect<T extends DataWithId>(
282
286
  value={item}
283
287
  itemId={String(item.id)}
284
288
  label={itemToString(item)}
289
+ disabled={item.disabled}
285
290
  inputType={inputType}
286
291
  renderItem={
287
292
  renderItem
@@ -296,18 +301,31 @@ export default function SingleSearchableSelect<T extends DataWithId>(
296
301
  </React.Fragment>
297
302
  )
298
303
  ) : (
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
- )
304
+ <>
305
+ {includePlaceholderItem && (
306
+ <ComboboxItem
307
+ key="placeholder"
308
+ value={null as unknown as T}
309
+ itemId="placeholder"
310
+ label={placeholder}
311
+ />
312
+ )}
313
+ <Combobox.Collection>
314
+ {(item: T) => (
315
+ <ComboboxItem
316
+ key={item.id}
317
+ value={item}
318
+ itemId={String(item.id)}
319
+ label={itemToString(item)}
320
+ disabled={item.disabled}
321
+ inputType={inputType}
322
+ renderItem={
323
+ renderItem ? () => renderItem(item) : undefined
324
+ }
325
+ />
326
+ )}
327
+ </Combobox.Collection>
328
+ </>
311
329
  )}
312
330
  </SearchableSelectList>
313
331
  </SearchableSelectPopup>
@@ -1,10 +1,15 @@
1
1
  import React from "react";
2
2
  import type { Meta, StoryObj } from "@storybook/react";
3
3
  import { FormField } from "@sproutsocial/seeds-react-form-field";
4
- import SingleSelect from "./SingleSelect";
4
+ import { SingleSelect } from "./SingleSelect";
5
5
  import SelectItem from "./Common/SelectItem";
6
6
  import SelectGroup from "./Common/SelectGroup";
7
- import { books, itemToString, renderItem } from "./storyData";
7
+ import {
8
+ books,
9
+ booksWithDisabled,
10
+ itemToString,
11
+ renderItem,
12
+ } from "./storyData";
8
13
 
9
14
  const meta: Meta<typeof SingleSelect> = {
10
15
  title: "Really Under Development/V3/Single Select",
@@ -32,6 +37,25 @@ export const Basic: Story = {
32
37
  ),
33
38
  };
34
39
 
40
+ export const Disabled: Story = {
41
+ name: "Disabled",
42
+ render: () => (
43
+ <div style={{ width: "300px" }}>
44
+ <FormField label="Pick a book">
45
+ {({ id }) => (
46
+ <SingleSelect
47
+ id={id}
48
+ data={books}
49
+ itemToString={itemToString}
50
+ placeholder="Select a book"
51
+ disabled
52
+ />
53
+ )}
54
+ </FormField>
55
+ </div>
56
+ ),
57
+ };
58
+
35
59
  export const IncludePlaceholderItem: Story = {
36
60
  name: "Include placeholder item",
37
61
  render: () => (
@@ -198,6 +222,24 @@ export const CustomSelection: Story = {
198
222
  ),
199
223
  };
200
224
 
225
+ export const DisabledItems: Story = {
226
+ name: "Disabled items",
227
+ render: () => (
228
+ <div style={{ width: "300px" }}>
229
+ <FormField label="Pick a book">
230
+ {({ id }) => (
231
+ <SingleSelect
232
+ id={id}
233
+ data={booksWithDisabled}
234
+ itemToString={itemToString}
235
+ placeholder="Select a book"
236
+ />
237
+ )}
238
+ </FormField>
239
+ </div>
240
+ ),
241
+ };
242
+
201
243
  export const Children: Story = {
202
244
  name: "Children API",
203
245
  render: () => (
@@ -38,6 +38,7 @@ interface SingleSelectBaseProps {
38
38
  id?: string;
39
39
  placeholder?: string;
40
40
  includePlaceholderItem?: boolean;
41
+ disabled?: boolean;
41
42
  }
42
43
 
43
44
  interface SingleSelectDataProps<T extends DataWithId>
@@ -75,7 +76,7 @@ export type SingleSelectProps<T extends DataWithId> =
75
76
 
76
77
  // ─── Component ────────────────────────────────────────────────────────────────
77
78
 
78
- export default function SingleSelect<T extends DataWithId>(
79
+ export function SingleSelect<T extends DataWithId>(
79
80
  props: SingleSelectProps<T>
80
81
  ) {
81
82
  const { id, placeholder, includePlaceholderItem } = props;
@@ -161,6 +162,7 @@ export default function SingleSelect<T extends DataWithId>(
161
162
  <Select.Root
162
163
  items={items}
163
164
  value={value}
165
+ disabled={props.disabled}
164
166
  onValueChange={handleValueChange}
165
167
  id={id}
166
168
  >
@@ -197,6 +199,7 @@ export default function SingleSelect<T extends DataWithId>(
197
199
  key={item.id}
198
200
  value={String(item.id)}
199
201
  label={itemToString(item)}
202
+ disabled={item.disabled}
200
203
  inputType={inputType}
201
204
  renderItem={
202
205
  renderItem
@@ -210,7 +213,6 @@ export default function SingleSelect<T extends DataWithId>(
210
213
  </React.Fragment>
211
214
  ))
212
215
  ) : (
213
- // TODO: need to add a placeholder item when includePlaceholderItem is true
214
216
  <>
215
217
  {includePlaceholderItem && (
216
218
  <SelectItem
@@ -224,6 +226,7 @@ export default function SingleSelect<T extends DataWithId>(
224
226
  key={item.id}
225
227
  value={String(item.id)}
226
228
  label={itemToString(item)}
229
+ disabled={item.disabled}
227
230
  inputType={inputType}
228
231
  renderItem={
229
232
  renderItem
@@ -105,6 +105,11 @@ const generateBooks = (count: number): Book[] => {
105
105
 
106
106
  export const largeBookSet = generateBooks(1000);
107
107
 
108
+ export const booksWithDisabled: Book[] = books.map((book) => ({
109
+ ...book,
110
+ disabled: book.id === "book-3" || book.id === "book-7",
111
+ }));
112
+
108
113
  export const itemToString = (item: Book | null) => item?.title ?? "";
109
114
 
110
115
  export const renderItem = (item: Book) => (