@enonic/ui 0.15.0 → 0.16.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.
@@ -81,6 +81,33 @@ const useMenu = () => {
81
81
  }
82
82
  return context;
83
83
  };
84
+ const MenubarMenuContext = createContext(void 0);
85
+ const MenubarMenuProvider = MenubarMenuContext.Provider;
86
+ function useMenubarMenu() {
87
+ const context = useContext(MenubarMenuContext);
88
+ if (!context) {
89
+ throw new Error("useMenubarMenu must be used within a Menubar.Menu component");
90
+ }
91
+ return context;
92
+ }
93
+ function useMenubarMenuOptional() {
94
+ return useContext(MenubarMenuContext);
95
+ }
96
+ const MenubarContext = createContext(void 0);
97
+ const MenubarProvider = ({ value, children }) => {
98
+ return /* @__PURE__ */ u(MenubarContext.Provider, { value, children });
99
+ };
100
+ MenubarProvider.displayName = "MenubarProvider";
101
+ const useMenubar = () => {
102
+ const context = useContext(MenubarContext);
103
+ if (!context) {
104
+ throw new Error("useMenubar must be used within a MenubarProvider");
105
+ }
106
+ return context;
107
+ };
108
+ const useMenubarOptional = () => {
109
+ return useContext(MenubarContext);
110
+ };
84
111
  const SearchFieldContext = createContext(void 0);
85
112
  const SearchFieldProvider = ({ value, children }) => {
86
113
  return /* @__PURE__ */ u(SearchFieldContext.Provider, { value, children });
@@ -3266,17 +3293,18 @@ const buttonVariants = cva(
3266
3293
  "inline-flex items-center justify-center",
3267
3294
  "text-main font-semibold",
3268
3295
  "box-border rounded-sm transition-highlight",
3269
- "focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:ring-offset-0",
3296
+ "focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-bdr-strong focus-visible:ring-offset-3 focus-visible:ring-offset-surface-neutral",
3297
+ "active:bg-btn-active active:text-alt data-[active=true]:bg-btn-active data-[active=true]:text-alt",
3270
3298
  "disabled:select-none disabled:pointer-events-none disabled:opacity-30",
3271
3299
  "overflow-hidden text-ellipsis whitespace-nowrap cursor-pointer"
3272
3300
  ],
3273
3301
  {
3274
3302
  variants: {
3275
3303
  variant: {
3276
- text: "bg-btn-primary hover:bg-btn-primary-hover active:bg-btn-active active:text-alt",
3277
- filled: "bg-btn-secondary hover:bg-btn-secondary-hover active:bg-btn-active active:text-alt",
3278
- solid: "bg-btn-tertiary text-alt hover:bg-btn-tertiary-hover active:bg-btn-active",
3279
- outline: "bg-btn-primary hover:bg-btn-primary-hover active:bg-btn-active active:text-alt border border-bdr-strong"
3304
+ text: "bg-btn-primary hover:bg-btn-primary-hover hover:text-main",
3305
+ filled: "bg-btn-secondary hover:bg-btn-secondary-hover hover:text-main",
3306
+ solid: "bg-btn-tertiary text-alt hover:bg-btn-tertiary-hover",
3307
+ outline: "bg-btn-primary hover:bg-btn-primary-hover border border-bdr-strong"
3280
3308
  },
3281
3309
  size: {
3282
3310
  sm: "h-9 px-3.5 gap-2 text-sm",
@@ -3366,6 +3394,23 @@ function useControlledState(controlledValue, defaultValue, onChange) {
3366
3394
  );
3367
3395
  return [value, setValue];
3368
3396
  }
3397
+ function useControlledStateWithNull(controlledValue, defaultValue, onChange) {
3398
+ const internalControlled = controlledValue === null ? void 0 : controlledValue;
3399
+ const internalOnChange = useCallback(
3400
+ (value) => {
3401
+ onChange?.(value ?? null);
3402
+ },
3403
+ [onChange]
3404
+ );
3405
+ const [internalValue, setInternalValue] = useControlledState(internalControlled, defaultValue, internalOnChange);
3406
+ const setValue = useCallback(
3407
+ (value) => {
3408
+ setInternalValue(value ?? void 0);
3409
+ },
3410
+ [setInternalValue]
3411
+ );
3412
+ return [internalValue, setValue];
3413
+ }
3369
3414
  function useItemRegistry() {
3370
3415
  const itemsRef = useRef(/* @__PURE__ */ new Map());
3371
3416
  const registerItem = useCallback((id, disabled = false) => {
@@ -3701,10 +3746,7 @@ const checkboxBoxVariants = cva(
3701
3746
  {
3702
3747
  variants: {
3703
3748
  editable: {
3704
- true: [
3705
- "peer-focus-visible:outline-none peer-focus-visible:ring-3 peer-focus-visible:ring-ring/50 peer-focus-visible:ring-offset-0",
3706
- "peer-hover:outline-none peer-hover:ring-3 peer-hover:ring-ring/50 peer-hover:ring-offset-0"
3707
- ],
3749
+ true: [],
3708
3750
  false: "opacity-30"
3709
3751
  },
3710
3752
  state: {
@@ -3721,9 +3763,7 @@ const checkboxBoxVariants = cva(
3721
3763
  error: [
3722
3764
  "border-error",
3723
3765
  "peer-checked:bg-error peer-checked:border-error",
3724
- "peer-indeterminate:bg-error peer-indeterminate:border-error",
3725
- "peer-focus-visible:border-error peer-focus-visible:ring-error/50",
3726
- "peer-hover:border-error peer-hover:ring-error/50"
3766
+ "peer-indeterminate:bg-error peer-indeterminate:border-error"
3727
3767
  ]
3728
3768
  }
3729
3769
  },
@@ -3767,15 +3807,17 @@ const Checkbox = forwardRef(
3767
3807
  }
3768
3808
  setCheckedState(nextValue);
3769
3809
  };
3770
- return /* @__PURE__ */ u("div", { children: [
3810
+ return /* @__PURE__ */ u("div", { className: "flex flex-col gap-1 w-fit", children: [
3771
3811
  /* @__PURE__ */ u(
3772
3812
  "label",
3773
3813
  {
3774
3814
  htmlFor: inputId,
3775
3815
  className: cn(
3776
- "relative flex items-center select-none gap-2",
3816
+ "relative flex items-center select-none gap-2 my-0.75 rounded-xs leading-4",
3777
3817
  align === "right" && "flex-row-reverse justify-end",
3778
3818
  editable ? "cursor-pointer" : "cursor-default",
3819
+ editable && "focus-within:outline-none focus-within:ring-3 focus-within:ring-bdr-strong focus-within:ring-offset-3 focus-within:ring-offset-surface-neutral",
3820
+ error && editable && "focus-within:ring-error",
3779
3821
  className
3780
3822
  ),
3781
3823
  children: [
@@ -3824,7 +3866,7 @@ const Checkbox = forwardRef(
3824
3866
  ]
3825
3867
  }
3826
3868
  ),
3827
- state === "error" && errorMessage && /* @__PURE__ */ u("div", { className: cn("flex items-center gap-2 mt-1 text-error leading-5", disabled && "opacity-30"), children: [
3869
+ state === "error" && errorMessage && /* @__PURE__ */ u("div", { className: cn("flex items-center gap-2 text-error leading-5", disabled && "opacity-30"), children: [
3828
3870
  /* @__PURE__ */ u(OctagonAlert, { size: 14, strokeWidth: 2.5 }),
3829
3871
  errorMessage
3830
3872
  ] })
@@ -3938,7 +3980,7 @@ const ComboboxRoot = ({
3938
3980
  }
3939
3981
  setStagedSelection(updateArrayIfChanged(appliedSelection));
3940
3982
  }, [stagingEnabled, appliedSelection]);
3941
- const [activeInternal, setActiveInternal] = useControlledState(controlledActive, defaultActive, setActive);
3983
+ const [activeInternal, setActiveInternal] = useControlledStateWithNull(controlledActive, defaultActive, setActive);
3942
3984
  const { registerItem, unregisterItem, getItems, isItemDisabled } = useItemRegistry();
3943
3985
  const setInputValue = useCallback(
3944
3986
  (next) => {
@@ -4121,13 +4163,13 @@ const comboboxControlVariants = cva(
4121
4163
  [
4122
4164
  "flex gap-2.5 items-center",
4123
4165
  "h-12 rounded-sm border bg-surface-neutral",
4124
- "focus-within:outline-none focus-within:ring-3 focus-within:ring-ring/50 focus-within:ring-offset-0",
4166
+ "focus-within:outline-none focus-within:ring-3 focus-within:ring-bdr-strong focus-within:ring-offset-3 focus-within:ring-offset-surface-neutral",
4125
4167
  "transition-highlight"
4126
4168
  ],
4127
4169
  {
4128
4170
  variants: {
4129
4171
  error: {
4130
- true: "border-error focus-within:border-error focus-within:ring-error/50",
4172
+ true: "border-error focus-within:border-error focus-within:ring-error",
4131
4173
  false: "border-bdr-subtle focus-within:border-bdr-strong"
4132
4174
  },
4133
4175
  open: {
@@ -4166,34 +4208,32 @@ const ComboboxControl = ({ className, children, ...props }) => {
4166
4208
  );
4167
4209
  };
4168
4210
  ComboboxControl.displayName = "Combobox.Control";
4169
- const ComboboxInput = forwardRef(
4170
- ({ placeholder, ...props }, ref) => {
4171
- const innerRef = useRef(null);
4172
- const { open, keyHandler, baseId, active, disabled, error } = useCombobox();
4173
- useEffect(() => {
4174
- if (open && !disabled) {
4175
- innerRef.current?.focus();
4176
- }
4177
- }, [open, disabled]);
4178
- return /* @__PURE__ */ u(
4179
- SearchField.Input,
4180
- {
4181
- ref: useComposedRefs(ref, innerRef),
4182
- id: `${baseId}-input`,
4183
- onKeyDown: keyHandler,
4184
- "aria-disabled": disabled,
4185
- "aria-invalid": error ?? void 0,
4186
- role: "combobox",
4187
- "aria-autocomplete": "list",
4188
- "aria-expanded": open,
4189
- "aria-haspopup": "listbox",
4190
- "aria-controls": `${baseId}-listbox`,
4191
- "aria-activedescendant": active ? `${baseId}-listbox-option-${active}` : void 0,
4192
- ...props
4193
- }
4194
- );
4195
- }
4196
- );
4211
+ const ComboboxInput = forwardRef((props, ref) => {
4212
+ const innerRef = useRef(null);
4213
+ const { open, keyHandler, baseId, active, disabled, error } = useCombobox();
4214
+ useEffect(() => {
4215
+ if (open && !disabled) {
4216
+ innerRef.current?.focus();
4217
+ }
4218
+ }, [open, disabled]);
4219
+ return /* @__PURE__ */ u(
4220
+ SearchField.Input,
4221
+ {
4222
+ ref: useComposedRefs(ref, innerRef),
4223
+ id: `${baseId}-input`,
4224
+ onKeyDown: keyHandler,
4225
+ "aria-disabled": disabled,
4226
+ "aria-invalid": error ?? void 0,
4227
+ role: "combobox",
4228
+ "aria-autocomplete": "list",
4229
+ "aria-expanded": open,
4230
+ "aria-haspopup": "listbox",
4231
+ "aria-controls": `${baseId}-listbox`,
4232
+ "aria-activedescendant": active ? `${baseId}-listbox-option-${active}` : void 0,
4233
+ ...props
4234
+ }
4235
+ );
4236
+ });
4197
4237
  ComboboxInput.displayName = "Combobox.Input";
4198
4238
  const ComboboxSearch = ({ children, className, ...props }) => {
4199
4239
  const { inputValue, setInputValue } = useCombobox();
@@ -4202,17 +4242,17 @@ const ComboboxSearch = ({ children, className, ...props }) => {
4202
4242
  {
4203
4243
  value: inputValue,
4204
4244
  onChange: setInputValue,
4205
- className: cn(
4206
- "w-full pr-0",
4207
- "border-0 focus-within:border-0 focus-within:ring-0 focus-within:ring-offset-0",
4208
- className
4209
- ),
4245
+ className: cn("w-full pr-0", "border-0 focus-within:border-0 focus-within:ring-0", className),
4210
4246
  ...props,
4211
4247
  children
4212
4248
  }
4213
4249
  );
4214
4250
  };
4215
4251
  ComboboxSearch.displayName = "Combobox.Search";
4252
+ const ComboboxSearchIcon = ({ className, ...props }) => {
4253
+ return /* @__PURE__ */ u(SearchField.Icon, { className, ...props });
4254
+ };
4255
+ ComboboxSearchIcon.displayName = "Combobox.SearchIcon";
4216
4256
  const ComboboxToggle = ({ className, ...props }) => {
4217
4257
  const { open, setOpen, disabled } = useCombobox();
4218
4258
  return /* @__PURE__ */ u(
@@ -4280,7 +4320,7 @@ const Combobox = Object.assign(ComboboxRoot, {
4280
4320
  Control: ComboboxControl,
4281
4321
  Search: ComboboxSearch,
4282
4322
  Input: ComboboxInput,
4283
- SearchIcon: SearchField.Icon,
4323
+ SearchIcon: ComboboxSearchIcon,
4284
4324
  Toggle: ComboboxToggle,
4285
4325
  Apply: ComboboxApply,
4286
4326
  Popup: ComboboxPopup
@@ -4594,14 +4634,14 @@ const inputContainerVariants = cva(
4594
4634
  [
4595
4635
  "relative flex rounded-sm overflow-hidden",
4596
4636
  "h-12 border focus-within:border-bdr-solid",
4597
- "focus-within:outline-none focus-within:ring-3 focus-within:ring-ring/50 focus-within:ring-offset-0",
4637
+ "focus-within:outline-none focus-within:ring-3 focus-within:ring-bdr-strong focus-within:ring-offset-3 focus-within:ring-offset-surface-neutral",
4598
4638
  "transition-highlight"
4599
4639
  ],
4600
4640
  {
4601
4641
  variants: {
4602
4642
  state: {
4603
4643
  default: "border-bdr-subtle focus-within:border-bdr-strong",
4604
- error: "border-error focus-within:border-error focus-within:ring-error/50"
4644
+ error: "border-error focus-within:border-error focus-within:ring-error"
4605
4645
  },
4606
4646
  disabled: {
4607
4647
  true: "select-none"
@@ -4700,6 +4740,8 @@ const Link = forwardRef(
4700
4740
  "inline-flex items-center align-baseline gap-1",
4701
4741
  "text-sm font-normal underline underline-offset-3 decoration-1",
4702
4742
  "visited:text-link-visited hover:text-main-hover",
4743
+ "focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-bdr-strong focus-visible:bg-main focus-visible:text-rev",
4744
+ "transition-highlight",
4703
4745
  className
4704
4746
  ),
4705
4747
  target,
@@ -4764,7 +4806,7 @@ const ListItemRoot = ({ children, className, selected, ...props }) => {
4764
4806
  selected && "bg-surface-primary-selected text-alt",
4765
4807
  className
4766
4808
  ),
4767
- "data-tone": selected && "inverse",
4809
+ "data-tone": selected ? "inverse" : void 0,
4768
4810
  role: "listitem",
4769
4811
  ...props,
4770
4812
  children: [
@@ -4808,7 +4850,7 @@ const ListboxRoot = ({
4808
4850
  () => isSelectionControlled ? new Set(controlledSelection) : uncontrolledSelection,
4809
4851
  [isSelectionControlled, controlledSelection, uncontrolledSelection]
4810
4852
  );
4811
- const [active, updateActive] = useControlledState(controlledActive, defaultActive, setActive);
4853
+ const [active, updateActive] = useControlledStateWithNull(controlledActive, defaultActive, setActive);
4812
4854
  const internalRegistry = useItemRegistry();
4813
4855
  const registerItem = externalRegisterItem ?? internalRegistry.registerItem;
4814
4856
  const unregisterItem = externalUnregisterItem ?? internalRegistry.unregisterItem;
@@ -4920,7 +4962,8 @@ const ListboxContent = forwardRef(
4920
4962
  className: cn(
4921
4963
  "flex flex-col items-start grow shrink-0 basis-0",
4922
4964
  "max-h-100 overflow-y-auto",
4923
- "focus-within:outline-none focus-within:ring-3 focus-within:ring-ring/50",
4965
+ "focus-within:outline-none focus-within:ring-3 focus-within:ring-bdr-strong focus-within:ring-offset-3 focus-within:ring-offset-surface-neutral",
4966
+ "transition-highlight",
4924
4967
  disabled && "pointer-events-none select-none opacity-30",
4925
4968
  className
4926
4969
  ),
@@ -4995,7 +5038,7 @@ const ListboxItem = ({ value, disabled = false, children, className, ...props })
4995
5038
  "aria-disabled": isDisabled ?? void 0,
4996
5039
  "data-value": value,
4997
5040
  "data-active": isActive || void 0,
4998
- "data-tone": isSelected && "inverse",
5041
+ "data-tone": isSelected ? "inverse" : void 0,
4999
5042
  onClick: handleClick,
5000
5043
  ...props,
5001
5044
  children
@@ -5068,10 +5111,11 @@ const MenuTrigger = forwardRef(
5068
5111
  type: asChild ? void 0 : "button",
5069
5112
  "aria-haspopup": "menu",
5070
5113
  "aria-expanded": open,
5114
+ "data-active": open,
5071
5115
  "aria-controls": open ? menuId : void 0,
5072
5116
  onClick: handleClick,
5073
5117
  onKeyDown: handleKeyDown,
5074
- className,
5118
+ className: cn(open && "bg-btn-active text-alt", className),
5075
5119
  ...props,
5076
5120
  children
5077
5121
  }
@@ -5265,7 +5309,7 @@ MenuContent.displayName = "Menu.Content";
5265
5309
  const menuItemVariants = cva("flex w-full items-center px-4.5 py-2.5 gap-x-1.25 cursor-pointer text-sm outline-none", {
5266
5310
  variants: {
5267
5311
  active: {
5268
- true: "bg-surface-primary-selected",
5312
+ true: "bg-surface-primary-selected text-alt",
5269
5313
  false: ""
5270
5314
  },
5271
5315
  disabled: {
@@ -5277,7 +5321,7 @@ const menuItemVariants = cva("flex w-full items-center px-4.5 py-2.5 gap-x-1.25
5277
5321
  {
5278
5322
  active: true,
5279
5323
  disabled: false,
5280
- class: "text-alt outline-none ring-3 ring-inset ring-bdr-strong"
5324
+ class: "focus-visible:ring-3 focus-visible:ring-inset focus-visible:ring-bdr-strong hover:bg-surface-primary-selected hover:text-alt"
5281
5325
  }
5282
5326
  ],
5283
5327
  defaultVariants: {
@@ -5285,59 +5329,102 @@ const menuItemVariants = cva("flex w-full items-center px-4.5 py-2.5 gap-x-1.25
5285
5329
  disabled: false
5286
5330
  }
5287
5331
  });
5288
- const MenuItem = ({
5289
- id: providedId,
5290
- asChild = false,
5291
- disabled = false,
5292
- onSelect,
5293
- className,
5294
- children,
5295
- ...props
5296
- }) => {
5297
- const id = usePrefixedId(providedId, providedId ? void 0 : "menu-item");
5298
- const { active, setActive, setOpen, registerItem, unregisterItem } = useMenu();
5299
- const isActive = active === id;
5300
- const isDisabled = disabled;
5301
- useEffect(() => {
5302
- registerItem(id, disabled);
5303
- return () => unregisterItem(id);
5304
- }, [id, disabled, registerItem, unregisterItem]);
5305
- const handleClick = useCallback(() => {
5306
- if (isDisabled) {
5307
- return;
5308
- }
5309
- const event = new Event("select", { bubbles: true, cancelable: true });
5310
- onSelect?.(event);
5311
- if (!event.defaultPrevented) {
5312
- setOpen(false);
5313
- }
5314
- }, [isDisabled, onSelect, setOpen]);
5315
- const handlePointerMove = useCallback(() => {
5316
- if (!isActive) {
5317
- setActive(id);
5318
- }
5319
- }, [isActive, setActive, id]);
5320
- const handlePointerLeave = useCallback(() => {
5321
- setActive(void 0);
5322
- }, [setActive]);
5323
- const Comp = asChild ? Slot : "div";
5324
- return /* @__PURE__ */ u(
5325
- Comp,
5326
- {
5327
- id,
5328
- role: "menuitem",
5329
- "aria-disabled": isDisabled,
5330
- "data-active": isActive || void 0,
5331
- "data-disabled": isDisabled || void 0,
5332
- className: cn(menuItemVariants({ active: isActive, disabled: isDisabled }), className),
5333
- onClick: handleClick,
5334
- onPointerMove: handlePointerMove,
5335
- onPointerLeave: handlePointerLeave,
5336
- ...props,
5337
- children
5338
- }
5339
- );
5340
- };
5332
+ const MenuItem = forwardRef(
5333
+ ({
5334
+ id: providedId,
5335
+ asChild = false,
5336
+ disabled = false,
5337
+ onSelect,
5338
+ className,
5339
+ children,
5340
+ onClick,
5341
+ onPointerMove,
5342
+ onPointerLeave,
5343
+ onFocus,
5344
+ ...props
5345
+ }, ref) => {
5346
+ const id = usePrefixedId(providedId, providedId ? void 0 : "menu-item");
5347
+ const { active, setActive, setOpen, registerItem, unregisterItem, getItems, isItemDisabled } = useMenu();
5348
+ const isActive = active === id;
5349
+ const isDisabled = disabled;
5350
+ const itemRef = useRef(null);
5351
+ const composedRefs = useComposedRefs(ref, itemRef);
5352
+ useEffect(() => {
5353
+ registerItem(id, disabled);
5354
+ return () => unregisterItem(id);
5355
+ }, [id, disabled, registerItem, unregisterItem]);
5356
+ const handleClick = useCallback(
5357
+ (e) => {
5358
+ onClick?.(e);
5359
+ if (isDisabled) {
5360
+ return;
5361
+ }
5362
+ const event = new Event("select", { bubbles: true, cancelable: true });
5363
+ onSelect?.(event);
5364
+ if (!event.defaultPrevented) {
5365
+ setOpen(false);
5366
+ }
5367
+ },
5368
+ [isDisabled, onSelect, setOpen, onClick]
5369
+ );
5370
+ const handlePointerMove = useCallback(
5371
+ (e) => {
5372
+ onPointerMove?.(e);
5373
+ if (!isActive && !isDisabled) {
5374
+ setActive(id);
5375
+ }
5376
+ },
5377
+ [isActive, setActive, id, isDisabled, onPointerMove]
5378
+ );
5379
+ const handlePointerLeave = useCallback(
5380
+ (e) => {
5381
+ onPointerLeave?.(e);
5382
+ if (document.activeElement !== itemRef.current) {
5383
+ setActive(void 0);
5384
+ }
5385
+ },
5386
+ [setActive, onPointerLeave, itemRef]
5387
+ );
5388
+ const handleFocus = useCallback(
5389
+ (e) => {
5390
+ onFocus?.(e);
5391
+ if (isDisabled) return;
5392
+ setActive(id);
5393
+ },
5394
+ [onFocus, isDisabled, setActive, id]
5395
+ );
5396
+ const items = getItems();
5397
+ const firstItem = items.length > 0 ? items[0] : void 0;
5398
+ const fallbackFocusableId = active ?? items.find((itemId) => !isItemDisabled(itemId)) ?? firstItem ?? id;
5399
+ const isFocusable = !isDisabled && fallbackFocusableId === id;
5400
+ useEffect(() => {
5401
+ if (!isDisabled && isActive && document.activeElement !== itemRef.current) {
5402
+ itemRef.current?.focus();
5403
+ }
5404
+ }, [isActive, isDisabled]);
5405
+ const Comp = asChild ? Slot : "div";
5406
+ return /* @__PURE__ */ u(
5407
+ Comp,
5408
+ {
5409
+ ref: composedRefs,
5410
+ id,
5411
+ role: "menuitem",
5412
+ tabIndex: isDisabled ? -1 : isFocusable ? 0 : -1,
5413
+ "aria-disabled": isDisabled,
5414
+ "data-active": isActive || void 0,
5415
+ "data-disabled": isDisabled || void 0,
5416
+ "data-tone": isActive ? "inverse" : void 0,
5417
+ className: cn(menuItemVariants({ active: isActive, disabled: isDisabled }), className),
5418
+ onClick: handleClick,
5419
+ onPointerMove: handlePointerMove,
5420
+ onPointerLeave: handlePointerLeave,
5421
+ onFocus: handleFocus,
5422
+ ...props,
5423
+ children
5424
+ }
5425
+ );
5426
+ }
5427
+ );
5341
5428
  MenuItem.displayName = "Menu.Item";
5342
5429
  const MenuLabel = forwardRef(({ className, children, ...props }, ref) => {
5343
5430
  return /* @__PURE__ */ u("div", { ref, className: cn("px-3 py-1.5 text-xs font-semibold text-subtle", className), ...props, children });
@@ -5365,6 +5452,807 @@ const Menu = Object.assign(MenuRoot, {
5365
5452
  Label: MenuLabel,
5366
5453
  Separator: MenuSeparator
5367
5454
  });
5455
+ const MenubarContentContext = createContext(void 0);
5456
+ function useMenubarContent() {
5457
+ const context = useContext(MenubarContentContext);
5458
+ if (!context) {
5459
+ throw new Error("Menubar menu item components must be used within Menubar.Content");
5460
+ }
5461
+ return context;
5462
+ }
5463
+ const MenubarRoot = ({ defaultActive, onActiveChange, id, children }) => {
5464
+ const menubarId = usePrefixedId(id, "menubar");
5465
+ const menubarRef = useRef(null);
5466
+ const [active, setActiveInternal] = useState(defaultActive);
5467
+ const setActive = useCallback(
5468
+ (value2) => {
5469
+ setActiveInternal(value2);
5470
+ onActiveChange?.(value2);
5471
+ },
5472
+ [onActiveChange]
5473
+ );
5474
+ const { registerItem, unregisterItem, getItems, isItemDisabled } = useItemRegistry();
5475
+ const [openMenuId, setOpenMenuId] = useState(void 0);
5476
+ const value = useMemo(
5477
+ () => ({
5478
+ active,
5479
+ setActive,
5480
+ registerItem,
5481
+ unregisterItem,
5482
+ getItems,
5483
+ isItemDisabled,
5484
+ openMenuId,
5485
+ setOpenMenuId,
5486
+ menubarId,
5487
+ menubarRef
5488
+ }),
5489
+ [active, setActive, registerItem, unregisterItem, getItems, isItemDisabled, openMenuId, menubarId]
5490
+ );
5491
+ return /* @__PURE__ */ u(MenubarProvider, { value, children });
5492
+ };
5493
+ MenubarRoot.displayName = "Menubar.Root";
5494
+ const MenubarNav = forwardRef(
5495
+ ({ "aria-label": ariaLabel, loop = true, className, children, onKeyDown, onBlur, ...props }, ref) => {
5496
+ const { active, setActive, getItems, isItemDisabled, menubarId, menubarRef, setOpenMenuId, openMenuId } = useMenubar();
5497
+ const composedRefs = useComposedRefs(ref, menubarRef);
5498
+ const { handleKeyDown: handleNavKeyDown } = useKeyboardNavigation({
5499
+ getItems,
5500
+ isItemDisabled,
5501
+ active,
5502
+ setActive,
5503
+ loop,
5504
+ orientation: "horizontal",
5505
+ onSelect: (id) => {
5506
+ const itemElement = document.getElementById(id);
5507
+ if (itemElement) {
5508
+ itemElement.click();
5509
+ }
5510
+ }
5511
+ });
5512
+ const handleKeyDown_ = useCallback(
5513
+ (e) => {
5514
+ onKeyDown?.(e);
5515
+ const isActionKey = e.key === "ArrowDown" || e.key === "Enter" || e.key === " ";
5516
+ if (isActionKey && active) {
5517
+ const activeElement = document.getElementById(active);
5518
+ if (activeElement && activeElement.getAttribute("aria-haspopup") === "menu") {
5519
+ e.preventDefault();
5520
+ setOpenMenuId(active);
5521
+ return;
5522
+ }
5523
+ }
5524
+ if (e.key === "ArrowUp" && active) {
5525
+ const activeElement = document.getElementById(active);
5526
+ if (activeElement && activeElement.getAttribute("aria-haspopup") === "menu") {
5527
+ e.preventDefault();
5528
+ setOpenMenuId(active);
5529
+ return;
5530
+ }
5531
+ }
5532
+ handleNavKeyDown(e);
5533
+ },
5534
+ [onKeyDown, handleNavKeyDown, active, setOpenMenuId]
5535
+ );
5536
+ const handleBlur = useCallback(
5537
+ (e) => {
5538
+ onBlur?.(e);
5539
+ const relatedTarget = e.relatedTarget;
5540
+ if (menubarRef?.current && relatedTarget instanceof Node) {
5541
+ if (menubarRef.current.contains(relatedTarget)) {
5542
+ return;
5543
+ }
5544
+ }
5545
+ if (openMenuId) {
5546
+ return;
5547
+ }
5548
+ setActive(void 0);
5549
+ },
5550
+ [onBlur, setActive, menubarRef, openMenuId]
5551
+ );
5552
+ useEffect(() => {
5553
+ if (!active || !menubarRef?.current) {
5554
+ return;
5555
+ }
5556
+ const el = menubarRef.current.querySelector(`#${active}`);
5557
+ if (el) {
5558
+ el.scrollIntoView({
5559
+ block: "nearest",
5560
+ inline: "nearest",
5561
+ behavior: "auto"
5562
+ });
5563
+ }
5564
+ }, [active, menubarRef]);
5565
+ return /* @__PURE__ */ u(
5566
+ "div",
5567
+ {
5568
+ ref: composedRefs,
5569
+ id: menubarId,
5570
+ role: "menubar",
5571
+ "aria-label": ariaLabel,
5572
+ "aria-orientation": "horizontal",
5573
+ tabIndex: -1,
5574
+ className: cn("flex items-center gap-1.5 p-1.5", className),
5575
+ onKeyDown: handleKeyDown_,
5576
+ onBlur: handleBlur,
5577
+ ...props,
5578
+ children
5579
+ }
5580
+ );
5581
+ }
5582
+ );
5583
+ MenubarNav.displayName = "Menubar.Nav";
5584
+ const MenubarButton = forwardRef(
5585
+ ({
5586
+ id: providedId,
5587
+ disabled = false,
5588
+ onSelect,
5589
+ asChild,
5590
+ className,
5591
+ children,
5592
+ onClick,
5593
+ onPointerMove,
5594
+ onPointerDown,
5595
+ onPointerLeave,
5596
+ onFocus,
5597
+ ...props
5598
+ }, ref) => {
5599
+ const id = usePrefixedId(providedId, "menubar-button");
5600
+ const { active, setActive, registerItem, unregisterItem, openMenuId, getItems, isItemDisabled } = useMenubar();
5601
+ const isActive = active === id;
5602
+ const buttonRef = useRef(null);
5603
+ const composedRefs = useComposedRefs(ref, buttonRef);
5604
+ useEffect(() => {
5605
+ registerItem(id, disabled);
5606
+ return () => unregisterItem(id);
5607
+ }, [id, disabled, registerItem, unregisterItem]);
5608
+ const handlePointerDown = useCallback(
5609
+ (e) => {
5610
+ onPointerDown?.(e);
5611
+ if (disabled) return;
5612
+ setActive(id);
5613
+ },
5614
+ [disabled, id, onPointerDown, setActive]
5615
+ );
5616
+ const handleClick = useCallback(
5617
+ (e) => {
5618
+ onClick?.(e);
5619
+ if (disabled) return;
5620
+ if (!isActive) {
5621
+ setActive(id);
5622
+ }
5623
+ const event = new Event("select", { bubbles: true, cancelable: true });
5624
+ onSelect?.(event);
5625
+ },
5626
+ [disabled, isActive, onClick, setActive, id, onSelect]
5627
+ );
5628
+ const handlePointerMove = useCallback(
5629
+ (e) => {
5630
+ onPointerMove?.(e);
5631
+ if (!disabled && openMenuId && active !== void 0) {
5632
+ setActive(id);
5633
+ }
5634
+ },
5635
+ [disabled, openMenuId, setActive, active, id, onPointerMove]
5636
+ );
5637
+ const handlePointerLeave = useCallback(
5638
+ (e) => {
5639
+ onPointerLeave?.(e);
5640
+ if ((!openMenuId || openMenuId !== id) && document.activeElement !== buttonRef.current) {
5641
+ setActive(void 0);
5642
+ }
5643
+ },
5644
+ [openMenuId, id, active, setActive, onPointerLeave, buttonRef]
5645
+ );
5646
+ const handleFocus = useCallback(
5647
+ (e) => {
5648
+ onFocus?.(e);
5649
+ if (disabled) return;
5650
+ setActive(id);
5651
+ },
5652
+ [onFocus, disabled, setActive, id]
5653
+ );
5654
+ const items = getItems();
5655
+ const firstItem = items.length > 0 ? items[0] : void 0;
5656
+ const fallbackFocusableId = active ?? items.find((itemId) => !isItemDisabled(itemId)) ?? firstItem ?? id;
5657
+ const isFocusable = !disabled && fallbackFocusableId === id;
5658
+ useEffect(() => {
5659
+ if (!disabled && isActive && document.activeElement !== buttonRef.current) {
5660
+ buttonRef.current?.focus();
5661
+ }
5662
+ }, [isActive, disabled]);
5663
+ const Comp = asChild ? Slot : "button";
5664
+ return /* @__PURE__ */ u(
5665
+ Comp,
5666
+ {
5667
+ ref: composedRefs,
5668
+ id,
5669
+ type: asChild ? void 0 : "button",
5670
+ role: "menuitem",
5671
+ tabIndex: disabled ? -1 : isFocusable ? 0 : -1,
5672
+ "aria-disabled": disabled,
5673
+ "data-disabled": disabled || void 0,
5674
+ "data-tone": isActive ? "inverse" : void 0,
5675
+ className: cn(
5676
+ "group px-4.5 py-2.5 rounded-sm text-sm cursor-pointer outline-none",
5677
+ "focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-bdr-strong focus-visible:ring-offset-3",
5678
+ className
5679
+ ),
5680
+ onClick: handleClick,
5681
+ onPointerDown: handlePointerDown,
5682
+ onPointerMove: handlePointerMove,
5683
+ onPointerLeave: handlePointerLeave,
5684
+ onFocus: handleFocus,
5685
+ ...props,
5686
+ children
5687
+ }
5688
+ );
5689
+ }
5690
+ );
5691
+ MenubarButton.displayName = "Menubar.Button";
5692
+ const MenubarSeparator = forwardRef(
5693
+ ({ className, ...props }, ref) => {
5694
+ const menuContentContext = useContext(MenubarContentContext);
5695
+ const isInsideMenuDropdown = menuContentContext !== void 0;
5696
+ if (isInsideMenuDropdown) {
5697
+ return /* @__PURE__ */ u(
5698
+ "div",
5699
+ {
5700
+ ref,
5701
+ role: "separator",
5702
+ "aria-orientation": "horizontal",
5703
+ className: cn("my-1 h-px w-full bg-bdr-subtle", className),
5704
+ ...props
5705
+ }
5706
+ );
5707
+ }
5708
+ return /* @__PURE__ */ u(
5709
+ "div",
5710
+ {
5711
+ ref,
5712
+ role: "separator",
5713
+ "aria-orientation": "vertical",
5714
+ className: cn("mx-1 w-px h-4 bg-bdr-subtle", className),
5715
+ ...props
5716
+ }
5717
+ );
5718
+ }
5719
+ );
5720
+ MenubarSeparator.displayName = "Menubar.Separator";
5721
+ const MenubarMenu = ({ id: providedId, disabled = false, children }) => {
5722
+ const menuId = usePrefixedId(providedId, "menubar-menu");
5723
+ const contentId = `${menuId}-content`;
5724
+ const triggerRef = useRef(null);
5725
+ const menubarContext = useMenubar();
5726
+ const { registerItem, unregisterItem, openMenuId, setOpenMenuId } = menubarContext;
5727
+ const [open, setOpenInternal] = useState(false);
5728
+ useEffect(() => {
5729
+ registerItem(menuId, disabled);
5730
+ return () => unregisterItem(menuId);
5731
+ }, [menuId, disabled, registerItem, unregisterItem]);
5732
+ useEffect(() => {
5733
+ if (openMenuId === menuId && !open) {
5734
+ setOpenInternal(true);
5735
+ } else if (openMenuId !== menuId && open) {
5736
+ setOpenInternal(false);
5737
+ }
5738
+ }, [openMenuId, menuId, open]);
5739
+ const setOpen = useCallback(
5740
+ (newOpen) => {
5741
+ setOpenInternal(newOpen);
5742
+ setOpenMenuId(newOpen ? menuId : void 0);
5743
+ },
5744
+ [menuId, setOpenMenuId]
5745
+ );
5746
+ const value = useMemo(
5747
+ () => ({
5748
+ menuId,
5749
+ contentId,
5750
+ open,
5751
+ setOpen,
5752
+ menubarContext,
5753
+ triggerRef
5754
+ }),
5755
+ [menuId, contentId, open, setOpen, menubarContext]
5756
+ );
5757
+ return /* @__PURE__ */ u(MenubarMenuProvider, { value, children });
5758
+ };
5759
+ MenubarMenu.displayName = "Menubar.Menu";
5760
+ const MenubarTrigger = forwardRef(
5761
+ ({
5762
+ asChild,
5763
+ className,
5764
+ children,
5765
+ onClick,
5766
+ onKeyDown,
5767
+ onPointerMove,
5768
+ onPointerDown,
5769
+ onPointerLeave,
5770
+ onFocus,
5771
+ ...props
5772
+ }, ref) => {
5773
+ const { menuId, contentId, open, setOpen, triggerRef, menubarContext } = useMenubarMenu();
5774
+ const { active, setActive, openMenuId, isItemDisabled, getItems } = menubarContext;
5775
+ const composedRefs = useComposedRefs(ref, triggerRef);
5776
+ const isActive = active === menuId;
5777
+ const disabled = isItemDisabled(menuId);
5778
+ const handleClick = useCallback(
5779
+ (e) => {
5780
+ onClick?.(e);
5781
+ if (disabled) return;
5782
+ setOpen(!open);
5783
+ },
5784
+ [disabled, open, setOpen, onClick]
5785
+ );
5786
+ const handleKeyDown = useCallback(
5787
+ (e) => {
5788
+ onKeyDown?.(e);
5789
+ if (disabled) return;
5790
+ if (e.key === "ArrowDown" && !open) {
5791
+ e.preventDefault();
5792
+ setOpen(true);
5793
+ } else if ((e.key === " " || e.key === "Enter") && !open) {
5794
+ e.preventDefault();
5795
+ setOpen(true);
5796
+ }
5797
+ },
5798
+ [disabled, open, setOpen, onKeyDown]
5799
+ );
5800
+ const handlePointerDown = useCallback(
5801
+ (e) => {
5802
+ onPointerDown?.(e);
5803
+ if (disabled) return;
5804
+ setActive(menuId);
5805
+ },
5806
+ [disabled, menuId, onPointerDown, setActive]
5807
+ );
5808
+ const handlePointerMove = useCallback(
5809
+ (e) => {
5810
+ onPointerMove?.(e);
5811
+ if (disabled) return;
5812
+ if (active === void 0) return;
5813
+ setActive(menuId);
5814
+ if (openMenuId && openMenuId !== menuId) {
5815
+ setOpen(true);
5816
+ }
5817
+ },
5818
+ [disabled, openMenuId, menuId, active, setActive, setOpen, onPointerMove]
5819
+ );
5820
+ const handlePointerLeave = useCallback(
5821
+ (e) => {
5822
+ onPointerLeave?.(e);
5823
+ if (!open && document.activeElement !== triggerRef?.current) {
5824
+ setActive(void 0);
5825
+ }
5826
+ },
5827
+ [open, setActive, onPointerLeave, triggerRef]
5828
+ );
5829
+ const handleFocus = useCallback(
5830
+ (e) => {
5831
+ onFocus?.(e);
5832
+ if (disabled) return;
5833
+ setActive(menuId);
5834
+ },
5835
+ [onFocus, disabled, setActive, menuId]
5836
+ );
5837
+ const items = getItems();
5838
+ const firstItem = items.length > 0 ? items[0] : void 0;
5839
+ const fallbackFocusableId = active ?? items.find((itemId) => !isItemDisabled(itemId)) ?? firstItem ?? menuId;
5840
+ const isFocusable = !disabled && fallbackFocusableId === menuId;
5841
+ useEffect(() => {
5842
+ if (!disabled && isActive && triggerRef && document.activeElement !== triggerRef.current) {
5843
+ triggerRef.current?.focus();
5844
+ }
5845
+ }, [isActive, disabled, triggerRef]);
5846
+ const Comp = asChild ? Slot : "button";
5847
+ return /* @__PURE__ */ u(
5848
+ Comp,
5849
+ {
5850
+ ref: composedRefs,
5851
+ id: menuId,
5852
+ type: asChild ? void 0 : "button",
5853
+ role: "menuitem",
5854
+ tabIndex: disabled ? -1 : isFocusable ? 0 : -1,
5855
+ "aria-haspopup": "menu",
5856
+ "aria-expanded": open,
5857
+ "aria-controls": open ? contentId : void 0,
5858
+ "aria-disabled": disabled,
5859
+ "data-active": open,
5860
+ "data-disabled": disabled || void 0,
5861
+ "data-state": open ? "open" : "closed",
5862
+ "data-tone": isActive ? "inverse" : void 0,
5863
+ className: cn(
5864
+ "group px-4.5 py-2.5 rounded-sm text-sm cursor-pointer outline-none",
5865
+ "focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-bdr-strong focus-visible:ring-offset-3",
5866
+ open && "bg-btn-active text-alt",
5867
+ disabled && "opacity-30 cursor-not-allowed pointer-events-none",
5868
+ className
5869
+ ),
5870
+ onClick: handleClick,
5871
+ onKeyDown: handleKeyDown,
5872
+ onPointerDown: handlePointerDown,
5873
+ onPointerMove: handlePointerMove,
5874
+ onPointerLeave: handlePointerLeave,
5875
+ onFocus: handleFocus,
5876
+ ...props,
5877
+ children
5878
+ }
5879
+ );
5880
+ }
5881
+ );
5882
+ MenubarTrigger.displayName = "Menubar.Trigger";
5883
+ const MenubarPortal = ({ container, forceMount = false, children }) => {
5884
+ const { open } = useMenubarMenu();
5885
+ const [mounted, setMounted] = useState(false);
5886
+ useEffect(() => {
5887
+ setMounted(true);
5888
+ }, []);
5889
+ if (!mounted) {
5890
+ return null;
5891
+ }
5892
+ const shouldRender = forceMount || open;
5893
+ if (!shouldRender) {
5894
+ return null;
5895
+ }
5896
+ const targetContainer = container ?? document.body;
5897
+ return createPortal(/* @__PURE__ */ u(Fragment, { children }), targetContainer);
5898
+ };
5899
+ MenubarPortal.displayName = "Menubar.Portal";
5900
+ const MenubarContent = forwardRef(
5901
+ ({
5902
+ align = "start",
5903
+ loop = true,
5904
+ forceMount = false,
5905
+ onEscapeKeyDown,
5906
+ onPointerDownOutside,
5907
+ onInteractOutside,
5908
+ className,
5909
+ children,
5910
+ onKeyDown,
5911
+ ...props
5912
+ }, ref) => {
5913
+ const { menuId, contentId, open, setOpen, triggerRef, menubarContext } = useMenubarMenu();
5914
+ const { getItems: getMenubarItems, setActive: setMenubarActive } = menubarContext;
5915
+ const contentRef = useRef(null);
5916
+ const composedRefs = useComposedRefs(ref, contentRef);
5917
+ const { registerItem, unregisterItem, getItems, isItemDisabled } = useItemRegistry();
5918
+ const [active, setActive] = useState(void 0);
5919
+ const [position, setPosition] = useState(null);
5920
+ if (!open && !forceMount) {
5921
+ return null;
5922
+ }
5923
+ const { handleKeyDown: handleNavKeyDown } = useKeyboardNavigation({
5924
+ getItems,
5925
+ isItemDisabled,
5926
+ active,
5927
+ setActive,
5928
+ loop,
5929
+ orientation: "vertical",
5930
+ onSelect: (id) => {
5931
+ const itemElement = document.getElementById(id);
5932
+ if (itemElement) {
5933
+ itemElement.click();
5934
+ }
5935
+ }
5936
+ });
5937
+ const handleKeyDown_ = useCallback(
5938
+ (e) => {
5939
+ onKeyDown?.(e);
5940
+ if (e.key === "Escape") {
5941
+ e.preventDefault();
5942
+ e.stopPropagation();
5943
+ setOpen(false);
5944
+ onEscapeKeyDown?.(e);
5945
+ triggerRef?.current?.focus();
5946
+ return;
5947
+ }
5948
+ if (e.key === "Tab") {
5949
+ setOpen(false);
5950
+ return;
5951
+ }
5952
+ if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
5953
+ e.preventDefault();
5954
+ const menubarItems = getMenubarItems();
5955
+ const currentIndex = menubarItems.indexOf(menuId);
5956
+ if (currentIndex === -1) return;
5957
+ const direction = e.key === "ArrowRight" ? 1 : -1;
5958
+ let nextIndex = (currentIndex + direction + menubarItems.length) % menubarItems.length;
5959
+ let attempts = 0;
5960
+ while (attempts < menubarItems.length && menubarContext.isItemDisabled(menubarItems[nextIndex])) {
5961
+ nextIndex = (nextIndex + direction + menubarItems.length) % menubarItems.length;
5962
+ attempts += 1;
5963
+ }
5964
+ const nextMenuId = menubarItems[nextIndex];
5965
+ if (!nextMenuId || menubarContext.isItemDisabled(nextMenuId)) {
5966
+ return;
5967
+ }
5968
+ setMenubarActive(nextMenuId);
5969
+ const nextElement = document.getElementById(nextMenuId);
5970
+ nextElement?.focus();
5971
+ if (nextElement && nextElement.getAttribute("aria-haspopup") === "menu") {
5972
+ menubarContext.setOpenMenuId(nextMenuId);
5973
+ } else {
5974
+ setOpen(false);
5975
+ }
5976
+ return;
5977
+ }
5978
+ handleNavKeyDown(e);
5979
+ },
5980
+ [
5981
+ onKeyDown,
5982
+ setOpen,
5983
+ onEscapeKeyDown,
5984
+ menuId,
5985
+ getMenubarItems,
5986
+ setMenubarActive,
5987
+ menubarContext,
5988
+ handleNavKeyDown,
5989
+ triggerRef
5990
+ ]
5991
+ );
5992
+ useEffect(() => {
5993
+ if (open && position !== null) {
5994
+ const items = getItems();
5995
+ const firstItem = items.length > 0 ? items[0] : void 0;
5996
+ const firstSelectableItem = items.find((itemId) => !isItemDisabled(itemId)) ?? firstItem;
5997
+ if (firstSelectableItem) {
5998
+ setActive(firstSelectableItem);
5999
+ }
6000
+ } else if (!open) {
6001
+ setActive(void 0);
6002
+ }
6003
+ }, [open, position, getItems, isItemDisabled]);
6004
+ useEffect(() => {
6005
+ if (!open || !triggerRef?.current || !contentRef.current) {
6006
+ return;
6007
+ }
6008
+ const updatePosition = () => {
6009
+ if (!triggerRef?.current || !contentRef.current) return;
6010
+ const triggerRect = triggerRef.current.getBoundingClientRect();
6011
+ const contentRect = contentRef.current.getBoundingClientRect();
6012
+ const viewportWidth = window.innerWidth;
6013
+ const viewportHeight = window.innerHeight;
6014
+ const padding = 10;
6015
+ let top = triggerRect.bottom;
6016
+ let left;
6017
+ let right;
6018
+ if (top + contentRect.height > viewportHeight - padding) {
6019
+ const topPosition = triggerRect.top - contentRect.height;
6020
+ if (topPosition >= padding) {
6021
+ top = topPosition;
6022
+ }
6023
+ }
6024
+ if (align === "start") {
6025
+ left = triggerRect.left;
6026
+ if (left + contentRect.width > viewportWidth - padding) {
6027
+ left = void 0;
6028
+ right = viewportWidth - triggerRect.right;
6029
+ }
6030
+ } else {
6031
+ right = viewportWidth - triggerRect.right;
6032
+ if (triggerRect.right - contentRect.width < padding) {
6033
+ right = void 0;
6034
+ left = triggerRect.left;
6035
+ }
6036
+ }
6037
+ setPosition({ top, left, right });
6038
+ };
6039
+ updatePosition();
6040
+ window.addEventListener("resize", updatePosition);
6041
+ window.addEventListener("scroll", updatePosition, true);
6042
+ return () => {
6043
+ window.removeEventListener("resize", updatePosition);
6044
+ window.removeEventListener("scroll", updatePosition, true);
6045
+ };
6046
+ }, [open, align, triggerRef]);
6047
+ useEffect(() => {
6048
+ if (!open) return;
6049
+ const handlePointerDown = (e) => {
6050
+ const target = e.target;
6051
+ if (triggerRef?.current?.contains(target)) {
6052
+ return;
6053
+ }
6054
+ if (contentRef.current?.contains(target)) {
6055
+ return;
6056
+ }
6057
+ onPointerDownOutside?.(e);
6058
+ onInteractOutside?.(e);
6059
+ setOpen(false);
6060
+ };
6061
+ document.addEventListener("pointerdown", handlePointerDown);
6062
+ return () => document.removeEventListener("pointerdown", handlePointerDown);
6063
+ }, [open, setOpen, onPointerDownOutside, onInteractOutside, triggerRef]);
6064
+ const contentContextValue = useMemo(
6065
+ () => ({
6066
+ registerItem,
6067
+ unregisterItem,
6068
+ getItems,
6069
+ isItemDisabled,
6070
+ active,
6071
+ setActive
6072
+ }),
6073
+ [registerItem, unregisterItem, getItems, isItemDisabled, active, setActive]
6074
+ );
6075
+ const isPositioned = position !== null;
6076
+ return /* @__PURE__ */ u(MenubarContentContext.Provider, { value: contentContextValue, children: /* @__PURE__ */ u(
6077
+ "div",
6078
+ {
6079
+ ref: composedRefs,
6080
+ id: contentId,
6081
+ role: "menu",
6082
+ "aria-orientation": "vertical",
6083
+ "aria-labelledby": menuId,
6084
+ tabIndex: -1,
6085
+ "data-state": open ? "open" : "closed",
6086
+ "data-align": align,
6087
+ style: {
6088
+ position: "fixed",
6089
+ top: position ? `${position.top}px` : "0",
6090
+ left: position?.left !== void 0 ? `${position.left}px` : void 0,
6091
+ right: position?.right !== void 0 ? `${position.right}px` : void 0,
6092
+ zIndex: 9999,
6093
+ visibility: isPositioned ? "visible" : "hidden"
6094
+ },
6095
+ className: cn(
6096
+ "flex flex-col items-start w-fit py-2.5 mt-2.5 overflow-hidden",
6097
+ "rounded-sm border border-bdr-subtle bg-surface-neutral shadow-lg",
6098
+ // Animations
6099
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
6100
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
6101
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
6102
+ className
6103
+ ),
6104
+ onKeyDown: handleKeyDown_,
6105
+ ...props,
6106
+ children
6107
+ }
6108
+ ) });
6109
+ }
6110
+ );
6111
+ MenubarContent.displayName = "Menubar.Content";
6112
+ const menubarItemVariants = cva(
6113
+ "flex w-full items-center px-4.5 py-2.5 gap-x-1.25 cursor-pointer text-sm outline-none",
6114
+ {
6115
+ variants: {
6116
+ active: {
6117
+ true: "bg-surface-primary-selected text-alt",
6118
+ false: ""
6119
+ },
6120
+ disabled: {
6121
+ true: "hover:bg-transparent opacity-30 select-none pointer-events-none",
6122
+ false: ""
6123
+ }
6124
+ },
6125
+ compoundVariants: [
6126
+ {
6127
+ active: true,
6128
+ disabled: false,
6129
+ class: "focus-visible:ring-3 focus-visible:ring-inset focus-visible:ring-bdr-strong"
6130
+ },
6131
+ {
6132
+ active: false,
6133
+ disabled: false,
6134
+ class: "hover:bg-surface-primary-hover"
6135
+ }
6136
+ ],
6137
+ defaultVariants: {
6138
+ active: false,
6139
+ disabled: false
6140
+ }
6141
+ }
6142
+ );
6143
+ const MenubarItem = forwardRef(
6144
+ ({
6145
+ id: providedId,
6146
+ disabled = false,
6147
+ onSelect,
6148
+ asChild,
6149
+ className,
6150
+ children,
6151
+ onClick,
6152
+ onPointerMove,
6153
+ onPointerLeave,
6154
+ onFocus,
6155
+ ...props
6156
+ }, ref) => {
6157
+ const id = usePrefixedId(providedId, "menubar-item");
6158
+ const { registerItem, unregisterItem, active, setActive, getItems, isItemDisabled } = useMenubarContent();
6159
+ const { setOpen, triggerRef } = useMenubarMenu();
6160
+ const isActive = active === id;
6161
+ const itemRef = useRef(null);
6162
+ const composedRefs = useComposedRefs(ref, itemRef);
6163
+ useEffect(() => {
6164
+ registerItem(id, disabled);
6165
+ return () => unregisterItem(id);
6166
+ }, [id, disabled, registerItem, unregisterItem]);
6167
+ const handleClick = useCallback(
6168
+ (e) => {
6169
+ onClick?.(e);
6170
+ if (disabled) return;
6171
+ const event = new Event("select", { bubbles: true, cancelable: true });
6172
+ onSelect?.(event);
6173
+ setOpen(false);
6174
+ triggerRef?.current?.focus();
6175
+ },
6176
+ [disabled, onClick, onSelect, setOpen, triggerRef]
6177
+ );
6178
+ const handlePointerMove = useCallback(
6179
+ (e) => {
6180
+ onPointerMove?.(e);
6181
+ if (!disabled) {
6182
+ setActive(id);
6183
+ }
6184
+ },
6185
+ [disabled, id, setActive, onPointerMove]
6186
+ );
6187
+ const handlePointerLeave = useCallback(
6188
+ (e) => {
6189
+ onPointerLeave?.(e);
6190
+ if (document.activeElement !== itemRef.current) {
6191
+ setActive(void 0);
6192
+ }
6193
+ },
6194
+ [setActive, onPointerLeave, itemRef]
6195
+ );
6196
+ const handleFocus = useCallback(
6197
+ (e) => {
6198
+ onFocus?.(e);
6199
+ if (disabled) return;
6200
+ setActive(id);
6201
+ },
6202
+ [onFocus, disabled, setActive, id]
6203
+ );
6204
+ const items = getItems();
6205
+ const firstItem = items.length > 0 ? items[0] : void 0;
6206
+ const fallbackFocusableId = active ?? items.find((itemId) => !isItemDisabled(itemId)) ?? firstItem ?? id;
6207
+ const isFocusable = !disabled && fallbackFocusableId === id;
6208
+ useEffect(() => {
6209
+ if (!disabled && isActive && document.activeElement !== itemRef.current) {
6210
+ itemRef.current?.focus();
6211
+ }
6212
+ }, [isActive, disabled]);
6213
+ const Comp = asChild ? Slot : "div";
6214
+ return /* @__PURE__ */ u(
6215
+ Comp,
6216
+ {
6217
+ ref: composedRefs,
6218
+ id,
6219
+ role: "menuitem",
6220
+ tabIndex: disabled ? -1 : isFocusable ? 0 : -1,
6221
+ "aria-disabled": disabled,
6222
+ "data-active": isActive || void 0,
6223
+ "data-disabled": disabled || void 0,
6224
+ "data-tone": isActive ? "inverse" : void 0,
6225
+ className: cn(menubarItemVariants({ active: isActive, disabled }), className),
6226
+ onClick: handleClick,
6227
+ onPointerMove: handlePointerMove,
6228
+ onPointerLeave: handlePointerLeave,
6229
+ onFocus: handleFocus,
6230
+ ...props,
6231
+ children
6232
+ }
6233
+ );
6234
+ }
6235
+ );
6236
+ MenubarItem.displayName = "Menubar.Item";
6237
+ const MenubarLabel = forwardRef(
6238
+ ({ className, children, ...props }, ref) => {
6239
+ return /* @__PURE__ */ u("div", { ref, role: "none", className: cn("px-3 py-1.5 text-xs font-semibold text-subtle", className), ...props, children });
6240
+ }
6241
+ );
6242
+ MenubarLabel.displayName = "Menubar.Label";
6243
+ const Menubar = Object.assign(MenubarRoot, {
6244
+ Root: MenubarRoot,
6245
+ Nav: MenubarNav,
6246
+ Content: MenubarContent,
6247
+ // Dropdown menu content (context-aware via MenubarMenu)
6248
+ Button: MenubarButton,
6249
+ Separator: MenubarSeparator,
6250
+ Menu: MenubarMenu,
6251
+ Trigger: MenubarTrigger,
6252
+ Portal: MenubarPortal,
6253
+ Item: MenubarItem,
6254
+ Label: MenubarLabel
6255
+ });
5368
6256
  const SearchFieldRoot = ({
5369
6257
  id,
5370
6258
  value,
@@ -5401,7 +6289,7 @@ const SearchFieldRoot = ({
5401
6289
  "relative flex gap-2.5 items-center rounded-sm overflow-hidden",
5402
6290
  "h-11.5 px-4.5 py-3",
5403
6291
  "border border-bdr-subtle focus-within:border-bdr-strong",
5404
- "focus-within:outline-none focus-within:ring-3 focus-within:ring-ring/50 focus-within:ring-offset-0",
6292
+ "focus-within:outline-none focus-within:ring-3 focus-within:ring-bdr-strong focus-within:ring-offset-3 focus-within:ring-offset-surface-neutral",
5405
6293
  "transition-highlight",
5406
6294
  readOnly ? "bg-surface-primary" : "bg-surface-neutral",
5407
6295
  disabled && "pointer-events-none select-none opacity-30",
@@ -5777,6 +6665,10 @@ export {
5777
6665
  ListboxProvider,
5778
6666
  Menu,
5779
6667
  MenuProvider,
6668
+ Menubar,
6669
+ MenubarMenuContext,
6670
+ MenubarMenuProvider,
6671
+ MenubarProvider,
5780
6672
  SearchField,
5781
6673
  SearchFieldProvider,
5782
6674
  SelectableListItem,
@@ -5793,11 +6685,16 @@ export {
5793
6685
  useCombobox,
5794
6686
  useComposedRefs,
5795
6687
  useControlledState,
6688
+ useControlledStateWithNull,
5796
6689
  useDialog,
5797
6690
  useItemRegistry,
5798
6691
  useKeyboardNavigation,
5799
6692
  useListbox,
5800
6693
  useMenu,
6694
+ useMenubar,
6695
+ useMenubarMenu,
6696
+ useMenubarMenuOptional,
6697
+ useMenubarOptional,
5801
6698
  usePrefixedId,
5802
6699
  useScrollLock,
5803
6700
  useSearchField