@sproutsocial/seeds-react-menu 1.11.0 → 1.11.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/.turbo/turbo-build.log +16 -16
- package/CHANGELOG.md +13 -0
- package/dist/esm/v3/index.js +1385 -2
- package/dist/esm/v3/index.js.map +1 -1
- package/dist/v3/index.d.mts +14 -2
- package/dist/v3/index.d.ts +14 -2
- package/dist/v3/index.js +1391 -2
- package/dist/v3/index.js.map +1 -1
- package/package.json +3 -3
- package/src/v3/Common/ComboboxItem.tsx +1 -0
- package/src/v3/Common/ComboboxStyles.tsx +28 -0
- package/src/v3/Common/SelectItem.tsx +5 -1
- package/src/v3/Common/SelectStyles.tsx +5 -0
- package/src/v3/ModalStories.stories.tsx +6 -6
- package/src/v3/MultiCombobox.stories.tsx +1 -1
- package/src/v3/MultiCombobox.tsx +3 -1
- package/src/v3/MultiSearchableSelect.stories.tsx +1 -1
- package/src/v3/MultiSearchableSelect.tsx +3 -1
- package/src/v3/MultiSelect.stories.tsx +53 -53
- package/src/v3/MultiSelect.tsx +3 -222
- package/src/v3/SingleCombobox.stories.tsx +1 -1
- package/src/v3/SingleCombobox.tsx +3 -1
- package/src/v3/SingleSearchableSelect.stories.tsx +1 -1
- package/src/v3/SingleSearchableSelect.tsx +3 -1
- package/src/v3/SingleSelect.stories.tsx +20 -1
- package/src/v3/SingleSelect.tsx +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sproutsocial/seeds-react-menu",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.2",
|
|
4
4
|
"description": "Seeds React Menu",
|
|
5
5
|
"author": "Sprout Social, Inc.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"@sproutsocial/seeds-react-icon": "^2.3.3",
|
|
43
43
|
"@sproutsocial/seeds-react-input": "^1.5.12",
|
|
44
44
|
"@sproutsocial/seeds-react-label": "^1.0.16",
|
|
45
|
-
"@sproutsocial/seeds-react-popout": "^2.4.
|
|
45
|
+
"@sproutsocial/seeds-react-popout": "^2.4.34",
|
|
46
46
|
"@sproutsocial/seeds-react-radio": "^1.3.16",
|
|
47
47
|
"@sproutsocial/seeds-react-switch": "^1.2.28",
|
|
48
48
|
"@sproutsocial/seeds-react-system-props": "^3.0.2",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"styled-system": "^5.1.5"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@sproutsocial/seeds-react-tooltip": "^1.1.
|
|
60
|
+
"@sproutsocial/seeds-react-tooltip": "^1.1.16",
|
|
61
61
|
"@sproutsocial/eslint-config-seeds": "*",
|
|
62
62
|
"@sproutsocial/seeds-react-testing-library": "*",
|
|
63
63
|
"@sproutsocial/seeds-testing": "*",
|
|
@@ -25,6 +25,11 @@ export const ComboboxInputGroup = styled(Combobox.InputGroup)`
|
|
|
25
25
|
&:focus-within {
|
|
26
26
|
${focusRing}
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
&[data-disabled] {
|
|
30
|
+
opacity: 0.4;
|
|
31
|
+
cursor: not-allowed;
|
|
32
|
+
}
|
|
28
33
|
`;
|
|
29
34
|
|
|
30
35
|
export const ComboboxInput = styled(Combobox.Input)`
|
|
@@ -41,6 +46,10 @@ export const ComboboxInput = styled(Combobox.Input)`
|
|
|
41
46
|
&::placeholder {
|
|
42
47
|
color: ${({ theme }) => theme.colors.text.subtext};
|
|
43
48
|
}
|
|
49
|
+
|
|
50
|
+
&:disabled {
|
|
51
|
+
cursor: not-allowed;
|
|
52
|
+
}
|
|
44
53
|
`;
|
|
45
54
|
|
|
46
55
|
export const ComboboxTokenInput = styled(Combobox.Input)`
|
|
@@ -58,6 +67,10 @@ export const ComboboxTokenInput = styled(Combobox.Input)`
|
|
|
58
67
|
&::placeholder {
|
|
59
68
|
color: ${({ theme }) => theme.colors.text.subtext};
|
|
60
69
|
}
|
|
70
|
+
|
|
71
|
+
&:disabled {
|
|
72
|
+
cursor: not-allowed;
|
|
73
|
+
}
|
|
61
74
|
`;
|
|
62
75
|
|
|
63
76
|
export const ComboboxActionButtons = styled.div`
|
|
@@ -323,6 +336,11 @@ export const ComboboxTokenInputGroup = styled(Combobox.InputGroup)`
|
|
|
323
336
|
&:focus-within {
|
|
324
337
|
${focusRing}
|
|
325
338
|
}
|
|
339
|
+
|
|
340
|
+
&[data-disabled] {
|
|
341
|
+
opacity: 0.4;
|
|
342
|
+
cursor: not-allowed;
|
|
343
|
+
}
|
|
326
344
|
`;
|
|
327
345
|
|
|
328
346
|
export const Token = styled.span`
|
|
@@ -388,6 +406,11 @@ export const SearchableSelectTrigger = styled(Combobox.Trigger)`
|
|
|
388
406
|
&[data-popup-open] [data-chevron] {
|
|
389
407
|
transform: rotate(-180deg);
|
|
390
408
|
}
|
|
409
|
+
|
|
410
|
+
&[data-disabled] {
|
|
411
|
+
opacity: 0.4;
|
|
412
|
+
cursor: not-allowed;
|
|
413
|
+
}
|
|
391
414
|
`;
|
|
392
415
|
|
|
393
416
|
export const SearchableSelectTriggerIcon = styled(Combobox.Icon)`
|
|
@@ -472,4 +495,9 @@ export const SearchableSelectTokenTrigger = styled(Combobox.Trigger)`
|
|
|
472
495
|
&[data-popup-open] [data-chevron] {
|
|
473
496
|
transform: rotate(-180deg);
|
|
474
497
|
}
|
|
498
|
+
|
|
499
|
+
&[data-disabled] {
|
|
500
|
+
opacity: 0.4;
|
|
501
|
+
cursor: not-allowed;
|
|
502
|
+
}
|
|
475
503
|
`;
|
|
@@ -70,7 +70,11 @@ export default function SelectItem({
|
|
|
70
70
|
hidden,
|
|
71
71
|
}: SelectItemProps) {
|
|
72
72
|
return (
|
|
73
|
-
<StyledItem
|
|
73
|
+
<StyledItem
|
|
74
|
+
value={value}
|
|
75
|
+
style={hidden ? { display: "none" } : undefined}
|
|
76
|
+
data-qa-menu-item={label}
|
|
77
|
+
>
|
|
74
78
|
{inputType === "radio" ? (
|
|
75
79
|
<StyledItemIndicator
|
|
76
80
|
keepMounted
|
|
@@ -44,6 +44,11 @@ export const SelectTrigger = styled(Select.Trigger)`
|
|
|
44
44
|
&[data-popup-open] [data-chevron] {
|
|
45
45
|
transform: rotate(-180deg);
|
|
46
46
|
}
|
|
47
|
+
|
|
48
|
+
&[data-disabled] {
|
|
49
|
+
opacity: 0.4;
|
|
50
|
+
cursor: not-allowed;
|
|
51
|
+
}
|
|
47
52
|
`;
|
|
48
53
|
|
|
49
54
|
export const SelectValue = styled(Select.Value)`
|
|
@@ -10,12 +10,12 @@ import {
|
|
|
10
10
|
ModalBody,
|
|
11
11
|
ModalFooter,
|
|
12
12
|
} from "@sproutsocial/seeds-react-modal";
|
|
13
|
-
import SingleSelect from "./SingleSelect";
|
|
14
|
-
import MultiSelect from "./MultiSelect";
|
|
15
|
-
import SingleCombobox from "./SingleCombobox";
|
|
16
|
-
import MultiCombobox from "./MultiCombobox";
|
|
17
|
-
import SingleSearchableSelect from "./SingleSearchableSelect";
|
|
18
|
-
import MultiSearchableSelect from "./MultiSearchableSelect";
|
|
13
|
+
import { SingleSelect } from "./SingleSelect";
|
|
14
|
+
import { MultiSelect } from "./MultiSelect";
|
|
15
|
+
import { SingleCombobox } from "./SingleCombobox";
|
|
16
|
+
import { MultiCombobox } from "./MultiCombobox";
|
|
17
|
+
import { SingleSearchableSelect } from "./SingleSearchableSelect";
|
|
18
|
+
import { MultiSearchableSelect } from "./MultiSearchableSelect";
|
|
19
19
|
import { books, itemToString } from "./storyData";
|
|
20
20
|
|
|
21
21
|
// ─── Shared content ───────────────────────────────────────────────────────────
|
|
@@ -1,7 +1,7 @@
|
|
|
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 MultiCombobox from "./MultiCombobox";
|
|
4
|
+
import { MultiCombobox } from "./MultiCombobox";
|
|
5
5
|
import { books, largeBookSet, itemToString, renderItem } from "./storyData";
|
|
6
6
|
|
|
7
7
|
const meta: Meta<typeof MultiCombobox> = {
|
package/src/v3/MultiCombobox.tsx
CHANGED
|
@@ -64,6 +64,7 @@ interface MultiComboboxBaseProps {
|
|
|
64
64
|
emptyText?: string;
|
|
65
65
|
isLoading?: boolean;
|
|
66
66
|
loadingText?: string;
|
|
67
|
+
disabled?: boolean;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
interface MultiComboboxDataProps<T extends DataWithId>
|
|
@@ -115,7 +116,7 @@ export type MultiComboboxProps<T extends DataWithId> =
|
|
|
115
116
|
|
|
116
117
|
// ─── Component ────────────────────────────────────────────────────────────────
|
|
117
118
|
|
|
118
|
-
export
|
|
119
|
+
export function MultiCombobox<T extends DataWithId>(
|
|
119
120
|
props: MultiComboboxProps<T>
|
|
120
121
|
) {
|
|
121
122
|
const {
|
|
@@ -374,6 +375,7 @@ export default function MultiCombobox<T extends DataWithId>(
|
|
|
374
375
|
id={id}
|
|
375
376
|
virtualized={isVirtualized}
|
|
376
377
|
open={open}
|
|
378
|
+
disabled={props.disabled}
|
|
377
379
|
onOpenChange={setOpen}
|
|
378
380
|
onItemHighlighted={
|
|
379
381
|
isVirtualized
|
|
@@ -1,7 +1,7 @@
|
|
|
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 MultiSearchableSelect from "./MultiSearchableSelect";
|
|
4
|
+
import { MultiSearchableSelect } from "./MultiSearchableSelect";
|
|
5
5
|
import { books, largeBookSet, itemToString, renderItem } from "./storyData";
|
|
6
6
|
|
|
7
7
|
const meta: Meta<typeof MultiSearchableSelect> = {
|
|
@@ -71,6 +71,7 @@ interface MultiSearchableSelectBaseProps {
|
|
|
71
71
|
emptyText?: string;
|
|
72
72
|
isLoading?: boolean;
|
|
73
73
|
loadingText?: string;
|
|
74
|
+
disabled?: boolean;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
interface MultiSearchableSelectDataProps<T extends DataWithId>
|
|
@@ -121,7 +122,7 @@ export type MultiSearchableSelectProps<T extends DataWithId> =
|
|
|
121
122
|
|
|
122
123
|
// ─── Component ────────────────────────────────────────────────────────────────
|
|
123
124
|
|
|
124
|
-
export
|
|
125
|
+
export function MultiSearchableSelect<T extends DataWithId>(
|
|
125
126
|
props: MultiSearchableSelectProps<T>
|
|
126
127
|
) {
|
|
127
128
|
const {
|
|
@@ -364,6 +365,7 @@ export default function MultiSearchableSelect<T extends DataWithId>(
|
|
|
364
365
|
id={id}
|
|
365
366
|
virtualized={isVirtualized}
|
|
366
367
|
open={open}
|
|
368
|
+
disabled={props.disabled}
|
|
367
369
|
onOpenChange={setOpen}
|
|
368
370
|
onItemHighlighted={
|
|
369
371
|
isVirtualized
|
|
@@ -1,7 +1,7 @@
|
|
|
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 MultiSelect from "./MultiSelect";
|
|
4
|
+
import { MultiSelect } from "./MultiSelect";
|
|
5
5
|
import SelectItem from "./Common/SelectItem";
|
|
6
6
|
import SelectGroup from "./Common/SelectGroup";
|
|
7
7
|
import { books, itemToString, renderItem } from "./storyData";
|
|
@@ -169,59 +169,59 @@ export const CustomSelection: Story = {
|
|
|
169
169
|
),
|
|
170
170
|
};
|
|
171
171
|
|
|
172
|
-
export const CustomToken: Story = {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
};
|
|
172
|
+
// export const CustomToken: Story = {
|
|
173
|
+
// name: "Custom renderToken",
|
|
174
|
+
// render: () => (
|
|
175
|
+
// <div style={{ width: "300px" }}>
|
|
176
|
+
// <FormField label="Pick books">
|
|
177
|
+
// {({ id }) => (
|
|
178
|
+
// <MultiSelect
|
|
179
|
+
// id={id}
|
|
180
|
+
// data={books}
|
|
181
|
+
// itemToString={itemToString}
|
|
182
|
+
// renderItem={renderItem}
|
|
183
|
+
// placeholder="Select books..."
|
|
184
|
+
// renderToken={(item) => (
|
|
185
|
+
// <span>
|
|
186
|
+
// {item.title}{" "}
|
|
187
|
+
// <span style={{ fontSize: "11px", opacity: 0.7 }}>
|
|
188
|
+
// ({item.author})
|
|
189
|
+
// </span>
|
|
190
|
+
// </span>
|
|
191
|
+
// )}
|
|
192
|
+
// />
|
|
193
|
+
// )}
|
|
194
|
+
// </FormField>
|
|
195
|
+
// </div>
|
|
196
|
+
// ),
|
|
197
|
+
// };
|
|
198
198
|
|
|
199
|
-
export const MaxTokens: Story = {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
};
|
|
199
|
+
// export const MaxTokens: Story = {
|
|
200
|
+
// name: "Max tokens",
|
|
201
|
+
// render: () => (
|
|
202
|
+
// <div style={{ width: "300px" }}>
|
|
203
|
+
// <FormField label="Pick books">
|
|
204
|
+
// {({ id }) => (
|
|
205
|
+
// <MultiSelect
|
|
206
|
+
// id={id}
|
|
207
|
+
// data={books}
|
|
208
|
+
// itemToString={itemToString}
|
|
209
|
+
// renderItem={renderItem}
|
|
210
|
+
// placeholder="Select books..."
|
|
211
|
+
// defaultSelectedItemIds={[
|
|
212
|
+
// "book-1",
|
|
213
|
+
// "book-2",
|
|
214
|
+
// "book-3",
|
|
215
|
+
// "book-4",
|
|
216
|
+
// "book-5",
|
|
217
|
+
// ]}
|
|
218
|
+
// maxTokens={2}
|
|
219
|
+
// />
|
|
220
|
+
// )}
|
|
221
|
+
// </FormField>
|
|
222
|
+
// </div>
|
|
223
|
+
// ),
|
|
224
|
+
// };
|
|
225
225
|
|
|
226
226
|
export const RemoveSelectedItems: Story = {
|
|
227
227
|
name: "Remove selected items",
|
package/src/v3/MultiSelect.tsx
CHANGED
|
@@ -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
|
|
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>
|
|
@@ -1,7 +1,7 @@
|
|
|
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";
|
|
4
|
+
import { SingleCombobox } from "./SingleCombobox";
|
|
5
5
|
import { books, largeBookSet, itemToString, renderItem } from "./storyData";
|
|
6
6
|
|
|
7
7
|
const meta: Meta<typeof SingleCombobox> = {
|
|
@@ -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
|
|
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={
|
|
@@ -1,7 +1,7 @@
|
|
|
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";
|
|
4
|
+
import { SingleSearchableSelect } from "./SingleSearchableSelect";
|
|
5
5
|
import { books, largeBookSet, itemToString, renderItem } from "./storyData";
|
|
6
6
|
|
|
7
7
|
const meta: Meta<typeof SingleSearchableSelect> = {
|
|
@@ -33,6 +33,7 @@ interface SingleSearchableSelectBaseProps {
|
|
|
33
33
|
emptyText?: string;
|
|
34
34
|
isLoading?: boolean;
|
|
35
35
|
loadingText?: string;
|
|
36
|
+
disabled?: boolean;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
interface SingleSearchableSelectDataBaseProps<T extends DataWithId>
|
|
@@ -85,7 +86,7 @@ export type SingleSearchableSelectProps<T extends DataWithId> =
|
|
|
85
86
|
|
|
86
87
|
// ─── Component ────────────────────────────────────────────────────────────────
|
|
87
88
|
|
|
88
|
-
export
|
|
89
|
+
export function SingleSearchableSelect<T extends DataWithId>(
|
|
89
90
|
props: SingleSearchableSelectProps<T>
|
|
90
91
|
) {
|
|
91
92
|
const {
|
|
@@ -179,6 +180,7 @@ export default function SingleSearchableSelect<T extends DataWithId>(
|
|
|
179
180
|
<Combobox.Root
|
|
180
181
|
id={id}
|
|
181
182
|
value={value}
|
|
183
|
+
disabled={props.disabled}
|
|
182
184
|
onValueChange={handleValueChange}
|
|
183
185
|
itemToStringLabel={itemToString}
|
|
184
186
|
isItemEqualToValue={(a, b) => a != null && b != null && a.id === b.id}
|