@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutsocial/seeds-react-menu",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "Seeds React Menu",
5
5
  "author": "Sprout Social, Inc.",
6
6
  "license": "MIT",
@@ -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 [selectedItems, setSelectedItems] =
50
+ const [selectedItemsState, setSelectedItemsState] =
49
51
  React.useState(initialSelectedItems);
50
- const [selectedItemIds, setSelectedItemIds] = React.useState<
51
- Record<string, boolean>
52
- >(getSelectedItemIds(initialSelectedItems));
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
- setSelectedItems(selectedItems);
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
- setSelectedItems(newSelectedItems);
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 listbox = screen.queryByRole("listbox");
188
- expect(listbox).not.toBeInTheDocument();
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