@sproutsocial/seeds-react-menu 0.6.0 → 1.0.0

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.
@@ -164,36 +164,42 @@ export const SingleSelectWithIcons: Story = {
164
164
  },
165
165
  };
166
166
 
167
+ const TestControlledSingleSelectMenu = ({
168
+ inputType,
169
+ ...args
170
+ }: CustomSingleSelectArgs) => {
171
+ const [isOpen, setIsOpen] = React.useState<boolean>(false);
172
+ const [selectedItem, setSelectedItem] =
173
+ React.useState<TypeInternalItemProps | null>(null);
174
+ return (
175
+ <SingleSelectMenu
176
+ isOpen={isOpen}
177
+ onIsOpenChange={({ isOpen }) => setIsOpen(isOpen)}
178
+ selectedItem={selectedItem}
179
+ onSelectedItemChange={({ selectedItem: newSelectedItem }) => {
180
+ setSelectedItem(newSelectedItem);
181
+ }}
182
+ itemToString={(item) => (item ? TEST_BOOKS[item.index].title : "")}
183
+ menuToggleElement={<StorybookMenuToggleButton />}
184
+ {...(args as object)}
185
+ >
186
+ <MenuContent>
187
+ <MenuGroup id="books">
188
+ {TEST_BOOKS.map(({ id, title }) => (
189
+ <MenuItem key={id} id={id} inputType={inputType}>
190
+ {title}
191
+ </MenuItem>
192
+ ))}
193
+ </MenuGroup>
194
+ </MenuContent>
195
+ </SingleSelectMenu>
196
+ );
197
+ };
167
198
  export const SingleSelectControlled: Story = {
168
199
  name: "Controlled State",
169
- render: ({ inputType, ...args }) => {
170
- const [isOpen, setIsOpen] = React.useState<boolean>(false);
171
- const [selectedItem, setSelectedItem] =
172
- React.useState<TypeInternalItemProps | null>(null);
173
- return (
174
- <SingleSelectMenu
175
- isOpen={isOpen}
176
- onIsOpenChange={({ isOpen }) => setIsOpen(isOpen)}
177
- selectedItem={selectedItem}
178
- onSelectedItemChange={({ selectedItem: newSelectedItem }) => {
179
- setSelectedItem(newSelectedItem);
180
- }}
181
- itemToString={(item) => (item ? TEST_BOOKS[item.index].title : "")}
182
- menuToggleElement={<StorybookMenuToggleButton />}
183
- {...(args as object)}
184
- >
185
- <MenuContent>
186
- <MenuGroup id="books">
187
- {TEST_BOOKS.map(({ id, title }) => (
188
- <MenuItem key={id} id={id} inputType={inputType}>
189
- {title}
190
- </MenuItem>
191
- ))}
192
- </MenuGroup>
193
- </MenuContent>
194
- </SingleSelectMenu>
195
- );
196
- },
200
+ render: (args: CustomSingleSelectArgs) => (
201
+ <TestControlledSingleSelectMenu {...args} />
202
+ ),
197
203
  };
198
204
 
199
205
  export const SingleSelectMenuWithSearch: Story = {
@@ -65,7 +65,7 @@ const doesRefContainEventTarget = (
65
65
  );
66
66
  };
67
67
 
68
- export const SubmenuItem = <
68
+ const SubmenuItem = <
69
69
  I extends TypeMenuItemProps,
70
70
  P extends TypeSubmenuValidMenuComponentProps<I>
71
71
  >({
@@ -252,3 +252,7 @@ export const SubmenuItem = <
252
252
  />
253
253
  );
254
254
  };
255
+
256
+ SubmenuItem.__MENU_PRIMITIVE_TYPE = "MenuItem";
257
+
258
+ export { SubmenuItem };
@@ -46,7 +46,7 @@ export const defaultUseComboboxPropsForMulti: Partial<
46
46
  export interface TypeUseDownshiftDefaultsReturn
47
47
  extends Pick<
48
48
  Required<TypeMenuContextBaseProps>,
49
- "hiddenItemsMap" | "setHiddenItemsMap"
49
+ "hiddenItemsMap" | "setHiddenItemsMap" | "hiddenItemsCount"
50
50
  > {
51
51
  defaultDownshiftProps: {
52
52
  select: Partial<UseSelectProps<TypeInternalItemProps>>;
@@ -55,6 +55,8 @@ export interface TypeUseDownshiftDefaultsReturn
55
55
  };
56
56
  }
57
57
 
58
+ export const itemToKey = (item: TypeInternalItemProps | null) => item?.id;
59
+
58
60
  export const useDownshiftDefaults = ({
59
61
  isCombobox = false,
60
62
  isMulti = false,
@@ -67,6 +69,10 @@ export const useDownshiftDefaults = ({
67
69
  const [hiddenItemsMap, setHiddenItemsMap] = React.useState<
68
70
  Record<string, boolean>
69
71
  >({});
72
+ const hiddenItemsCount = React.useMemo(
73
+ () => Object.keys(hiddenItemsMap).filter(Boolean).length,
74
+ [hiddenItemsMap]
75
+ );
70
76
  const isItemDisabled = React.useCallback<
71
77
  Required<UseSelectProps<TypeInternalItemProps>>["isItemDisabled"]
72
78
  >(
@@ -76,10 +82,12 @@ export const useDownshiftDefaults = ({
76
82
 
77
83
  return {
78
84
  hiddenItemsMap,
85
+ hiddenItemsCount,
79
86
  setHiddenItemsMap,
80
87
  defaultDownshiftProps: {
81
88
  select: !isCombobox
82
89
  ? {
90
+ itemToKey,
83
91
  // disabling while item.hidden so that hidden items are ignored by downshift
84
92
  isItemDisabled,
85
93
  ...(isMulti
@@ -89,6 +97,7 @@ export const useDownshiftDefaults = ({
89
97
  : {},
90
98
  combobox: isCombobox
91
99
  ? {
100
+ itemToKey,
92
101
  // disabling while item.hidden so that hidden items are ignored by downshift
93
102
  isItemDisabled,
94
103
  ...(isMulti
@@ -98,6 +107,7 @@ export const useDownshiftDefaults = ({
98
107
  : {},
99
108
  multi: isMulti
100
109
  ? {
110
+ itemToKey,
101
111
  // handles the prop name mapping for multi select
102
112
  ...getMultiSelectOverrides(multiSelectProps),
103
113
  }
@@ -45,6 +45,7 @@ export const useItemFiltering = ({
45
45
  const [hiddenItemsMap, setHiddenItemsMap] = React.useState<
46
46
  Required<TypeMenuContext>["hiddenItemsMap"]
47
47
  >({});
48
+
48
49
  const updateFilteredItems = React.useCallback(
49
50
  ({ inputValue }) => {
50
51
  const newHiddenItems = getShouldFilter({ inputValue })
@@ -53,6 +54,7 @@ export const useItemFiltering = ({
53
54
  return isItemVisible ? map : { ...map, [item.id]: true };
54
55
  }, {} as typeof hiddenItemsMap)
55
56
  : {};
57
+
56
58
  setHiddenItemsMap(newHiddenItems);
57
59
  setHiddenItemsMapProp?.(newHiddenItems);
58
60
  },
@@ -57,18 +57,21 @@ const walkAllChildren = (
57
57
  });
58
58
  };
59
59
 
60
+ // get the name from our custom __MENU_PRIMITIVE_TYPE property
60
61
  const getComponentName = (element: ReactNode): string | undefined => {
61
62
  if (
62
63
  element &&
63
64
  typeof element === "object" &&
64
65
  "type" in element &&
65
- typeof element.type !== "string" &&
66
- typeof element.type.name === "string"
66
+ (typeof element.type === "function" || typeof element.type === "object") &&
67
+ "__MENU_PRIMITIVE_TYPE" in element.type &&
68
+ typeof element.type.__MENU_PRIMITIVE_TYPE === "string"
67
69
  ) {
68
- return element.type.name;
70
+ return element.type.__MENU_PRIMITIVE_TYPE;
69
71
  }
70
72
  };
71
73
 
74
+ // Add a menu item to the provided allMenuItems array and menuItemsMap object
72
75
  const addMenuItem = ({
73
76
  itemProps,
74
77
  allMenuItems,
@@ -86,6 +89,7 @@ const addMenuItem = ({
86
89
  menuItemsMap[`${item.id}`] = item;
87
90
  };
88
91
 
92
+ // Add items from a MenuGroup element to the allMenuItems array and menuItemsMap object
89
93
  const addItemsFromGroup = <I extends TypeMenuItemProps = TypeMenuItemProps>({
90
94
  groupElement: menuGroup,
91
95
  allMenuItems,
@@ -130,6 +134,7 @@ const addItemsFromGroup = <I extends TypeMenuItemProps = TypeMenuItemProps>({
130
134
  }
131
135
  };
132
136
 
137
+ // Add items from a MenuContent element to the allMenuItems array and menuItemsMap object
133
138
  const addItemsFromContent = <I extends TypeMenuItemProps = TypeMenuItemProps>({
134
139
  contentElement: menuContent,
135
140
  allMenuItems,
@@ -160,6 +165,7 @@ const addItemsFromContent = <I extends TypeMenuItemProps = TypeMenuItemProps>({
160
165
  }
161
166
  };
162
167
 
168
+ // Traverse the children of a Menu component and add items to the allMenuItems array and menuItemsMap object
163
169
  const setMenuItemsFromChildren = ({
164
170
  children,
165
171
  allMenuItems,
@@ -172,8 +178,6 @@ const setMenuItemsFromChildren = ({
172
178
  walkAllChildren(children, (element) => {
173
179
  switch (getComponentName(element)) {
174
180
  case "MenuItem":
175
- case "SubmenuItem":
176
- case "NestedMenuItem":
177
181
  // Cast element to the TypeItemElement since we know it's one of the three.
178
182
  // This is needed for the addMenuItem function.
179
183
  const menuItem = element as TypeItemElement;
@@ -198,7 +202,6 @@ const setMenuItemsFromChildren = ({
198
202
  });
199
203
  break;
200
204
  case "MenuContent":
201
- case "NestedMenuContent":
202
205
  addItemsFromContent({
203
206
  // Cast element to the TypeMenuContentElement since we know it's a content and is needed for addItemsFromContent.
204
207
  contentElement: element as TypeMenuContentElement,
@@ -233,6 +236,7 @@ type TypeUseMenuChildrenProps<I extends TypeMenuItemProps = TypeMenuItemProps> =
233
236
  MenuItemComponent?: React.ComponentType<I>;
234
237
  };
235
238
 
239
+ // Return a MenuContent using the provided menuItems
236
240
  export const getChildrenFromMenuItems = <
237
241
  I extends TypeMenuItemProps = TypeMenuItemProps
238
242
  >({
@@ -245,6 +249,7 @@ export const getChildrenFromMenuItems = <
245
249
  <MenuContent menuItems={menuItems} MenuItemComponent={MenuItemComponent} />
246
250
  );
247
251
 
252
+ // Hook to get all menu items and children for a Menu component
248
253
  export const useMenuChildren = <
249
254
  I extends TypeMenuItemProps = TypeMenuItemProps
250
255
  >({