@sproutsocial/seeds-react-menu 1.6.1 → 1.6.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.
- package/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +14 -0
- package/dist/esm/index.js +17 -12
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/Autocomplete/Autocomplete.stories.tsx +60 -3
- package/src/Autocomplete/AutocompleteTypes.ts +1 -0
- package/src/Autocomplete/MultiAutocomplete.tsx +15 -13
- package/src/Autocomplete/SingleAutocomplete.tsx +2 -1
- package/src/Autocomplete/__tests__/Autocomplete.test.tsx +18 -3
package/package.json
CHANGED
|
@@ -20,6 +20,7 @@ import { TEST_BOOKS } from "../__fixtures__/menu-test-data";
|
|
|
20
20
|
import { Autocomplete } from "./Autocomplete";
|
|
21
21
|
import { AutocompleteInput } from "./AutocompleteInput";
|
|
22
22
|
import { AutocompleteTokenInput } from "./AutocompleteTokenInput";
|
|
23
|
+
import Button from "@sproutsocial/seeds-react-button";
|
|
23
24
|
|
|
24
25
|
type CustomAutocompleteArgs = React.ComponentProps<typeof Autocomplete>;
|
|
25
26
|
|
|
@@ -220,7 +221,6 @@ export const AutocompleteControlled: Story = {
|
|
|
220
221
|
};
|
|
221
222
|
|
|
222
223
|
const TestControlledMultiAutocomplete = (args: CustomAutocompleteArgs) => {
|
|
223
|
-
const [isOpen, setIsOpen] = React.useState<boolean>(false);
|
|
224
224
|
const [selectedItems, setSelectedItems] = React.useState<
|
|
225
225
|
TypeInternalItemProps[]
|
|
226
226
|
>([]);
|
|
@@ -228,8 +228,6 @@ const TestControlledMultiAutocomplete = (args: CustomAutocompleteArgs) => {
|
|
|
228
228
|
return (
|
|
229
229
|
<Autocomplete
|
|
230
230
|
multiSelect
|
|
231
|
-
isOpen={isOpen}
|
|
232
|
-
onIsOpenChange={({ isOpen }) => setIsOpen(isOpen)}
|
|
233
231
|
selectedItems={selectedItems}
|
|
234
232
|
onSelectedItemsChange={({ selectedItems: newSelectedItems }) =>
|
|
235
233
|
setSelectedItems(newSelectedItems)
|
|
@@ -259,9 +257,68 @@ const TestControlledMultiAutocomplete = (args: CustomAutocompleteArgs) => {
|
|
|
259
257
|
);
|
|
260
258
|
};
|
|
261
259
|
|
|
260
|
+
const TestControlledMultiAutocompleteWithExternalButton = () => {
|
|
261
|
+
const [isOpen, setIsOpen] = React.useState<boolean>(false);
|
|
262
|
+
const [selectedItems, setSelectedItems] = React.useState<
|
|
263
|
+
TypeInternalItemProps[]
|
|
264
|
+
>([]);
|
|
265
|
+
|
|
266
|
+
const handleSetSelectedItems = ({ selectedItems: newSelectedItems }) => {
|
|
267
|
+
setSelectedItems(newSelectedItems);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const handleAddRandom = () => {
|
|
271
|
+
const randomItem = {
|
|
272
|
+
id: "book-3",
|
|
273
|
+
index: 2,
|
|
274
|
+
menuItemProps: {
|
|
275
|
+
children: "The Idiot",
|
|
276
|
+
id: "book-3",
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
setSelectedItems((prev) => [...prev, randomItem]);
|
|
280
|
+
console.log("random thing");
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
return (
|
|
284
|
+
<>
|
|
285
|
+
<Autocomplete
|
|
286
|
+
multiSelect
|
|
287
|
+
isOpen={isOpen}
|
|
288
|
+
onIsOpenChange={({ isOpen }) => setIsOpen(isOpen)}
|
|
289
|
+
selectedItems={selectedItems}
|
|
290
|
+
onSelectedItemsChange={handleSetSelectedItems}
|
|
291
|
+
itemToString={(item) => (item ? TEST_BOOKS[item.index].title : "")}
|
|
292
|
+
menuToggleElement={
|
|
293
|
+
<>
|
|
294
|
+
<MenuLabel mb={300}>Choose a book</MenuLabel>
|
|
295
|
+
<AutocompleteTokenInput />
|
|
296
|
+
</>
|
|
297
|
+
}
|
|
298
|
+
>
|
|
299
|
+
<MenuContent>
|
|
300
|
+
<MenuGroup id="books">
|
|
301
|
+
{TEST_BOOKS.map(({ id, title }) => (
|
|
302
|
+
<MenuItem key={id} id={id}>
|
|
303
|
+
{title}
|
|
304
|
+
</MenuItem>
|
|
305
|
+
))}
|
|
306
|
+
</MenuGroup>
|
|
307
|
+
</MenuContent>
|
|
308
|
+
</Autocomplete>
|
|
309
|
+
<Button onClick={handleAddRandom}>Add Random thing</Button>
|
|
310
|
+
</>
|
|
311
|
+
);
|
|
312
|
+
};
|
|
313
|
+
|
|
262
314
|
export const MultiAutocompleteControlled: Story = {
|
|
263
315
|
name: "MultiAutocomplete Controlled State",
|
|
264
316
|
render: (args: CustomAutocompleteArgs) => (
|
|
265
317
|
<TestControlledMultiAutocomplete {...args} />
|
|
266
318
|
),
|
|
267
319
|
};
|
|
320
|
+
|
|
321
|
+
export const MultiAutocompleteControlledWithExternal: Story = {
|
|
322
|
+
name: "MultiAutocomplete Controlled State With External Button",
|
|
323
|
+
render: () => <TestControlledMultiAutocompleteWithExternalButton />,
|
|
324
|
+
};
|
|
@@ -16,6 +16,7 @@ export type TypeBaseAutocompleteProps = TypeHigherOrderComponentProps &
|
|
|
16
16
|
/** Set multiSelect to true to allow multiple selections.
|
|
17
17
|
* Default: false*/
|
|
18
18
|
multiSelect?: boolean;
|
|
19
|
+
autoCloseOnEmpty?: boolean;
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
export type TypeSingleAutocompleteProps<
|
|
@@ -37,6 +37,8 @@ export const MultiAutocomplete = <
|
|
|
37
37
|
getA11yMultiStatusMessage,
|
|
38
38
|
onMultiSelectStateChange,
|
|
39
39
|
multiSelectStateReducer,
|
|
40
|
+
autoCloseOnEmpty = true,
|
|
41
|
+
selectedItems: selectedItemsProp,
|
|
40
42
|
...useComboboxProps
|
|
41
43
|
}: TypeMultiAutocompleteProps<I>) => {
|
|
42
44
|
/** Get a list of menuItems and children */
|
|
@@ -45,11 +47,16 @@ export const MultiAutocomplete = <
|
|
|
45
47
|
menuItems,
|
|
46
48
|
MenuItemComponent,
|
|
47
49
|
});
|
|
48
|
-
const [
|
|
50
|
+
const [selectedItemsState, setSelectedItemsState] =
|
|
49
51
|
React.useState(initialSelectedItems);
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
const selectedItems = React.useMemo(
|
|
53
|
+
() => selectedItemsProp || selectedItemsState,
|
|
54
|
+
[selectedItemsProp, selectedItemsState]
|
|
55
|
+
);
|
|
56
|
+
const selectedItemIds = React.useMemo(
|
|
57
|
+
() => getSelectedItemIds(selectedItems),
|
|
58
|
+
[selectedItems]
|
|
59
|
+
);
|
|
53
60
|
|
|
54
61
|
// function that checks that item should be visible based on the selected items
|
|
55
62
|
const getIsItemVisibleMulti = React.useCallback<typeof getIsItemVisible>(
|
|
@@ -108,10 +115,9 @@ export const MultiAutocomplete = <
|
|
|
108
115
|
const useMultipleSelectionReturnProps = useMultipleSelection({
|
|
109
116
|
...defaultDownshiftProps.multi,
|
|
110
117
|
itemToKey,
|
|
111
|
-
selectedItems,
|
|
118
|
+
selectedItems: selectedItemsProp || selectedItems,
|
|
112
119
|
onSelectedItemsChange: ({ selectedItems, ...changes }) => {
|
|
113
|
-
|
|
114
|
-
setSelectedItemIds(getSelectedItemIds(selectedItems));
|
|
120
|
+
setSelectedItemsState(selectedItems);
|
|
115
121
|
onSelectedItemsChange &&
|
|
116
122
|
onSelectedItemsChange({ selectedItems, ...changes });
|
|
117
123
|
},
|
|
@@ -138,11 +144,7 @@ export const MultiAutocomplete = <
|
|
|
138
144
|
case useCombobox.stateChangeTypes.ItemClick:
|
|
139
145
|
if (newSelectedItem) {
|
|
140
146
|
const newSelectedItems = [...selectedItems, newSelectedItem];
|
|
141
|
-
|
|
142
|
-
setSelectedItemIds({
|
|
143
|
-
...selectedItemIds,
|
|
144
|
-
[newSelectedItem.id]: true,
|
|
145
|
-
});
|
|
147
|
+
setSelectedItemsState(newSelectedItems);
|
|
146
148
|
onSelectedItemsChange &&
|
|
147
149
|
onSelectedItemsChange({
|
|
148
150
|
selectedItems: newSelectedItems,
|
|
@@ -175,6 +177,7 @@ export const MultiAutocomplete = <
|
|
|
175
177
|
<MenuRootComponent
|
|
176
178
|
{...{
|
|
177
179
|
isListbox: true,
|
|
180
|
+
autoCloseOnEmpty,
|
|
178
181
|
items: allMenuItems,
|
|
179
182
|
itemToString,
|
|
180
183
|
initialSelectedItems,
|
|
@@ -192,7 +195,6 @@ export const MultiAutocomplete = <
|
|
|
192
195
|
focusOnContent: false,
|
|
193
196
|
...userPopoutProps,
|
|
194
197
|
}}
|
|
195
|
-
autoCloseOnEmpty
|
|
196
198
|
>
|
|
197
199
|
{children}
|
|
198
200
|
</MenuRootComponent>
|
|
@@ -20,6 +20,7 @@ export const SingleAutocomplete = <
|
|
|
20
20
|
MenuItemComponent,
|
|
21
21
|
itemToString = defaultItemToString,
|
|
22
22
|
getIsItemVisible,
|
|
23
|
+
autoCloseOnEmpty = true,
|
|
23
24
|
...useComboboxProps
|
|
24
25
|
}: TypeSingleAutocompleteProps<I>) => {
|
|
25
26
|
/** Get a list of menuItems and children */
|
|
@@ -63,6 +64,7 @@ export const SingleAutocomplete = <
|
|
|
63
64
|
<MenuRootComponent
|
|
64
65
|
{...{
|
|
65
66
|
isListbox: true,
|
|
67
|
+
autoCloseOnEmpty,
|
|
66
68
|
items: allMenuItems,
|
|
67
69
|
itemToString,
|
|
68
70
|
...useComboboxProps,
|
|
@@ -73,7 +75,6 @@ export const SingleAutocomplete = <
|
|
|
73
75
|
focusOnContent: false,
|
|
74
76
|
...userPopoutProps,
|
|
75
77
|
}}
|
|
76
|
-
autoCloseOnEmpty
|
|
77
78
|
>
|
|
78
79
|
{children}
|
|
79
80
|
</MenuRootComponent>
|
|
@@ -174,7 +174,7 @@ describe("Autocomplete Component", () => {
|
|
|
174
174
|
expect(customFilter).toHaveBeenCalled();
|
|
175
175
|
});
|
|
176
176
|
|
|
177
|
-
it("should close the dropdown when no items match the input", async () => {
|
|
177
|
+
it("should close the dropdown when no items match the input by default", async () => {
|
|
178
178
|
const { user } = render(<Autocomplete {...defaultAutocompleteProps} />);
|
|
179
179
|
const inputElement = screen.getByRole("combobox");
|
|
180
180
|
|
|
@@ -184,11 +184,26 @@ describe("Autocomplete Component", () => {
|
|
|
184
184
|
});
|
|
185
185
|
|
|
186
186
|
await waitFor(() => {
|
|
187
|
-
const
|
|
188
|
-
expect(
|
|
187
|
+
const emptyState = screen.queryByText("No items found");
|
|
188
|
+
expect(emptyState).not.toBeInTheDocument();
|
|
189
189
|
});
|
|
190
190
|
});
|
|
191
191
|
|
|
192
|
+
it("should not close the dropdown when no items match the input if supplied autoCloseOnEmpty={false}", async () => {
|
|
193
|
+
const { user } = render(
|
|
194
|
+
<Autocomplete {...defaultAutocompleteProps} autoCloseOnEmpty={false} />
|
|
195
|
+
);
|
|
196
|
+
const inputElement = screen.getByRole("combobox");
|
|
197
|
+
|
|
198
|
+
await act(async () => {
|
|
199
|
+
await user.click(inputElement);
|
|
200
|
+
await user.type(inputElement, "something not in the list");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const emptyState = screen.getByText("No items found");
|
|
204
|
+
expect(emptyState).toBeInTheDocument();
|
|
205
|
+
});
|
|
206
|
+
|
|
192
207
|
it("should apply custom popoutProps.width to the popout container", async () => {
|
|
193
208
|
const { user } = render(
|
|
194
209
|
<Autocomplete
|