@basic-ui/core 0.0.60 → 0.0.61
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/build/cjs/index.js.map +1 -1
- package/build/esm/Accordion/AccordionBody.d.ts.map +1 -1
- package/build/esm/Accordion/AccordionBody.js +6 -26
- package/build/esm/Accordion/AccordionBody.js.map +1 -1
- package/build/esm/Accordion/AccordionHeader.d.ts.map +1 -1
- package/build/esm/Accordion/AccordionHeader.js +21 -69
- package/build/esm/Accordion/AccordionHeader.js.map +1 -1
- package/build/esm/Accordion/AccordionItem.d.ts.map +1 -1
- package/build/esm/Accordion/AccordionItem.js +31 -18
- package/build/esm/Accordion/AccordionItem.js.map +1 -1
- package/build/esm/Accordion/context.d.ts +0 -8
- package/build/esm/Accordion/context.d.ts.map +1 -1
- package/build/esm/Accordion/context.js +0 -11
- package/build/esm/Accordion/context.js.map +1 -1
- package/build/esm/Accordion/scopeQuery.d.ts +1 -0
- package/build/esm/Accordion/scopeQuery.d.ts.map +1 -1
- package/build/esm/Accordion/scopeQuery.js +3 -0
- package/build/esm/Accordion/scopeQuery.js.map +1 -1
- package/build/esm/Collapsible/Collapsible.d.ts +13 -0
- package/build/esm/Collapsible/Collapsible.d.ts.map +1 -0
- package/build/esm/Collapsible/Collapsible.js +53 -0
- package/build/esm/Collapsible/Collapsible.js.map +1 -0
- package/build/esm/Collapsible/CollapsiblePanel.d.ts +10 -0
- package/build/esm/Collapsible/CollapsiblePanel.d.ts.map +1 -0
- package/build/esm/Collapsible/CollapsiblePanel.js +85 -0
- package/build/esm/Collapsible/CollapsiblePanel.js.map +1 -0
- package/build/esm/Collapsible/CollapsibleTrigger.d.ts +11 -0
- package/build/esm/Collapsible/CollapsibleTrigger.d.ts.map +1 -0
- package/build/esm/Collapsible/CollapsibleTrigger.js +51 -0
- package/build/esm/Collapsible/CollapsibleTrigger.js.map +1 -0
- package/build/esm/Collapsible/context.d.ts +16 -0
- package/build/esm/Collapsible/context.d.ts.map +1 -0
- package/build/esm/Collapsible/context.js +11 -0
- package/build/esm/Collapsible/context.js.map +1 -0
- package/build/esm/Collapsible/index.d.ts +4 -0
- package/build/esm/Collapsible/index.d.ts.map +1 -0
- package/build/esm/Collapsible/index.js +4 -0
- package/build/esm/Collapsible/index.js.map +1 -0
- package/build/esm/Menu/Menu.d.ts +3 -2
- package/build/esm/Menu/Menu.d.ts.map +1 -1
- package/build/esm/Menu/Menu.js +64 -4
- package/build/esm/Menu/Menu.js.map +1 -1
- package/build/esm/Menu/MenuButton.d.ts.map +1 -1
- package/build/esm/Menu/MenuButton.js +85 -8
- package/build/esm/Menu/MenuButton.js.map +1 -1
- package/build/esm/Menu/MenuItem.d.ts.map +1 -1
- package/build/esm/Menu/MenuItem.js +16 -4
- package/build/esm/Menu/MenuItem.js.map +1 -1
- package/build/esm/Menu/MenuList.d.ts.map +1 -1
- package/build/esm/Menu/MenuList.js +47 -12
- package/build/esm/Menu/MenuList.js.map +1 -1
- package/build/esm/Menu/MenuPopover.d.ts.map +1 -1
- package/build/esm/Menu/MenuPopover.js +12 -1
- package/build/esm/Menu/MenuPopover.js.map +1 -1
- package/build/esm/Menu/MenuSubmenuTrigger.d.ts +8 -0
- package/build/esm/Menu/MenuSubmenuTrigger.d.ts.map +1 -0
- package/build/esm/Menu/MenuSubmenuTrigger.js +131 -0
- package/build/esm/Menu/MenuSubmenuTrigger.js.map +1 -0
- package/build/esm/Menu/context.d.ts +13 -3
- package/build/esm/Menu/context.d.ts.map +1 -1
- package/build/esm/Menu/context.js +1 -0
- package/build/esm/Menu/context.js.map +1 -1
- package/build/esm/Menu/index.d.ts +3 -0
- package/build/esm/Menu/index.d.ts.map +1 -1
- package/build/esm/Menu/index.js +2 -0
- package/build/esm/Menu/index.js.map +1 -1
- package/build/esm/Menu/scope.d.ts +1 -0
- package/build/esm/Menu/scope.d.ts.map +1 -1
- package/build/esm/Menu/scope.js +2 -1
- package/build/esm/Menu/scope.js.map +1 -1
- package/build/esm/MenuBar/MenuBar.d.ts +11 -0
- package/build/esm/MenuBar/MenuBar.d.ts.map +1 -0
- package/build/esm/MenuBar/MenuBar.js +153 -0
- package/build/esm/MenuBar/MenuBar.js.map +1 -0
- package/build/esm/MenuBar/context.d.ts +29 -0
- package/build/esm/MenuBar/context.d.ts.map +1 -0
- package/build/esm/MenuBar/context.js +7 -0
- package/build/esm/MenuBar/context.js.map +1 -0
- package/build/esm/MenuBar/index.d.ts +2 -0
- package/build/esm/MenuBar/index.d.ts.map +1 -0
- package/build/esm/MenuBar/index.js +2 -0
- package/build/esm/MenuBar/index.js.map +1 -0
- package/build/esm/Slider/Slider.d.ts +47 -1
- package/build/esm/Slider/Slider.d.ts.map +1 -1
- package/build/esm/Slider/Slider.js +91 -5
- package/build/esm/Slider/Slider.js.map +1 -1
- package/build/esm/ToggleGroup/ToggleGroup.d.ts +40 -0
- package/build/esm/ToggleGroup/ToggleGroup.d.ts.map +1 -0
- package/build/esm/ToggleGroup/ToggleGroup.js +113 -0
- package/build/esm/ToggleGroup/ToggleGroup.js.map +1 -0
- package/build/esm/ToggleGroup/ToggleGroupContext.d.ts +10 -0
- package/build/esm/ToggleGroup/ToggleGroupContext.d.ts.map +1 -0
- package/build/esm/ToggleGroup/ToggleGroupContext.js +6 -0
- package/build/esm/ToggleGroup/ToggleGroupContext.js.map +1 -0
- package/build/esm/ToggleGroup/index.d.ts +3 -0
- package/build/esm/ToggleGroup/index.d.ts.map +1 -0
- package/build/esm/ToggleGroup/index.js +3 -0
- package/build/esm/ToggleGroup/index.js.map +1 -0
- package/build/esm/Tree/Tree.d.ts +3 -0
- package/build/esm/Tree/Tree.d.ts.map +1 -0
- package/build/esm/Tree/Tree.js +730 -0
- package/build/esm/Tree/Tree.js.map +1 -0
- package/build/esm/Tree/TreeHeader.d.ts +3 -0
- package/build/esm/Tree/TreeHeader.d.ts.map +1 -0
- package/build/esm/Tree/TreeHeader.js +5 -0
- package/build/esm/Tree/TreeHeader.js.map +1 -0
- package/build/esm/Tree/TreeItem.d.ts +3 -0
- package/build/esm/Tree/TreeItem.d.ts.map +1 -0
- package/build/esm/Tree/TreeItem.js +5 -0
- package/build/esm/Tree/TreeItem.js.map +1 -0
- package/build/esm/Tree/TreeItemContent.d.ts +3 -0
- package/build/esm/Tree/TreeItemContent.d.ts.map +1 -0
- package/build/esm/Tree/TreeItemContent.js +69 -0
- package/build/esm/Tree/TreeItemContent.js.map +1 -0
- package/build/esm/Tree/TreeSection.d.ts +3 -0
- package/build/esm/Tree/TreeSection.d.ts.map +1 -0
- package/build/esm/Tree/TreeSection.js +5 -0
- package/build/esm/Tree/TreeSection.js.map +1 -0
- package/build/esm/Tree/collection.d.ts +18 -0
- package/build/esm/Tree/collection.d.ts.map +1 -0
- package/build/esm/Tree/collection.js +252 -0
- package/build/esm/Tree/collection.js.map +1 -0
- package/build/esm/Tree/context.d.ts +3 -0
- package/build/esm/Tree/context.d.ts.map +1 -0
- package/build/esm/Tree/context.js +3 -0
- package/build/esm/Tree/context.js.map +1 -0
- package/build/esm/Tree/index.d.ts +8 -0
- package/build/esm/Tree/index.d.ts.map +1 -0
- package/build/esm/Tree/index.js +7 -0
- package/build/esm/Tree/index.js.map +1 -0
- package/build/esm/Tree/types.d.ts +128 -0
- package/build/esm/Tree/types.d.ts.map +1 -0
- package/build/esm/Tree/types.js +2 -0
- package/build/esm/Tree/types.js.map +1 -0
- package/build/esm/hooks/index.d.ts +1 -0
- package/build/esm/hooks/index.d.ts.map +1 -1
- package/build/esm/hooks/index.js +1 -0
- package/build/esm/hooks/index.js.map +1 -1
- package/build/esm/hooks/useTransitionStatus.d.ts +7 -0
- package/build/esm/hooks/useTransitionStatus.d.ts.map +1 -0
- package/build/esm/hooks/useTransitionStatus.js +48 -0
- package/build/esm/hooks/useTransitionStatus.js.map +1 -0
- package/build/esm/index.d.ts +5 -0
- package/build/esm/index.d.ts.map +1 -1
- package/build/esm/index.js +5 -0
- package/build/esm/index.js.map +1 -1
- package/build/esm/toggle/Toggle.d.ts +28 -0
- package/build/esm/toggle/Toggle.d.ts.map +1 -0
- package/build/esm/toggle/Toggle.js +55 -0
- package/build/esm/toggle/Toggle.js.map +1 -0
- package/build/esm/toggle/index.d.ts +2 -0
- package/build/esm/toggle/index.d.ts.map +1 -0
- package/build/esm/toggle/index.js +2 -0
- package/build/esm/toggle/index.js.map +1 -0
- package/build/esm/utils/assign-ref.d.ts +3 -3
- package/build/esm/utils/assign-ref.d.ts.map +1 -1
- package/build/esm/utils/assign-ref.js +1 -1
- package/build/esm/utils/assign-ref.js.map +1 -1
- package/build/tsconfig-build.tsbuildinfo +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -4
- package/src/Accordion/AccordionBody.tsx +6 -35
- package/src/Accordion/AccordionHeader.tsx +29 -103
- package/src/Accordion/AccordionItem.tsx +40 -29
- package/src/Accordion/context.ts +0 -18
- package/src/Accordion/scopeQuery.ts +4 -0
- package/src/Collapsible/Collapsible.story.tsx +153 -0
- package/src/Collapsible/Collapsible.tsx +79 -0
- package/src/Collapsible/CollapsiblePanel.tsx +103 -0
- package/src/Collapsible/CollapsibleTrigger.tsx +60 -0
- package/src/Collapsible/context.ts +28 -0
- package/src/Collapsible/index.ts +3 -0
- package/src/Menu/Menu.story.tsx +70 -1
- package/src/Menu/Menu.tsx +141 -65
- package/src/Menu/MenuButton.tsx +115 -9
- package/src/Menu/MenuItem.tsx +20 -3
- package/src/Menu/MenuList.tsx +50 -13
- package/src/Menu/MenuPopover.tsx +12 -2
- package/src/Menu/MenuSubmenuTrigger.tsx +167 -0
- package/src/Menu/context.ts +20 -10
- package/src/Menu/index.ts +3 -0
- package/src/Menu/scope.ts +4 -1
- package/src/Menu/styles.css +57 -22
- package/src/MenuBar/MenuBar.story.tsx +92 -0
- package/src/MenuBar/MenuBar.tsx +236 -0
- package/src/MenuBar/context.ts +46 -0
- package/src/MenuBar/index.ts +1 -0
- package/src/MenuBar/styles.css +78 -0
- package/src/Slider/Slider.story.tsx +1 -1
- package/src/Slider/Slider.tsx +145 -8
- package/src/Toggle/Toggle.story.tsx +42 -0
- package/src/Toggle/Toggle.tsx +95 -0
- package/src/Toggle/index.ts +1 -0
- package/src/Toggle/styles.css +39 -0
- package/src/ToggleGroup/ToggleGroup.story.tsx +86 -0
- package/src/ToggleGroup/ToggleGroup.tsx +185 -0
- package/src/ToggleGroup/ToggleGroupContext.ts +17 -0
- package/src/ToggleGroup/index.ts +2 -0
- package/src/ToggleGroup/styles.css +66 -0
- package/src/Tree/Tree.story.tsx +221 -0
- package/src/Tree/Tree.tsx +1081 -0
- package/src/Tree/TreeHeader.tsx +9 -0
- package/src/Tree/TreeItem.tsx +9 -0
- package/src/Tree/TreeItemContent.tsx +91 -0
- package/src/Tree/TreeSection.tsx +9 -0
- package/src/Tree/collection.tsx +371 -0
- package/src/Tree/context.ts +6 -0
- package/src/Tree/index.ts +7 -0
- package/src/Tree/styles.css +135 -0
- package/src/Tree/types.ts +161 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useTransitionStatus.ts +65 -0
- package/src/index.ts +5 -0
- package/src/utils/assign-ref.ts +4 -4
package/src/Menu/MenuList.tsx
CHANGED
|
@@ -12,7 +12,7 @@ import { useMenuContext, MenuListProvider } from './context';
|
|
|
12
12
|
import { assignMultipleRefs } from '../utils/assign-ref';
|
|
13
13
|
import { useOnClickOutside } from '../hooks/useOnClickOutside';
|
|
14
14
|
import { useScope } from '../hooks';
|
|
15
|
-
import { queryScope } from './scope';
|
|
15
|
+
import { MENU_SUBMENU_TRIGGER_ATTR, queryScope } from './scope';
|
|
16
16
|
import { getCircularIndex, wrapEvent } from '../utils';
|
|
17
17
|
|
|
18
18
|
const useEnhancedEffect =
|
|
@@ -44,10 +44,12 @@ export const MenuList = forwardRef<HTMLUListElement, MenuListProps>(
|
|
|
44
44
|
const {
|
|
45
45
|
menuListIdRef,
|
|
46
46
|
buttonRef,
|
|
47
|
-
|
|
47
|
+
closeMenu,
|
|
48
48
|
openWithArrowKeyRef,
|
|
49
49
|
open,
|
|
50
50
|
isContextMenu,
|
|
51
|
+
menuBar,
|
|
52
|
+
menuBarMenuId,
|
|
51
53
|
} = useMenuContext();
|
|
52
54
|
|
|
53
55
|
const [navigationItem, setNavigationItem] = useState<
|
|
@@ -59,6 +61,15 @@ export const MenuList = forwardRef<HTMLUListElement, MenuListProps>(
|
|
|
59
61
|
const menuListRef = useRef<HTMLUListElement | null>(null);
|
|
60
62
|
|
|
61
63
|
const scope = useScope<HTMLLIElement, HTMLUListElement>(menuListRef);
|
|
64
|
+
const getAllItems = useCallback(
|
|
65
|
+
() =>
|
|
66
|
+
scope.current.queryAllNodes(
|
|
67
|
+
(type, props, instance) =>
|
|
68
|
+
queryScope(type, props) &&
|
|
69
|
+
instance.closest('[data-menu-list]') === menuListRef.current
|
|
70
|
+
),
|
|
71
|
+
[scope]
|
|
72
|
+
);
|
|
62
73
|
|
|
63
74
|
const onNavigate = (el: HTMLElement) => {
|
|
64
75
|
el.focus({ preventScroll: true });
|
|
@@ -69,7 +80,7 @@ export const MenuList = forwardRef<HTMLUListElement, MenuListProps>(
|
|
|
69
80
|
|
|
70
81
|
useEnhancedEffect(() => {
|
|
71
82
|
if (!mounted) {
|
|
72
|
-
const allItems =
|
|
83
|
+
const allItems = getAllItems();
|
|
73
84
|
let index = getCircularIndex(
|
|
74
85
|
openWithArrowKeyRef.current === 'ArrowUp' ? -1 : 0,
|
|
75
86
|
allItems.length
|
|
@@ -107,7 +118,7 @@ export const MenuList = forwardRef<HTMLUListElement, MenuListProps>(
|
|
|
107
118
|
navigationItem,
|
|
108
119
|
onNavigate,
|
|
109
120
|
openWithArrowKeyRef,
|
|
110
|
-
|
|
121
|
+
getAllItems,
|
|
111
122
|
defaultActiveItemValue,
|
|
112
123
|
]);
|
|
113
124
|
|
|
@@ -122,7 +133,7 @@ export const MenuList = forwardRef<HTMLUListElement, MenuListProps>(
|
|
|
122
133
|
}
|
|
123
134
|
|
|
124
135
|
if (e.button === 0) {
|
|
125
|
-
|
|
136
|
+
closeMenu(e);
|
|
126
137
|
}
|
|
127
138
|
} else {
|
|
128
139
|
if (
|
|
@@ -130,24 +141,52 @@ export const MenuList = forwardRef<HTMLUListElement, MenuListProps>(
|
|
|
130
141
|
e.target !== buttonRef.current &&
|
|
131
142
|
!buttonRef.current?.contains(e.target)
|
|
132
143
|
) {
|
|
133
|
-
|
|
144
|
+
closeMenu(e);
|
|
134
145
|
}
|
|
135
146
|
}
|
|
136
147
|
e.preventDefault();
|
|
137
148
|
},
|
|
138
|
-
[buttonRef, isContextMenu,
|
|
149
|
+
[buttonRef, isContextMenu, closeMenu]
|
|
139
150
|
);
|
|
140
151
|
|
|
141
152
|
useOnClickOutside(menuListRef, handleClickOutside, open);
|
|
142
153
|
|
|
143
154
|
function handleKeyDown(e: KeyboardEvent<HTMLUListElement>) {
|
|
155
|
+
const isHorizontalMenuBarMenu =
|
|
156
|
+
menuBar && menuBarMenuId && menuBar.orientation === 'horizontal';
|
|
157
|
+
|
|
144
158
|
switch (e.key) {
|
|
145
159
|
case 'Escape':
|
|
146
160
|
case 'Tab': {
|
|
147
|
-
|
|
161
|
+
closeMenu(e, { focusTrigger: true });
|
|
148
162
|
e.preventDefault(); // prevents focusing on next element, because we will be handling it
|
|
149
163
|
itemSearchStr.current = '';
|
|
150
|
-
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case 'ArrowRight': {
|
|
167
|
+
if (isHorizontalMenuBarMenu) {
|
|
168
|
+
menuBar!.moveFocus(menuBarMenuId!, 1, e, {
|
|
169
|
+
open: true,
|
|
170
|
+
focusKey: 'ArrowDown',
|
|
171
|
+
});
|
|
172
|
+
e.preventDefault();
|
|
173
|
+
itemSearchStr.current = '';
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case 'ArrowLeft': {
|
|
178
|
+
if (buttonRef.current?.hasAttribute(MENU_SUBMENU_TRIGGER_ATTR)) {
|
|
179
|
+
closeMenu(e, { focusTrigger: true });
|
|
180
|
+
e.preventDefault();
|
|
181
|
+
itemSearchStr.current = '';
|
|
182
|
+
} else if (isHorizontalMenuBarMenu) {
|
|
183
|
+
menuBar!.moveFocus(menuBarMenuId!, -1, e, {
|
|
184
|
+
open: true,
|
|
185
|
+
focusKey: 'ArrowDown',
|
|
186
|
+
});
|
|
187
|
+
e.preventDefault();
|
|
188
|
+
itemSearchStr.current = '';
|
|
189
|
+
}
|
|
151
190
|
break;
|
|
152
191
|
}
|
|
153
192
|
case 'Home':
|
|
@@ -156,7 +195,7 @@ export const MenuList = forwardRef<HTMLUListElement, MenuListProps>(
|
|
|
156
195
|
case 'ArrowUp':
|
|
157
196
|
e.preventDefault();
|
|
158
197
|
itemSearchStr.current = '';
|
|
159
|
-
const allItems =
|
|
198
|
+
const allItems = getAllItems();
|
|
160
199
|
const currentIndex = allItems.findIndex((e) => e === navigationItem);
|
|
161
200
|
if (allItems.length === 0) {
|
|
162
201
|
return;
|
|
@@ -196,9 +235,7 @@ export const MenuList = forwardRef<HTMLUListElement, MenuListProps>(
|
|
|
196
235
|
itemSearchStr.current = '';
|
|
197
236
|
}, 500);
|
|
198
237
|
|
|
199
|
-
const allItems =
|
|
200
|
-
? scope.current.queryAllNodes(queryScope)
|
|
201
|
-
: [];
|
|
238
|
+
const allItems = getAllItems();
|
|
202
239
|
const currentIndex = allItems.findIndex(
|
|
203
240
|
(e) => e === navigationItem
|
|
204
241
|
);
|
package/src/Menu/MenuPopover.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { forwardRef } from 'react';
|
|
|
4
4
|
import type { PopperProps } from '../Popper';
|
|
5
5
|
import { Popper } from '../Popper';
|
|
6
6
|
import { useMenuContext } from './context';
|
|
7
|
+
import { MENU_SUBMENU_TRIGGER_ATTR } from './scope';
|
|
7
8
|
|
|
8
9
|
export interface MenuPopoverProps extends Omit<PopperProps, 'anchorEl'> {
|
|
9
10
|
as?: ElementType<any>;
|
|
@@ -13,8 +14,17 @@ export interface MenuPopoverProps extends Omit<PopperProps, 'anchorEl'> {
|
|
|
13
14
|
|
|
14
15
|
export const MenuPopover = forwardRef<HTMLDivElement, MenuPopoverProps>(
|
|
15
16
|
function MenuPopover(props, forwardedRef) {
|
|
16
|
-
const { as = 'div', innerAs, ...otherProps } = props;
|
|
17
|
+
const { as = 'div', innerAs, placement, ...otherProps } = props;
|
|
17
18
|
const { buttonRef, open, offsetFn, isContextMenu } = useMenuContext();
|
|
19
|
+
const getDefaultPlacement = () => {
|
|
20
|
+
if (buttonRef.current?.hasAttribute(MENU_SUBMENU_TRIGGER_ATTR)) {
|
|
21
|
+
return 'right-start';
|
|
22
|
+
}
|
|
23
|
+
if (isContextMenu.current) {
|
|
24
|
+
return 'bottom-start';
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
};
|
|
18
28
|
|
|
19
29
|
if (!open) {
|
|
20
30
|
return null;
|
|
@@ -27,7 +37,7 @@ export const MenuPopover = forwardRef<HTMLDivElement, MenuPopoverProps>(
|
|
|
27
37
|
ref={forwardedRef}
|
|
28
38
|
anchorEl={buttonRef}
|
|
29
39
|
offsetFn={offsetFn}
|
|
30
|
-
placement={
|
|
40
|
+
placement={placement ?? getDefaultPlacement()}
|
|
31
41
|
{...otherProps}
|
|
32
42
|
/>
|
|
33
43
|
);
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FocusEventHandler,
|
|
3
|
+
KeyboardEvent,
|
|
4
|
+
KeyboardEventHandler,
|
|
5
|
+
LiHTMLAttributes,
|
|
6
|
+
ElementType,
|
|
7
|
+
MouseEvent,
|
|
8
|
+
MouseEventHandler,
|
|
9
|
+
PointerEvent,
|
|
10
|
+
PointerEventHandler,
|
|
11
|
+
} from 'react';
|
|
12
|
+
import { forwardRef, useEffect, useId, useRef } from 'react';
|
|
13
|
+
|
|
14
|
+
import { useMenuContext, useMenuListContext } from './context';
|
|
15
|
+
import { MENU_SUBMENU_TRIGGER_ATTR } from './scope';
|
|
16
|
+
import { assignMultipleRefs } from '../utils/assign-ref';
|
|
17
|
+
import { wrapEvent } from '../utils';
|
|
18
|
+
|
|
19
|
+
export interface MenuSubmenuTriggerProps
|
|
20
|
+
extends LiHTMLAttributes<HTMLLIElement> {
|
|
21
|
+
as?: ElementType<any>;
|
|
22
|
+
innerAs?: ElementType<any>;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const MenuSubmenuTrigger = forwardRef<any, MenuSubmenuTriggerProps>(
|
|
27
|
+
function MenuSubmenuTrigger(props, forwardedRef) {
|
|
28
|
+
const {
|
|
29
|
+
as: Comp = 'li',
|
|
30
|
+
innerAs,
|
|
31
|
+
disabled,
|
|
32
|
+
onClick,
|
|
33
|
+
onFocus,
|
|
34
|
+
onKeyDown,
|
|
35
|
+
onMouseMove,
|
|
36
|
+
onPointerUp,
|
|
37
|
+
...otherProps
|
|
38
|
+
} = props;
|
|
39
|
+
const {
|
|
40
|
+
buttonRef,
|
|
41
|
+
menuListIdRef,
|
|
42
|
+
open,
|
|
43
|
+
onChange,
|
|
44
|
+
openWithArrowKeyRef,
|
|
45
|
+
} = useMenuContext();
|
|
46
|
+
const { navigationItem, onNavigate } = useMenuListContext();
|
|
47
|
+
const ref = useRef<HTMLLIElement | null>(null);
|
|
48
|
+
const itemId = useId();
|
|
49
|
+
|
|
50
|
+
const isActive = ref.current && ref.current === navigationItem;
|
|
51
|
+
|
|
52
|
+
const setTrigger = () => {
|
|
53
|
+
if (ref.current) {
|
|
54
|
+
buttonRef.current = ref.current;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const createMenuTriggerEvent = () =>
|
|
59
|
+
({
|
|
60
|
+
// This close path is only used when roving focus leaves the trigger.
|
|
61
|
+
// Internal state transitions only need currentTarget/target plus
|
|
62
|
+
// preventDefault/defaultPrevented for wrapEvent compatibility.
|
|
63
|
+
currentTarget: ref.current,
|
|
64
|
+
target: ref.current,
|
|
65
|
+
preventDefault() {},
|
|
66
|
+
defaultPrevented: false,
|
|
67
|
+
}) as unknown as Parameters<typeof onChange>[0];
|
|
68
|
+
|
|
69
|
+
const openSubmenu = (
|
|
70
|
+
e:
|
|
71
|
+
| MouseEvent<HTMLLIElement>
|
|
72
|
+
| KeyboardEvent<HTMLLIElement>
|
|
73
|
+
| PointerEvent<HTMLLIElement>,
|
|
74
|
+
focusFirstItem = false
|
|
75
|
+
) => {
|
|
76
|
+
if (disabled || !ref.current) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
setTrigger();
|
|
80
|
+
onNavigate && onNavigate(ref.current);
|
|
81
|
+
if (focusFirstItem) {
|
|
82
|
+
openWithArrowKeyRef.current = 'ArrowDown';
|
|
83
|
+
}
|
|
84
|
+
onChange(e, true);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (!open || !ref.current) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (navigationItem && navigationItem !== ref.current) {
|
|
92
|
+
onChange(createMenuTriggerEvent(), false);
|
|
93
|
+
}
|
|
94
|
+
}, [navigationItem, onChange, open]);
|
|
95
|
+
|
|
96
|
+
const handleClick = (e: MouseEvent<HTMLLIElement>) => {
|
|
97
|
+
openSubmenu(e);
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const handleFocus: FocusEventHandler<HTMLLIElement> = () => {
|
|
102
|
+
if (!disabled && ref.current && ref.current !== navigationItem) {
|
|
103
|
+
onNavigate && onNavigate(ref.current);
|
|
104
|
+
}
|
|
105
|
+
setTrigger();
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const handleKeyDown: KeyboardEventHandler<HTMLLIElement> = (e) => {
|
|
109
|
+
switch (e.key) {
|
|
110
|
+
case ' ':
|
|
111
|
+
case 'Enter':
|
|
112
|
+
openSubmenu(e, true);
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
break;
|
|
115
|
+
case 'ArrowRight':
|
|
116
|
+
if (!open) {
|
|
117
|
+
openSubmenu(e, true);
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const handleMouseMove: MouseEventHandler<HTMLLIElement> = (e) => {
|
|
125
|
+
if (!disabled && ref.current && ref.current !== navigationItem) {
|
|
126
|
+
onNavigate && onNavigate(ref.current);
|
|
127
|
+
}
|
|
128
|
+
if (!disabled && !open) {
|
|
129
|
+
openSubmenu(e);
|
|
130
|
+
} else {
|
|
131
|
+
setTrigger();
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const handlePointerUp: PointerEventHandler<HTMLLIElement> = (e) => {
|
|
136
|
+
if (e.pointerType === 'touch') {
|
|
137
|
+
openSubmenu(e);
|
|
138
|
+
e.preventDefault();
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<Comp
|
|
144
|
+
ref={assignMultipleRefs(ref, forwardedRef)}
|
|
145
|
+
as={innerAs}
|
|
146
|
+
id={disabled ? undefined : itemId}
|
|
147
|
+
{...{ [MENU_SUBMENU_TRIGGER_ATTR]: '' }}
|
|
148
|
+
data-menu-submenu-trigger=""
|
|
149
|
+
data-highlighted={isActive ? '' : undefined}
|
|
150
|
+
role="menuitem"
|
|
151
|
+
aria-haspopup="menu"
|
|
152
|
+
aria-controls={menuListIdRef.current}
|
|
153
|
+
aria-expanded={open ? true : undefined}
|
|
154
|
+
onClick={wrapEvent(onClick, handleClick)}
|
|
155
|
+
tabIndex={disabled ? -1 : 0}
|
|
156
|
+
onFocus={wrapEvent(onFocus, handleFocus)}
|
|
157
|
+
onKeyDown={wrapEvent(onKeyDown, handleKeyDown)}
|
|
158
|
+
onMouseMove={wrapEvent(onMouseMove, handleMouseMove)}
|
|
159
|
+
onPointerUp={wrapEvent(onPointerUp, handlePointerUp)}
|
|
160
|
+
data-disabled={disabled ? '' : undefined}
|
|
161
|
+
aria-disabled={disabled ? '' : undefined}
|
|
162
|
+
disabled={disabled}
|
|
163
|
+
{...otherProps}
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
);
|
package/src/Menu/context.ts
CHANGED
|
@@ -4,34 +4,44 @@ import type {
|
|
|
4
4
|
KeyboardEvent,
|
|
5
5
|
MouseEvent,
|
|
6
6
|
MutableRefObject,
|
|
7
|
-
PointerEvent,
|
|
7
|
+
PointerEvent as ReactPointerEvent,
|
|
8
8
|
SetStateAction,
|
|
9
9
|
} from 'react';
|
|
10
10
|
import { createContext, useContext } from 'react';
|
|
11
11
|
|
|
12
|
+
import type { MenuBarContextProps } from '../MenuBar/context';
|
|
13
|
+
|
|
12
14
|
export type ItemObject = { text: string; value: any; id: string | undefined };
|
|
15
|
+
export type MenuTriggerEvent =
|
|
16
|
+
| KeyboardEvent<any>
|
|
17
|
+
| MouseEvent<any>
|
|
18
|
+
| ReactPointerEvent<any>
|
|
19
|
+
| PointerEvent;
|
|
13
20
|
|
|
14
21
|
// MenuRoot
|
|
15
22
|
export interface MenuContextProps {
|
|
16
23
|
buttonRef: MutableRefObject<HTMLElement | null>;
|
|
17
24
|
menuListIdRef: MutableRefObject<undefined | string>;
|
|
18
25
|
openWithArrowKeyRef: MutableRefObject<string | null>;
|
|
19
|
-
onChange: (
|
|
20
|
-
e:
|
|
21
|
-
| KeyboardEvent<HTMLElement>
|
|
22
|
-
| MouseEvent<HTMLElement>
|
|
23
|
-
| PointerEvent<HTMLElement>,
|
|
24
|
-
isOpen: boolean
|
|
25
|
-
) => void;
|
|
26
|
+
onChange: (e: MenuTriggerEvent, isOpen: boolean) => void;
|
|
26
27
|
open: boolean;
|
|
27
28
|
offsetFn: OffsetsFunction | undefined;
|
|
28
29
|
setOffsetFn: Dispatch<SetStateAction<OffsetsFunction | undefined>>;
|
|
29
30
|
isContextMenu: MutableRefObject<boolean>;
|
|
31
|
+
parentMenu: MenuContextProps | null;
|
|
32
|
+
menuBar: MenuBarContextProps | null;
|
|
33
|
+
menuBarMenuId: string | null;
|
|
34
|
+
closeMenu: (
|
|
35
|
+
e: MenuTriggerEvent,
|
|
36
|
+
options?: { focusTrigger?: boolean }
|
|
37
|
+
) => void;
|
|
38
|
+
closeAllMenus: (e: MenuTriggerEvent) => void;
|
|
30
39
|
}
|
|
31
40
|
|
|
32
|
-
const menuContext = createContext<MenuContextProps>(null
|
|
41
|
+
const menuContext = createContext<MenuContextProps | null>(null);
|
|
33
42
|
export const { Provider: MenuProvider } = menuContext;
|
|
34
|
-
export const useMenuContext = () => useContext(menuContext);
|
|
43
|
+
export const useMenuContext = () => useContext(menuContext) as MenuContextProps;
|
|
44
|
+
export const useOptionalMenuContext = () => useContext(menuContext);
|
|
35
45
|
|
|
36
46
|
// MenuList
|
|
37
47
|
export interface MenuListContextProps {
|
package/src/Menu/index.ts
CHANGED
|
@@ -4,3 +4,6 @@ export * from './ContextMenuTrigger';
|
|
|
4
4
|
export * from './MenuItem';
|
|
5
5
|
export * from './MenuList';
|
|
6
6
|
export * from './MenuPopover';
|
|
7
|
+
export { Menu as MenuSubmenu } from './Menu';
|
|
8
|
+
export type { MenuProps as MenuSubmenuProps } from './Menu';
|
|
9
|
+
export * from './MenuSubmenuTrigger';
|
package/src/Menu/scope.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
export const MENU_SUBMENU_TRIGGER_ATTR = 'data-menu-submenu-trigger';
|
|
2
|
+
|
|
1
3
|
export function queryScope(type: string, props: Record<string, unknown>) {
|
|
2
4
|
return (
|
|
3
|
-
props['data-menu-item'] === ''
|
|
5
|
+
(props['data-menu-item'] === '' ||
|
|
6
|
+
props[MENU_SUBMENU_TRIGGER_ATTR] === '') &&
|
|
4
7
|
props['data-disabled'] !== '' &&
|
|
5
8
|
props['data-disabled'] !== true
|
|
6
9
|
);
|
package/src/Menu/styles.css
CHANGED
|
@@ -1,32 +1,71 @@
|
|
|
1
|
-
[data-menu-
|
|
2
|
-
|
|
1
|
+
[data-menu-button] {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
height: 2rem;
|
|
4
|
+
margin: 0;
|
|
5
|
+
padding: 0 0.75rem;
|
|
6
|
+
border: 0;
|
|
7
|
+
background: transparent;
|
|
8
|
+
color: #1f1f1f;
|
|
9
|
+
font: sans-serif;
|
|
10
|
+
cursor: default;
|
|
11
|
+
user-select: none;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
[data-menu-button]:hover:not([disabled]),
|
|
15
|
+
[data-menu-button]:focus-visible,
|
|
16
|
+
[data-menu-button][aria-expanded='true'] {
|
|
17
|
+
background: #f0f0f0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
[data-menu-list] {
|
|
21
|
+
font-family: sans-serif;
|
|
22
|
+
min-width: 180px;
|
|
23
|
+
margin: 0;
|
|
24
|
+
padding: 4px 0;
|
|
25
|
+
border: 1px solid #d8d8d8;
|
|
26
|
+
background: white;
|
|
27
|
+
color: #1f1f1f;
|
|
28
|
+
box-shadow: 4px 4px 0 rgb(0 0 0 / 10%);
|
|
3
29
|
list-style: none;
|
|
4
|
-
cursor: pointer;
|
|
5
30
|
}
|
|
6
31
|
|
|
7
|
-
[data-menu-
|
|
8
|
-
|
|
32
|
+
[data-menu-list]:focus {
|
|
33
|
+
outline: 0;
|
|
9
34
|
}
|
|
10
35
|
|
|
11
|
-
[data-menu-
|
|
12
|
-
|
|
36
|
+
[data-menu-list] > hr {
|
|
37
|
+
height: 1px;
|
|
38
|
+
margin: 4px;
|
|
39
|
+
border: 0;
|
|
40
|
+
background: #d8d8d8;
|
|
13
41
|
}
|
|
14
42
|
|
|
15
|
-
[data-menu-item]
|
|
16
|
-
|
|
43
|
+
[data-menu-item],
|
|
44
|
+
[data-menu-submenu-trigger] {
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
gap: 1rem;
|
|
48
|
+
padding: 0.5rem 1rem;
|
|
49
|
+
outline: 0;
|
|
50
|
+
list-style: none;
|
|
51
|
+
cursor: default;
|
|
52
|
+
user-select: none;
|
|
17
53
|
}
|
|
18
54
|
|
|
19
|
-
[data-menu-
|
|
20
|
-
|
|
21
|
-
color: #777;
|
|
22
|
-
cursor: not-allowed;
|
|
55
|
+
[data-menu-submenu-trigger] {
|
|
56
|
+
padding-right: 0.5rem;
|
|
23
57
|
}
|
|
24
58
|
|
|
25
|
-
[data-menu-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
59
|
+
[data-menu-item][data-highlighted],
|
|
60
|
+
[data-menu-submenu-trigger][data-highlighted] {
|
|
61
|
+
background: #1f1f1f;
|
|
62
|
+
color: white;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
[data-menu-item][data-disabled],
|
|
66
|
+
[data-menu-submenu-trigger][data-disabled] {
|
|
67
|
+
color: #777;
|
|
68
|
+
cursor: not-allowed;
|
|
30
69
|
}
|
|
31
70
|
|
|
32
71
|
[data-popper-placement='top'] [data-menu-list] {
|
|
@@ -36,7 +75,3 @@
|
|
|
36
75
|
[data-popper-placement='bottom'] [data-menu-list] {
|
|
37
76
|
transform-origin: top center;
|
|
38
77
|
}
|
|
39
|
-
|
|
40
|
-
[data-menu-list]:focus {
|
|
41
|
-
outline: none;
|
|
42
|
-
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Menu,
|
|
5
|
+
MenuButton,
|
|
6
|
+
MenuItem,
|
|
7
|
+
MenuList,
|
|
8
|
+
MenuPopover,
|
|
9
|
+
MenuSubmenu,
|
|
10
|
+
MenuSubmenuTrigger,
|
|
11
|
+
} from '../Menu';
|
|
12
|
+
import { MenuBar } from './MenuBar';
|
|
13
|
+
import './styles.css';
|
|
14
|
+
|
|
15
|
+
export default {
|
|
16
|
+
title: 'components/MenuBar',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const Divider = () => <li role="separator" />;
|
|
20
|
+
|
|
21
|
+
const MenuEntry = ({ children }: { children: ReactNode }) => (
|
|
22
|
+
<MenuItem>{children}</MenuItem>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export const Basic = () => (
|
|
26
|
+
<div className="MenuBarStory-frame">
|
|
27
|
+
<MenuBar>
|
|
28
|
+
<Menu>
|
|
29
|
+
<MenuButton>File</MenuButton>
|
|
30
|
+
<MenuPopover>
|
|
31
|
+
<MenuList>
|
|
32
|
+
<MenuEntry>New</MenuEntry>
|
|
33
|
+
<MenuEntry>Open</MenuEntry>
|
|
34
|
+
<MenuEntry>Save</MenuEntry>
|
|
35
|
+
<MenuSubmenu>
|
|
36
|
+
<MenuSubmenuTrigger>
|
|
37
|
+
<span>Export</span>
|
|
38
|
+
<span aria-hidden="true">›</span>
|
|
39
|
+
<MenuPopover>
|
|
40
|
+
<MenuList>
|
|
41
|
+
<MenuEntry>Email</MenuEntry>
|
|
42
|
+
<MenuEntry>Copy link</MenuEntry>
|
|
43
|
+
<MenuEntry>Duplicate</MenuEntry>
|
|
44
|
+
</MenuList>
|
|
45
|
+
</MenuPopover>
|
|
46
|
+
</MenuSubmenuTrigger>
|
|
47
|
+
</MenuSubmenu>
|
|
48
|
+
<Divider />
|
|
49
|
+
<MenuEntry>Archive</MenuEntry>
|
|
50
|
+
</MenuList>
|
|
51
|
+
</MenuPopover>
|
|
52
|
+
</Menu>
|
|
53
|
+
|
|
54
|
+
<Menu>
|
|
55
|
+
<MenuButton>Edit</MenuButton>
|
|
56
|
+
<MenuPopover>
|
|
57
|
+
<MenuList>
|
|
58
|
+
<MenuEntry>Undo</MenuEntry>
|
|
59
|
+
<MenuEntry>Redo</MenuEntry>
|
|
60
|
+
<MenuEntry>Cut</MenuEntry>
|
|
61
|
+
<MenuEntry>Copy</MenuEntry>
|
|
62
|
+
<MenuEntry>Paste</MenuEntry>
|
|
63
|
+
</MenuList>
|
|
64
|
+
</MenuPopover>
|
|
65
|
+
</Menu>
|
|
66
|
+
|
|
67
|
+
<Menu>
|
|
68
|
+
<MenuButton>View</MenuButton>
|
|
69
|
+
<MenuPopover>
|
|
70
|
+
<MenuList>
|
|
71
|
+
<MenuEntry>Zoom In</MenuEntry>
|
|
72
|
+
<MenuEntry>Zoom Out</MenuEntry>
|
|
73
|
+
<MenuEntry>Reset Zoom</MenuEntry>
|
|
74
|
+
<MenuEntry>Full Screen</MenuEntry>
|
|
75
|
+
<MenuSubmenu>
|
|
76
|
+
<MenuSubmenuTrigger>
|
|
77
|
+
<span>Layout</span>
|
|
78
|
+
<span aria-hidden="true">›</span>
|
|
79
|
+
<MenuPopover>
|
|
80
|
+
<MenuList>
|
|
81
|
+
<MenuEntry>Single page</MenuEntry>
|
|
82
|
+
<MenuEntry>Two pages</MenuEntry>
|
|
83
|
+
</MenuList>
|
|
84
|
+
</MenuPopover>
|
|
85
|
+
</MenuSubmenuTrigger>
|
|
86
|
+
</MenuSubmenu>
|
|
87
|
+
</MenuList>
|
|
88
|
+
</MenuPopover>
|
|
89
|
+
</Menu>
|
|
90
|
+
</MenuBar>
|
|
91
|
+
</div>
|
|
92
|
+
);
|