@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.
- package/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +57 -0
- package/dist/esm/index.js +73 -30
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +34 -7
- package/dist/index.d.ts +34 -7
- package/dist/index.js +68 -25
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
- package/src/ActionMenu/ActionMenu.stories.tsx +20 -24
- package/src/Autocomplete/MultiAutocomplete.tsx +0 -1
- package/src/Autocomplete/__tests__/Autocomplete.test.tsx +36 -0
- package/src/MenuContent/MenuContent.tsx +2 -0
- package/src/MenuContext.tsx +2 -1
- package/src/MenuFooter/styles.tsx +1 -1
- package/src/MenuGroup/MenuGroup.tsx +5 -1
- package/src/MenuGroup/styles.tsx +1 -1
- package/src/MenuHeader/MenuHeader.tsx +10 -7
- package/src/MenuHeader/styles.tsx +4 -0
- package/src/MenuItem/MenuItem.tsx +5 -1
- package/src/MenuItem/styles.tsx +4 -2
- package/src/MenuItem/useOptionProps.tsx +2 -2
- package/src/MenuRoot/MenuRoot.tsx +17 -3
- package/src/MenuSearchInput/MenuSearchInput.tsx +30 -8
- package/src/MenuSearchTokenInput/MenuSearchTokenInput.tsx +13 -3
- package/src/MultiSelectMenu/MultiSelectMenu.stories.tsx +51 -43
- package/src/NestedMenu/NestedMenuContent.tsx +5 -1
- package/src/NestedMenu/NestedMenuItem.tsx +5 -1
- package/src/SingleSelectMenu/SingleSelectMenu.stories.tsx +34 -28
- package/src/SubmenuItem/SubmenuItem.tsx +5 -1
- package/src/hooks/useDownshiftDefaults.tsx +11 -1
- package/src/hooks/useItemFiltering.tsx +2 -0
- package/src/hooks/useMenuChildren.tsx +11 -6
|
@@ -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: (
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
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
|
|
66
|
-
|
|
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.
|
|
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
|
>({
|