@octanejs/radix 0.1.2
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/LICENSE +21 -0
- package/README.md +100 -0
- package/package.json +45 -0
- package/src/AccessibleIcon.ts +26 -0
- package/src/Accordion.ts +282 -0
- package/src/AlertDialog.ts +110 -0
- package/src/Arrow.ts +21 -0
- package/src/AspectRatio.ts +34 -0
- package/src/Avatar.ts +152 -0
- package/src/Checkbox.ts +325 -0
- package/src/Collapsible.ts +170 -0
- package/src/ContextMenu.ts +303 -0
- package/src/Dialog.ts +308 -0
- package/src/DismissableLayer.ts +413 -0
- package/src/DropdownMenu.ts +275 -0
- package/src/FocusScope.ts +266 -0
- package/src/Form.ts +621 -0
- package/src/HoverCard.ts +318 -0
- package/src/Label.ts +25 -0
- package/src/Menu.ts +1057 -0
- package/src/Menubar.ts +572 -0
- package/src/NavigationMenu.ts +1236 -0
- package/src/OneTimePasswordField.ts +977 -0
- package/src/PasswordToggleField.ts +495 -0
- package/src/Popover.ts +354 -0
- package/src/Popper.ts +401 -0
- package/src/Portal.ts +19 -0
- package/src/Presence.ts +225 -0
- package/src/Primitive.ts +53 -0
- package/src/Progress.ts +92 -0
- package/src/Radio.ts +262 -0
- package/src/RadioGroup.ts +212 -0
- package/src/RovingFocusGroup.ts +259 -0
- package/src/ScrollArea.ts +966 -0
- package/src/Select.ts +1901 -0
- package/src/Separator.ts +36 -0
- package/src/Slider.ts +745 -0
- package/src/Slot.ts +105 -0
- package/src/Switch.ts +278 -0
- package/src/Tabs.ts +177 -0
- package/src/Toast.ts +923 -0
- package/src/Toggle.ts +31 -0
- package/src/ToggleGroup.ts +157 -0
- package/src/Toolbar.ts +130 -0
- package/src/Tooltip.ts +669 -0
- package/src/VisuallyHidden.ts +29 -0
- package/src/collection.ts +97 -0
- package/src/compose-event-handlers.ts +15 -0
- package/src/compose-refs.ts +53 -0
- package/src/context.ts +109 -0
- package/src/direction.ts +19 -0
- package/src/focus-guards.ts +56 -0
- package/src/index.ts +54 -0
- package/src/internal.ts +42 -0
- package/src/scroll-lock.ts +57 -0
- package/src/use-callback-ref.ts +28 -0
- package/src/use-effect-event.ts +36 -0
- package/src/use-is-hydrated.ts +23 -0
- package/src/use-previous.ts +28 -0
- package/src/use-size.ts +57 -0
- package/src/useControllableState.ts +78 -0
- package/src/useId.ts +15 -0
package/src/Menubar.ts
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
// Ported from @radix-ui/react-menubar (source:
|
|
2
|
+
// .radix-primitives/packages/react/menubar/src/menubar.tsx). A visually persistent
|
|
3
|
+
// `role=menubar` composing the shared Menu primitive: a horizontal RovingFocusGroup of
|
|
4
|
+
// triggers (one tab stop, managed manually via `currentTabStopId` since triggers may
|
|
5
|
+
// never receive focus), where the menubar's `value` is the open menu's value. Triggers
|
|
6
|
+
// open on pointerdown / ArrowDown and toggle on Enter/Space; pointerenter switches menus
|
|
7
|
+
// while one is open; ArrowRight/ArrowLeft at the edges of an open menu's content move to
|
|
8
|
+
// the next/previous menu (dir-aware, loop-aware, skipping submenus); closing returns
|
|
9
|
+
// focus to the trigger unless the close came from an outside interaction.
|
|
10
|
+
//
|
|
11
|
+
// octane adaptations (all previously established in this package):
|
|
12
|
+
// - Plain `.ts` + `createElement` (no JSX); no forwardRef — `ref: forwardedRef` is
|
|
13
|
+
// destructured from props and composed with useComposedRefs.
|
|
14
|
+
// - Explicit hook slots: per component `S('Menubar.X')` + a unique `subSlot(slot, tag)`
|
|
15
|
+
// as every hook call's trailing arg (octane's auto-slotting pass only runs on
|
|
16
|
+
// compiled .tsx/.tsrx).
|
|
17
|
+
// - Events are native delegated DOM events (onPointerDown/onPointerEnter/onKeyDown/
|
|
18
|
+
// onFocus/onBlur map 1:1; the runtime handles enter/leave + focus/blur delegation).
|
|
19
|
+
// - `useControllableState`'s dev-only `caller` param is not ported (repo policy:
|
|
20
|
+
// functional outcomes only).
|
|
21
|
+
// - Menu.Portal children convention: children passed at prop position
|
|
22
|
+
// (`children={[<Content/>]}`), portal'd via `asChild` unless a function child.
|
|
23
|
+
import { createElement, useCallback, useEffect, useRef, useState } from 'octane';
|
|
24
|
+
|
|
25
|
+
import { createCollection } from './collection';
|
|
26
|
+
import { composeEventHandlers } from './compose-event-handlers';
|
|
27
|
+
import { useComposedRefs } from './compose-refs';
|
|
28
|
+
import { createContextScope } from './context';
|
|
29
|
+
import { useDirection } from './direction';
|
|
30
|
+
import { S, subSlot } from './internal';
|
|
31
|
+
import * as MenuPrimitive from './Menu';
|
|
32
|
+
import { createMenuScope } from './Menu';
|
|
33
|
+
import { Primitive } from './Primitive';
|
|
34
|
+
import * as RovingFocusGroup from './RovingFocusGroup';
|
|
35
|
+
import { createRovingFocusGroupScope } from './RovingFocusGroup';
|
|
36
|
+
import { useControllableState } from './useControllableState';
|
|
37
|
+
import { useId } from './useId';
|
|
38
|
+
|
|
39
|
+
type Direction = 'ltr' | 'rtl';
|
|
40
|
+
|
|
41
|
+
/* -------------------------------------------------------------------------------------------------
|
|
42
|
+
* Menubar
|
|
43
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
44
|
+
|
|
45
|
+
const MENUBAR_NAME = 'Menubar';
|
|
46
|
+
|
|
47
|
+
const [Collection, useCollection, createCollectionScope] = createCollection(MENUBAR_NAME);
|
|
48
|
+
|
|
49
|
+
const [createMenubarContext, createMenubarScope] = createContextScope(MENUBAR_NAME, [
|
|
50
|
+
createCollectionScope,
|
|
51
|
+
createRovingFocusGroupScope,
|
|
52
|
+
]);
|
|
53
|
+
export { createMenubarScope };
|
|
54
|
+
|
|
55
|
+
const useMenuScope = createMenuScope();
|
|
56
|
+
const useRovingFocusGroupScope = createRovingFocusGroupScope();
|
|
57
|
+
|
|
58
|
+
interface MenubarContextValue {
|
|
59
|
+
value: string;
|
|
60
|
+
dir: Direction;
|
|
61
|
+
loop: boolean;
|
|
62
|
+
onMenuOpen(value: string): void;
|
|
63
|
+
onMenuClose(): void;
|
|
64
|
+
onMenuToggle(value: string): void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const [MenubarContextProvider, useMenubarContext] =
|
|
68
|
+
createMenubarContext<MenubarContextValue>(MENUBAR_NAME);
|
|
69
|
+
|
|
70
|
+
export function Root(props: any): any {
|
|
71
|
+
const slot = S('Menubar.Root');
|
|
72
|
+
const {
|
|
73
|
+
__scopeMenubar,
|
|
74
|
+
value: valueProp,
|
|
75
|
+
onValueChange,
|
|
76
|
+
defaultValue,
|
|
77
|
+
loop = true,
|
|
78
|
+
dir,
|
|
79
|
+
ref: forwardedRef,
|
|
80
|
+
...menubarProps
|
|
81
|
+
} = props ?? {};
|
|
82
|
+
const direction = useDirection(dir);
|
|
83
|
+
const rovingFocusGroupScope = useRovingFocusGroupScope(__scopeMenubar, subSlot(slot, 'rfs'));
|
|
84
|
+
const [value, setValue] = useControllableState<string>(
|
|
85
|
+
{ prop: valueProp, onChange: onValueChange, defaultProp: defaultValue ?? '' },
|
|
86
|
+
subSlot(slot, 'value'),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// We need to manage tab stop id manually as `RovingFocusGroup` updates the stop
|
|
90
|
+
// based on focus, and in some situations our triggers won't ever be given focus
|
|
91
|
+
// (e.g. click to open and then outside to close)
|
|
92
|
+
const [currentTabStopId, setCurrentTabStopId] = useState<string | null>(
|
|
93
|
+
null,
|
|
94
|
+
subSlot(slot, 'tabStop'),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return createElement(MenubarContextProvider, {
|
|
98
|
+
scope: __scopeMenubar,
|
|
99
|
+
value,
|
|
100
|
+
onMenuOpen: useCallback(
|
|
101
|
+
(value: string) => {
|
|
102
|
+
setValue(value);
|
|
103
|
+
setCurrentTabStopId(value);
|
|
104
|
+
},
|
|
105
|
+
[setValue],
|
|
106
|
+
subSlot(slot, 'open'),
|
|
107
|
+
),
|
|
108
|
+
onMenuClose: useCallback(() => setValue(''), [setValue], subSlot(slot, 'close')),
|
|
109
|
+
onMenuToggle: useCallback(
|
|
110
|
+
(value: string) => {
|
|
111
|
+
setValue((prevValue) => (prevValue ? '' : value));
|
|
112
|
+
// `openMenuOpen` and `onMenuToggle` are called exclusively so we
|
|
113
|
+
// need to update the id in either case.
|
|
114
|
+
setCurrentTabStopId(value);
|
|
115
|
+
},
|
|
116
|
+
[setValue],
|
|
117
|
+
subSlot(slot, 'toggle'),
|
|
118
|
+
),
|
|
119
|
+
dir: direction,
|
|
120
|
+
loop,
|
|
121
|
+
children: createElement(Collection.Provider, {
|
|
122
|
+
scope: __scopeMenubar,
|
|
123
|
+
children: createElement(Collection.Slot, {
|
|
124
|
+
scope: __scopeMenubar,
|
|
125
|
+
children: createElement(RovingFocusGroup.Root, {
|
|
126
|
+
asChild: true,
|
|
127
|
+
...rovingFocusGroupScope,
|
|
128
|
+
orientation: 'horizontal',
|
|
129
|
+
loop,
|
|
130
|
+
dir: direction,
|
|
131
|
+
currentTabStopId,
|
|
132
|
+
onCurrentTabStopIdChange: setCurrentTabStopId,
|
|
133
|
+
children: createElement(Primitive.div, {
|
|
134
|
+
role: 'menubar',
|
|
135
|
+
...menubarProps,
|
|
136
|
+
ref: forwardedRef,
|
|
137
|
+
}),
|
|
138
|
+
}),
|
|
139
|
+
}),
|
|
140
|
+
}),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* -------------------------------------------------------------------------------------------------
|
|
145
|
+
* MenubarMenu
|
|
146
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
147
|
+
|
|
148
|
+
const MENU_NAME = 'MenubarMenu';
|
|
149
|
+
|
|
150
|
+
interface MenubarMenuContextValue {
|
|
151
|
+
value: string;
|
|
152
|
+
triggerId: string;
|
|
153
|
+
triggerRef: { current: HTMLElement | null };
|
|
154
|
+
contentId: string;
|
|
155
|
+
wasKeyboardTriggerOpenRef: { current: boolean };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const [MenubarMenuProvider, useMenubarMenuContext] =
|
|
159
|
+
createMenubarContext<MenubarMenuContextValue>(MENU_NAME);
|
|
160
|
+
|
|
161
|
+
export function Menu(props: any): any {
|
|
162
|
+
const slot = S('Menubar.Menu');
|
|
163
|
+
const { __scopeMenubar, value: valueProp, ...menuProps } = props ?? {};
|
|
164
|
+
const autoValue = useId(subSlot(slot, 'autoValue'));
|
|
165
|
+
// We need to provide an initial deterministic value as `useId` will return
|
|
166
|
+
// empty string on the first render and we don't want to match our internal "closed" value.
|
|
167
|
+
const value = valueProp || autoValue || 'LEGACY_REACT_AUTO_VALUE';
|
|
168
|
+
const context = useMenubarContext(MENU_NAME, __scopeMenubar);
|
|
169
|
+
const menuScope = useMenuScope(__scopeMenubar, subSlot(slot, 'menu'));
|
|
170
|
+
const triggerRef = useRef<HTMLElement | null>(null, subSlot(slot, 'triggerRef'));
|
|
171
|
+
const wasKeyboardTriggerOpenRef = useRef(false, subSlot(slot, 'wasKeyboard'));
|
|
172
|
+
const open = context.value === value;
|
|
173
|
+
|
|
174
|
+
useEffect(
|
|
175
|
+
() => {
|
|
176
|
+
if (!open) wasKeyboardTriggerOpenRef.current = false;
|
|
177
|
+
},
|
|
178
|
+
[open],
|
|
179
|
+
subSlot(slot, 'e:open'),
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
return createElement(MenubarMenuProvider, {
|
|
183
|
+
scope: __scopeMenubar,
|
|
184
|
+
value,
|
|
185
|
+
triggerId: useId(subSlot(slot, 'triggerId')),
|
|
186
|
+
triggerRef,
|
|
187
|
+
contentId: useId(subSlot(slot, 'contentId')),
|
|
188
|
+
wasKeyboardTriggerOpenRef,
|
|
189
|
+
children: createElement(MenuPrimitive.Root, {
|
|
190
|
+
...menuScope,
|
|
191
|
+
open,
|
|
192
|
+
onOpenChange: (open: boolean) => {
|
|
193
|
+
// Menu only calls `onOpenChange` when dismissing so we
|
|
194
|
+
// want to close our MenuBar based on the same events.
|
|
195
|
+
if (!open) context.onMenuClose();
|
|
196
|
+
},
|
|
197
|
+
modal: false,
|
|
198
|
+
dir: context.dir,
|
|
199
|
+
...menuProps,
|
|
200
|
+
}),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* -------------------------------------------------------------------------------------------------
|
|
205
|
+
* MenubarTrigger
|
|
206
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
207
|
+
|
|
208
|
+
const TRIGGER_NAME = 'MenubarTrigger';
|
|
209
|
+
|
|
210
|
+
export function Trigger(props: any): any {
|
|
211
|
+
const slot = S('Menubar.Trigger');
|
|
212
|
+
const { __scopeMenubar, disabled = false, ref: forwardedRef, ...triggerProps } = props ?? {};
|
|
213
|
+
const rovingFocusGroupScope = useRovingFocusGroupScope(__scopeMenubar, subSlot(slot, 'rfs'));
|
|
214
|
+
const menuScope = useMenuScope(__scopeMenubar, subSlot(slot, 'menu'));
|
|
215
|
+
const context = useMenubarContext(TRIGGER_NAME, __scopeMenubar);
|
|
216
|
+
const menuContext = useMenubarMenuContext(TRIGGER_NAME, __scopeMenubar);
|
|
217
|
+
const ref = useRef<HTMLElement | null>(null, subSlot(slot, 'ref'));
|
|
218
|
+
const composedRefs = useComposedRefs(
|
|
219
|
+
forwardedRef,
|
|
220
|
+
ref,
|
|
221
|
+
menuContext.triggerRef,
|
|
222
|
+
subSlot(slot, 'refs'),
|
|
223
|
+
);
|
|
224
|
+
const [isFocused, setIsFocused] = useState(false, subSlot(slot, 'focused'));
|
|
225
|
+
const open = context.value === menuContext.value;
|
|
226
|
+
|
|
227
|
+
return createElement(Collection.ItemSlot, {
|
|
228
|
+
scope: __scopeMenubar,
|
|
229
|
+
value: menuContext.value,
|
|
230
|
+
disabled,
|
|
231
|
+
children: createElement(RovingFocusGroup.Item, {
|
|
232
|
+
asChild: true,
|
|
233
|
+
...rovingFocusGroupScope,
|
|
234
|
+
focusable: !disabled,
|
|
235
|
+
tabStopId: menuContext.value,
|
|
236
|
+
children: createElement(MenuPrimitive.Anchor, {
|
|
237
|
+
asChild: true,
|
|
238
|
+
...menuScope,
|
|
239
|
+
children: createElement(Primitive.button, {
|
|
240
|
+
type: 'button',
|
|
241
|
+
role: 'menuitem',
|
|
242
|
+
id: menuContext.triggerId,
|
|
243
|
+
'aria-haspopup': 'menu',
|
|
244
|
+
'aria-expanded': open,
|
|
245
|
+
'aria-controls': open ? menuContext.contentId : undefined,
|
|
246
|
+
'data-highlighted': isFocused ? '' : undefined,
|
|
247
|
+
'data-state': open ? 'open' : 'closed',
|
|
248
|
+
'data-disabled': disabled ? '' : undefined,
|
|
249
|
+
disabled,
|
|
250
|
+
...triggerProps,
|
|
251
|
+
ref: composedRefs,
|
|
252
|
+
onPointerDown: composeEventHandlers(props?.onPointerDown, (event: PointerEvent) => {
|
|
253
|
+
// only call handler if it's the left button (mousedown gets triggered by all mouse
|
|
254
|
+
// buttons) but not when the control key is pressed (avoiding MacOS right click)
|
|
255
|
+
if (!disabled && event.button === 0 && event.ctrlKey === false) {
|
|
256
|
+
context.onMenuOpen(menuContext.value);
|
|
257
|
+
// prevent trigger focusing when opening
|
|
258
|
+
// this allows the content to be given focus without competition
|
|
259
|
+
if (!open) event.preventDefault();
|
|
260
|
+
}
|
|
261
|
+
}),
|
|
262
|
+
onPointerEnter: composeEventHandlers(props?.onPointerEnter, () => {
|
|
263
|
+
const menubarOpen = Boolean(context.value);
|
|
264
|
+
if (menubarOpen && !open) {
|
|
265
|
+
context.onMenuOpen(menuContext.value);
|
|
266
|
+
ref.current?.focus();
|
|
267
|
+
}
|
|
268
|
+
}),
|
|
269
|
+
onKeyDown: composeEventHandlers(props?.onKeyDown, (event: KeyboardEvent) => {
|
|
270
|
+
if (disabled) return;
|
|
271
|
+
if (['Enter', ' '].includes(event.key)) context.onMenuToggle(menuContext.value);
|
|
272
|
+
if (event.key === 'ArrowDown') context.onMenuOpen(menuContext.value);
|
|
273
|
+
// prevent keydown from scrolling window / first focused item to execute
|
|
274
|
+
// that keydown (inadvertently closing the menu)
|
|
275
|
+
if (['Enter', ' ', 'ArrowDown'].includes(event.key)) {
|
|
276
|
+
menuContext.wasKeyboardTriggerOpenRef.current = true;
|
|
277
|
+
event.preventDefault();
|
|
278
|
+
}
|
|
279
|
+
}),
|
|
280
|
+
onFocus: composeEventHandlers(props?.onFocus, () => setIsFocused(true)),
|
|
281
|
+
onBlur: composeEventHandlers(props?.onBlur, () => setIsFocused(false)),
|
|
282
|
+
}),
|
|
283
|
+
}),
|
|
284
|
+
}),
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/* -------------------------------------------------------------------------------------------------
|
|
289
|
+
* MenubarPortal
|
|
290
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
291
|
+
|
|
292
|
+
export function Portal(props: any): any {
|
|
293
|
+
const slot = S('Menubar.Portal');
|
|
294
|
+
const { __scopeMenubar, ...portalProps } = props ?? {};
|
|
295
|
+
const menuScope = useMenuScope(__scopeMenubar, subSlot(slot, 'menu'));
|
|
296
|
+
return createElement(MenuPrimitive.Portal, { ...menuScope, ...portalProps });
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* -------------------------------------------------------------------------------------------------
|
|
300
|
+
* MenubarContent
|
|
301
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
302
|
+
|
|
303
|
+
const CONTENT_NAME = 'MenubarContent';
|
|
304
|
+
|
|
305
|
+
export function Content(props: any): any {
|
|
306
|
+
const slot = S('Menubar.Content');
|
|
307
|
+
const { __scopeMenubar, align = 'start', ref: forwardedRef, ...contentProps } = props ?? {};
|
|
308
|
+
const menuScope = useMenuScope(__scopeMenubar, subSlot(slot, 'menu'));
|
|
309
|
+
const context = useMenubarContext(CONTENT_NAME, __scopeMenubar);
|
|
310
|
+
const menuContext = useMenubarMenuContext(CONTENT_NAME, __scopeMenubar);
|
|
311
|
+
const getItems = useCollection(__scopeMenubar, subSlot(slot, 'items'));
|
|
312
|
+
const hasInteractedOutsideRef = useRef(false, subSlot(slot, 'interacted'));
|
|
313
|
+
|
|
314
|
+
return createElement(MenuPrimitive.Content, {
|
|
315
|
+
id: menuContext.contentId,
|
|
316
|
+
'aria-labelledby': menuContext.triggerId,
|
|
317
|
+
'data-radix-menubar-content': '',
|
|
318
|
+
...menuScope,
|
|
319
|
+
...contentProps,
|
|
320
|
+
ref: forwardedRef,
|
|
321
|
+
align,
|
|
322
|
+
onCloseAutoFocus: composeEventHandlers(props?.onCloseAutoFocus, (event: Event) => {
|
|
323
|
+
const menubarOpen = Boolean(context.value);
|
|
324
|
+
if (!menubarOpen && !hasInteractedOutsideRef.current) {
|
|
325
|
+
menuContext.triggerRef.current?.focus();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
hasInteractedOutsideRef.current = false;
|
|
329
|
+
// Always prevent auto focus because we either focus manually or want user agent focus
|
|
330
|
+
event.preventDefault();
|
|
331
|
+
}),
|
|
332
|
+
onFocusOutside: composeEventHandlers(props?.onFocusOutside, (event: Event) => {
|
|
333
|
+
const target = event.target as HTMLElement;
|
|
334
|
+
const isMenubarTrigger = getItems().some((item: any) => item.ref.current?.contains(target));
|
|
335
|
+
if (isMenubarTrigger) event.preventDefault();
|
|
336
|
+
}),
|
|
337
|
+
onInteractOutside: composeEventHandlers(props?.onInteractOutside, () => {
|
|
338
|
+
hasInteractedOutsideRef.current = true;
|
|
339
|
+
}),
|
|
340
|
+
onEntryFocus: (event: Event) => {
|
|
341
|
+
if (!menuContext.wasKeyboardTriggerOpenRef.current) event.preventDefault();
|
|
342
|
+
},
|
|
343
|
+
onKeyDown: composeEventHandlers(
|
|
344
|
+
props?.onKeyDown,
|
|
345
|
+
(event: KeyboardEvent) => {
|
|
346
|
+
if (['ArrowRight', 'ArrowLeft'].includes(event.key)) {
|
|
347
|
+
const target = event.target as HTMLElement;
|
|
348
|
+
const targetIsSubTrigger = target.hasAttribute('data-radix-menubar-subtrigger');
|
|
349
|
+
const isKeyDownInsideSubMenu =
|
|
350
|
+
target.closest('[data-radix-menubar-content]') !== event.currentTarget;
|
|
351
|
+
|
|
352
|
+
const prevMenuKey = context.dir === 'rtl' ? 'ArrowRight' : 'ArrowLeft';
|
|
353
|
+
const isPrevKey = prevMenuKey === event.key;
|
|
354
|
+
const isNextKey = !isPrevKey;
|
|
355
|
+
|
|
356
|
+
// Prevent navigation when we're opening a submenu
|
|
357
|
+
if (isNextKey && targetIsSubTrigger) return;
|
|
358
|
+
// or we're inside a submenu and are moving backwards to close it
|
|
359
|
+
if (isKeyDownInsideSubMenu && isPrevKey) return;
|
|
360
|
+
|
|
361
|
+
const items = getItems().filter((item: any) => !item.disabled);
|
|
362
|
+
let candidateValues = items.map((item: any) => item.value);
|
|
363
|
+
if (isPrevKey) candidateValues.reverse();
|
|
364
|
+
|
|
365
|
+
const currentIndex = candidateValues.indexOf(menuContext.value);
|
|
366
|
+
|
|
367
|
+
candidateValues = context.loop
|
|
368
|
+
? wrapArray(candidateValues, currentIndex + 1)
|
|
369
|
+
: candidateValues.slice(currentIndex + 1);
|
|
370
|
+
|
|
371
|
+
const [nextValue] = candidateValues;
|
|
372
|
+
if (nextValue) context.onMenuOpen(nextValue);
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
{ checkForDefaultPrevented: false },
|
|
376
|
+
),
|
|
377
|
+
style: {
|
|
378
|
+
...props?.style,
|
|
379
|
+
// re-namespace exposed content custom properties
|
|
380
|
+
'--radix-menubar-content-transform-origin': 'var(--radix-popper-transform-origin)',
|
|
381
|
+
'--radix-menubar-content-available-width': 'var(--radix-popper-available-width)',
|
|
382
|
+
'--radix-menubar-content-available-height': 'var(--radix-popper-available-height)',
|
|
383
|
+
'--radix-menubar-trigger-width': 'var(--radix-popper-anchor-width)',
|
|
384
|
+
'--radix-menubar-trigger-height': 'var(--radix-popper-anchor-height)',
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/* -------------------------------------------------------------------------------------------------
|
|
390
|
+
* MenubarGroup
|
|
391
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
392
|
+
|
|
393
|
+
export function Group(props: any): any {
|
|
394
|
+
const slot = S('Menubar.Group');
|
|
395
|
+
const { __scopeMenubar, ...groupProps } = props ?? {};
|
|
396
|
+
const menuScope = useMenuScope(__scopeMenubar, subSlot(slot, 'menu'));
|
|
397
|
+
return createElement(MenuPrimitive.Group, { ...menuScope, ...groupProps });
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/* -------------------------------------------------------------------------------------------------
|
|
401
|
+
* MenubarLabel
|
|
402
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
403
|
+
|
|
404
|
+
export function Label(props: any): any {
|
|
405
|
+
const slot = S('Menubar.Label');
|
|
406
|
+
const { __scopeMenubar, ...labelProps } = props ?? {};
|
|
407
|
+
const menuScope = useMenuScope(__scopeMenubar, subSlot(slot, 'menu'));
|
|
408
|
+
return createElement(MenuPrimitive.Label, { ...menuScope, ...labelProps });
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/* -------------------------------------------------------------------------------------------------
|
|
412
|
+
* MenubarItem
|
|
413
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
414
|
+
|
|
415
|
+
export function Item(props: any): any {
|
|
416
|
+
const slot = S('Menubar.Item');
|
|
417
|
+
const { __scopeMenubar, ...itemProps } = props ?? {};
|
|
418
|
+
const menuScope = useMenuScope(__scopeMenubar, subSlot(slot, 'menu'));
|
|
419
|
+
return createElement(MenuPrimitive.Item, { ...menuScope, ...itemProps });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/* -------------------------------------------------------------------------------------------------
|
|
423
|
+
* MenubarCheckboxItem
|
|
424
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
425
|
+
|
|
426
|
+
export function CheckboxItem(props: any): any {
|
|
427
|
+
const slot = S('Menubar.CheckboxItem');
|
|
428
|
+
const { __scopeMenubar, ...checkboxItemProps } = props ?? {};
|
|
429
|
+
const menuScope = useMenuScope(__scopeMenubar, subSlot(slot, 'menu'));
|
|
430
|
+
return createElement(MenuPrimitive.CheckboxItem, { ...menuScope, ...checkboxItemProps });
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/* -------------------------------------------------------------------------------------------------
|
|
434
|
+
* MenubarRadioGroup
|
|
435
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
436
|
+
|
|
437
|
+
export function RadioGroup(props: any): any {
|
|
438
|
+
const slot = S('Menubar.RadioGroup');
|
|
439
|
+
const { __scopeMenubar, ...radioGroupProps } = props ?? {};
|
|
440
|
+
const menuScope = useMenuScope(__scopeMenubar, subSlot(slot, 'menu'));
|
|
441
|
+
return createElement(MenuPrimitive.RadioGroup, { ...menuScope, ...radioGroupProps });
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/* -------------------------------------------------------------------------------------------------
|
|
445
|
+
* MenubarRadioItem
|
|
446
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
447
|
+
|
|
448
|
+
export function RadioItem(props: any): any {
|
|
449
|
+
const slot = S('Menubar.RadioItem');
|
|
450
|
+
const { __scopeMenubar, ...radioItemProps } = props ?? {};
|
|
451
|
+
const menuScope = useMenuScope(__scopeMenubar, subSlot(slot, 'menu'));
|
|
452
|
+
return createElement(MenuPrimitive.RadioItem, { ...menuScope, ...radioItemProps });
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/* -------------------------------------------------------------------------------------------------
|
|
456
|
+
* MenubarItemIndicator
|
|
457
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
458
|
+
|
|
459
|
+
export function ItemIndicator(props: any): any {
|
|
460
|
+
const slot = S('Menubar.ItemIndicator');
|
|
461
|
+
const { __scopeMenubar, ...itemIndicatorProps } = props ?? {};
|
|
462
|
+
const menuScope = useMenuScope(__scopeMenubar, subSlot(slot, 'menu'));
|
|
463
|
+
return createElement(MenuPrimitive.ItemIndicator, { ...menuScope, ...itemIndicatorProps });
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/* -------------------------------------------------------------------------------------------------
|
|
467
|
+
* MenubarSeparator
|
|
468
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
469
|
+
|
|
470
|
+
export function Separator(props: any): any {
|
|
471
|
+
const slot = S('Menubar.Separator');
|
|
472
|
+
const { __scopeMenubar, ...separatorProps } = props ?? {};
|
|
473
|
+
const menuScope = useMenuScope(__scopeMenubar, subSlot(slot, 'menu'));
|
|
474
|
+
return createElement(MenuPrimitive.Separator, { ...menuScope, ...separatorProps });
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/* -------------------------------------------------------------------------------------------------
|
|
478
|
+
* MenubarArrow
|
|
479
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
480
|
+
|
|
481
|
+
export function Arrow(props: any): any {
|
|
482
|
+
const slot = S('Menubar.Arrow');
|
|
483
|
+
const { __scopeMenubar, ...arrowProps } = props ?? {};
|
|
484
|
+
const menuScope = useMenuScope(__scopeMenubar, subSlot(slot, 'menu'));
|
|
485
|
+
return createElement(MenuPrimitive.Arrow, { ...menuScope, ...arrowProps });
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/* -------------------------------------------------------------------------------------------------
|
|
489
|
+
* MenubarSub
|
|
490
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
491
|
+
|
|
492
|
+
export function Sub(props: any): any {
|
|
493
|
+
const slot = S('Menubar.Sub');
|
|
494
|
+
const { __scopeMenubar, children, open: openProp, onOpenChange, defaultOpen } = props ?? {};
|
|
495
|
+
const menuScope = useMenuScope(__scopeMenubar, subSlot(slot, 'menu'));
|
|
496
|
+
const [open, setOpen] = useControllableState<boolean>(
|
|
497
|
+
{ prop: openProp, defaultProp: defaultOpen ?? false, onChange: onOpenChange },
|
|
498
|
+
subSlot(slot, 'open'),
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
return createElement(MenuPrimitive.Sub, { ...menuScope, open, onOpenChange: setOpen, children });
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/* -------------------------------------------------------------------------------------------------
|
|
505
|
+
* MenubarSubTrigger
|
|
506
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
507
|
+
|
|
508
|
+
export function SubTrigger(props: any): any {
|
|
509
|
+
const slot = S('Menubar.SubTrigger');
|
|
510
|
+
const { __scopeMenubar, ...subTriggerProps } = props ?? {};
|
|
511
|
+
const menuScope = useMenuScope(__scopeMenubar, subSlot(slot, 'menu'));
|
|
512
|
+
return createElement(MenuPrimitive.SubTrigger, {
|
|
513
|
+
'data-radix-menubar-subtrigger': '',
|
|
514
|
+
...menuScope,
|
|
515
|
+
...subTriggerProps,
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/* -------------------------------------------------------------------------------------------------
|
|
520
|
+
* MenubarSubContent
|
|
521
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
522
|
+
|
|
523
|
+
export function SubContent(props: any): any {
|
|
524
|
+
const slot = S('Menubar.SubContent');
|
|
525
|
+
const { __scopeMenubar, ...subContentProps } = props ?? {};
|
|
526
|
+
const menuScope = useMenuScope(__scopeMenubar, subSlot(slot, 'menu'));
|
|
527
|
+
|
|
528
|
+
return createElement(MenuPrimitive.SubContent, {
|
|
529
|
+
...menuScope,
|
|
530
|
+
'data-radix-menubar-content': '',
|
|
531
|
+
...subContentProps,
|
|
532
|
+
style: {
|
|
533
|
+
...props?.style,
|
|
534
|
+
// re-namespace exposed content custom properties
|
|
535
|
+
'--radix-menubar-content-transform-origin': 'var(--radix-popper-transform-origin)',
|
|
536
|
+
'--radix-menubar-content-available-width': 'var(--radix-popper-available-width)',
|
|
537
|
+
'--radix-menubar-content-available-height': 'var(--radix-popper-available-height)',
|
|
538
|
+
'--radix-menubar-trigger-width': 'var(--radix-popper-anchor-width)',
|
|
539
|
+
'--radix-menubar-trigger-height': 'var(--radix-popper-anchor-height)',
|
|
540
|
+
},
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/* -----------------------------------------------------------------------------------------------*/
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Wraps an array around itself at a given start index
|
|
548
|
+
* Example: `wrapArray(['a', 'b', 'c', 'd'], 2) === ['c', 'd', 'a', 'b']`
|
|
549
|
+
*/
|
|
550
|
+
function wrapArray<T>(array: T[], startIndex: number): T[] {
|
|
551
|
+
return array.map((_, index) => array[(startIndex + index) % array.length]);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
export {
|
|
555
|
+
Root as Menubar,
|
|
556
|
+
Menu as MenubarMenu,
|
|
557
|
+
Trigger as MenubarTrigger,
|
|
558
|
+
Portal as MenubarPortal,
|
|
559
|
+
Content as MenubarContent,
|
|
560
|
+
Group as MenubarGroup,
|
|
561
|
+
Label as MenubarLabel,
|
|
562
|
+
Item as MenubarItem,
|
|
563
|
+
CheckboxItem as MenubarCheckboxItem,
|
|
564
|
+
RadioGroup as MenubarRadioGroup,
|
|
565
|
+
RadioItem as MenubarRadioItem,
|
|
566
|
+
ItemIndicator as MenubarItemIndicator,
|
|
567
|
+
Separator as MenubarSeparator,
|
|
568
|
+
Arrow as MenubarArrow,
|
|
569
|
+
Sub as MenubarSub,
|
|
570
|
+
SubTrigger as MenubarSubTrigger,
|
|
571
|
+
SubContent as MenubarSubContent,
|
|
572
|
+
};
|