@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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +100 -0
  3. package/package.json +45 -0
  4. package/src/AccessibleIcon.ts +26 -0
  5. package/src/Accordion.ts +282 -0
  6. package/src/AlertDialog.ts +110 -0
  7. package/src/Arrow.ts +21 -0
  8. package/src/AspectRatio.ts +34 -0
  9. package/src/Avatar.ts +152 -0
  10. package/src/Checkbox.ts +325 -0
  11. package/src/Collapsible.ts +170 -0
  12. package/src/ContextMenu.ts +303 -0
  13. package/src/Dialog.ts +308 -0
  14. package/src/DismissableLayer.ts +413 -0
  15. package/src/DropdownMenu.ts +275 -0
  16. package/src/FocusScope.ts +266 -0
  17. package/src/Form.ts +621 -0
  18. package/src/HoverCard.ts +318 -0
  19. package/src/Label.ts +25 -0
  20. package/src/Menu.ts +1057 -0
  21. package/src/Menubar.ts +572 -0
  22. package/src/NavigationMenu.ts +1236 -0
  23. package/src/OneTimePasswordField.ts +977 -0
  24. package/src/PasswordToggleField.ts +495 -0
  25. package/src/Popover.ts +354 -0
  26. package/src/Popper.ts +401 -0
  27. package/src/Portal.ts +19 -0
  28. package/src/Presence.ts +225 -0
  29. package/src/Primitive.ts +53 -0
  30. package/src/Progress.ts +92 -0
  31. package/src/Radio.ts +262 -0
  32. package/src/RadioGroup.ts +212 -0
  33. package/src/RovingFocusGroup.ts +259 -0
  34. package/src/ScrollArea.ts +966 -0
  35. package/src/Select.ts +1901 -0
  36. package/src/Separator.ts +36 -0
  37. package/src/Slider.ts +745 -0
  38. package/src/Slot.ts +105 -0
  39. package/src/Switch.ts +278 -0
  40. package/src/Tabs.ts +177 -0
  41. package/src/Toast.ts +923 -0
  42. package/src/Toggle.ts +31 -0
  43. package/src/ToggleGroup.ts +157 -0
  44. package/src/Toolbar.ts +130 -0
  45. package/src/Tooltip.ts +669 -0
  46. package/src/VisuallyHidden.ts +29 -0
  47. package/src/collection.ts +97 -0
  48. package/src/compose-event-handlers.ts +15 -0
  49. package/src/compose-refs.ts +53 -0
  50. package/src/context.ts +109 -0
  51. package/src/direction.ts +19 -0
  52. package/src/focus-guards.ts +56 -0
  53. package/src/index.ts +54 -0
  54. package/src/internal.ts +42 -0
  55. package/src/scroll-lock.ts +57 -0
  56. package/src/use-callback-ref.ts +28 -0
  57. package/src/use-effect-event.ts +36 -0
  58. package/src/use-is-hydrated.ts +23 -0
  59. package/src/use-previous.ts +28 -0
  60. package/src/use-size.ts +57 -0
  61. package/src/useControllableState.ts +78 -0
  62. 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
+ };