@enonic/ui 0.12.0 → 0.13.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.
@@ -1,6 +1,8 @@
1
1
  import { options, Fragment } from "preact";
2
- import { forwardRef, createContext, useId, useContext, createElement, useState, isValidElement, Children, useRef, useEffect, useCallback, createPortal, useMemo } from "react";
3
- import { Root } from "@radix-ui/react-slot";
2
+ import { createContext, useContext, useId, useCallback, forwardRef, useState, useMemo, useEffect, createElement, useRef, isValidElement, Children, createPortal as createPortal$1 } from "react";
3
+ import { Slot, Root } from "@radix-ui/react-slot";
4
+ import { FocusTrap } from "focus-trap-react";
5
+ import { createPortal } from "react-dom";
4
6
  import { useState as useState$1 } from "preact/hooks";
5
7
  var f = 0;
6
8
  function u(e, t, n, o, i, u2) {
@@ -11,6 +13,75 @@ function u(e, t, n, o, i, u2) {
11
13
  if ("function" == typeof e && (a = e.defaultProps)) for (c in a) void 0 === p[c] && (p[c] = a[c]);
12
14
  return options.vnode && options.vnode(l), l;
13
15
  }
16
+ const AvatarContext = createContext(null);
17
+ const AvatarProvider = ({ value, children }) => {
18
+ return /* @__PURE__ */ u(AvatarContext.Provider, { value, children });
19
+ };
20
+ AvatarProvider.displayName = "AvatarProvider";
21
+ const useAvatar = () => {
22
+ const ctx = useContext(AvatarContext);
23
+ if (!ctx) {
24
+ throw new Error("useAvatar must be used within an Avatar");
25
+ }
26
+ return ctx;
27
+ };
28
+ const ComboboxContext = createContext(null);
29
+ const ComboboxProvider = ({ value, children }) => {
30
+ return /* @__PURE__ */ u(ComboboxContext.Provider, { value, children });
31
+ };
32
+ ComboboxProvider.displayName = "ComboboxProvider";
33
+ const useCombobox = () => {
34
+ const ctx = useContext(ComboboxContext);
35
+ if (!ctx) {
36
+ throw new Error("useCombobox must be used within a ComboboxProvider");
37
+ }
38
+ return ctx;
39
+ };
40
+ const DialogContext = createContext(void 0);
41
+ const DialogProvider = ({ value, children }) => {
42
+ return /* @__PURE__ */ u(DialogContext.Provider, { value, children });
43
+ };
44
+ DialogProvider.displayName = "DialogProvider";
45
+ const useDialog = () => {
46
+ const context = useContext(DialogContext);
47
+ if (!context) {
48
+ throw new Error("useDialog must be used within a DialogProvider");
49
+ }
50
+ return context;
51
+ };
52
+ const IdContext = createContext(void 0);
53
+ const IdProvider = ({ children, prefix }) => {
54
+ return /* @__PURE__ */ u(IdContext.Provider, { value: { prefix }, children });
55
+ };
56
+ IdProvider.displayName = "IdProvider";
57
+ const usePrefixedId = (providedId, prefix) => {
58
+ const baseId = providedId ?? useId();
59
+ const context = useContext(IdContext);
60
+ return [context?.prefix, prefix, baseId].filter(Boolean).join("-");
61
+ };
62
+ const ListboxContext = createContext(null);
63
+ const ListboxProvider = ({ value, children }) => {
64
+ return /* @__PURE__ */ u(ListboxContext.Provider, { value, children });
65
+ };
66
+ ListboxProvider.displayName = "ListboxProvider";
67
+ const useListbox = () => {
68
+ const ctx = useContext(ListboxContext);
69
+ if (!ctx) {
70
+ throw new Error("useListbox must be used within a Listbox");
71
+ }
72
+ return ctx;
73
+ };
74
+ const MenuContext = createContext(void 0);
75
+ const MenuProvider = ({ value, children }) => {
76
+ return /* @__PURE__ */ u(MenuContext.Provider, { value, children });
77
+ };
78
+ const useMenu = () => {
79
+ const context = useContext(MenuContext);
80
+ if (!context) {
81
+ throw new Error("useMenu must be used within a MenuProvider");
82
+ }
83
+ return context;
84
+ };
14
85
  function r(e) {
15
86
  var t, f2, n = "";
16
87
  if ("string" == typeof e || "number" == typeof e) n += e;
@@ -2978,6 +3049,22 @@ const twMerge = /* @__PURE__ */ createTailwindMerge(getDefaultConfig);
2978
3049
  function cn(...inputs) {
2979
3050
  return twMerge(clsx(inputs));
2980
3051
  }
3052
+ function setRef(ref, value) {
3053
+ if (!ref) return;
3054
+ if (typeof ref === "function") {
3055
+ ref(value);
3056
+ } else {
3057
+ try {
3058
+ ref.current = value;
3059
+ } catch {
3060
+ }
3061
+ }
3062
+ }
3063
+ function useComposedRefs(...refs) {
3064
+ return useCallback((node) => {
3065
+ refs.forEach((ref) => setRef(ref, node));
3066
+ }, refs);
3067
+ }
2981
3068
  function isSignal(v) {
2982
3069
  return !!(v && typeof v === "object" && "value" in v && "peek" in v && typeof v.peek === "function" && "subscribe" in v && typeof v.subscribe === "function");
2983
3070
  }
@@ -3024,6 +3111,125 @@ const cva = (base, config) => (props) => {
3024
3111
  }, []);
3025
3112
  return cx(base, getVariantClassNames, getCompoundVariantClassNames, props === null || props === void 0 ? void 0 : props.class, props === null || props === void 0 ? void 0 : props.className);
3026
3113
  };
3114
+ const avatarVariants = cva("relative flex shrink-0 overflow-hidden", {
3115
+ variants: {
3116
+ size: {
3117
+ sm: "w-6 h-6 text-xs",
3118
+ md: "w-8 h-8 text-sm",
3119
+ lg: "w-12 h-12 text-base"
3120
+ },
3121
+ shape: {
3122
+ circle: "rounded-full",
3123
+ square: "rounded-md"
3124
+ }
3125
+ },
3126
+ defaultVariants: {
3127
+ size: "md",
3128
+ shape: "circle"
3129
+ }
3130
+ });
3131
+ const AvatarRoot = forwardRef(
3132
+ ({ children, className, size, shape, ...props }, ref) => {
3133
+ const [imageLoadingStatus, setImageLoadingStatus] = useState("idle");
3134
+ const handleImageLoadingStatusChange = useCallback((status) => {
3135
+ setImageLoadingStatus(status);
3136
+ }, []);
3137
+ const contextValue = useMemo(
3138
+ () => ({
3139
+ imageLoadingStatus,
3140
+ onImageLoadingStatusChange: handleImageLoadingStatusChange
3141
+ }),
3142
+ [imageLoadingStatus, handleImageLoadingStatusChange]
3143
+ );
3144
+ return /* @__PURE__ */ u(AvatarProvider, { value: contextValue, children: /* @__PURE__ */ u("span", { ref, className: cn(avatarVariants({ size, shape }), className), ...props, children }) });
3145
+ }
3146
+ );
3147
+ AvatarRoot.displayName = "AvatarRoot";
3148
+ const AvatarImage = forwardRef(
3149
+ ({ src, alt, className, onLoadingStatusChange, ...props }, ref) => {
3150
+ const { imageLoadingStatus, onImageLoadingStatusChange } = useAvatar();
3151
+ const [hasError, setHasError] = useState(false);
3152
+ useEffect(() => {
3153
+ setHasError(false);
3154
+ if (!src) {
3155
+ return;
3156
+ }
3157
+ onImageLoadingStatusChange("loading");
3158
+ onLoadingStatusChange?.("loading");
3159
+ const img = new Image();
3160
+ const handleLoad = () => {
3161
+ onImageLoadingStatusChange("loaded");
3162
+ onLoadingStatusChange?.("loaded");
3163
+ };
3164
+ const handleError = () => {
3165
+ setHasError(true);
3166
+ onImageLoadingStatusChange("error");
3167
+ onLoadingStatusChange?.("error");
3168
+ };
3169
+ img.addEventListener("load", handleLoad);
3170
+ img.addEventListener("error", handleError);
3171
+ img.src = src;
3172
+ return () => {
3173
+ img.removeEventListener("load", handleLoad);
3174
+ img.removeEventListener("error", handleError);
3175
+ };
3176
+ }, [src, onImageLoadingStatusChange, onLoadingStatusChange]);
3177
+ if (!src || hasError || imageLoadingStatus === "error") {
3178
+ return null;
3179
+ }
3180
+ return /* @__PURE__ */ u(
3181
+ "img",
3182
+ {
3183
+ ref,
3184
+ src,
3185
+ alt,
3186
+ className: cn("aspect-square h-full w-full object-cover", className),
3187
+ ...props
3188
+ }
3189
+ );
3190
+ }
3191
+ );
3192
+ AvatarImage.displayName = "AvatarImage";
3193
+ const AvatarFallback = forwardRef(
3194
+ ({ children, className, delayMs = 0, ...props }, ref) => {
3195
+ const { imageLoadingStatus } = useAvatar();
3196
+ const [canRender, setCanRender] = useState(delayMs === 0);
3197
+ useEffect(() => {
3198
+ if (delayMs === 0) {
3199
+ return;
3200
+ }
3201
+ const timerId = setTimeout(() => {
3202
+ setCanRender(true);
3203
+ }, delayMs);
3204
+ return () => {
3205
+ clearTimeout(timerId);
3206
+ };
3207
+ }, [delayMs]);
3208
+ const shouldRender = canRender && (imageLoadingStatus === "idle" || imageLoadingStatus === "error");
3209
+ if (!shouldRender) {
3210
+ return null;
3211
+ }
3212
+ return /* @__PURE__ */ u(
3213
+ "span",
3214
+ {
3215
+ ref,
3216
+ className: cn(
3217
+ "flex h-full w-full items-center justify-center",
3218
+ "bg-surface-secondary text-alt font-medium uppercase cursor-default",
3219
+ className
3220
+ ),
3221
+ ...props,
3222
+ children
3223
+ }
3224
+ );
3225
+ }
3226
+ );
3227
+ AvatarFallback.displayName = "AvatarFallback";
3228
+ const Avatar = Object.assign(AvatarRoot, {
3229
+ Root: AvatarRoot,
3230
+ Image: AvatarImage,
3231
+ Fallback: AvatarFallback
3232
+ });
3027
3233
  const buttonVariants = cva(
3028
3234
  [
3029
3235
  "inline-flex items-center justify-center",
@@ -3036,10 +3242,10 @@ const buttonVariants = cva(
3036
3242
  {
3037
3243
  variants: {
3038
3244
  variant: {
3039
- text: "bg-btn-primary hover:bg-btn-primary-hover active:bg-btn-active active:text-rev dark:active:text-main",
3040
- filled: "bg-btn-secondary hover:bg-btn-secondary-hover active:bg-btn-active active:text-rev dark:active:text-main",
3041
- solid: "bg-btn-tertiary text-alt hover:bg-btn-tertiary-hover active:bg-btn-active dark:active:text-main",
3042
- outline: "bg-btn-primary hover:bg-btn-primary-hover active:bg-btn-active active:text-rev dark:active:text-main border border-bdr-strong"
3245
+ text: "bg-btn-primary hover:bg-btn-primary-hover active:bg-btn-active active:text-alt",
3246
+ filled: "bg-btn-secondary hover:bg-btn-secondary-hover active:bg-btn-active active:text-alt",
3247
+ solid: "bg-btn-tertiary text-alt hover:bg-btn-tertiary-hover active:bg-btn-active",
3248
+ outline: "bg-btn-primary hover:bg-btn-primary-hover active:bg-btn-active active:text-alt border border-bdr-strong"
3043
3249
  },
3044
3250
  size: {
3045
3251
  sm: "h-9 px-3.5 gap-2 text-sm",
@@ -3101,20 +3307,6 @@ const Button = forwardRef(
3101
3307
  }
3102
3308
  );
3103
3309
  Button.displayName = "Button";
3104
- const IdContext = createContext(void 0);
3105
- const IdProvider = ({ children, prefix }) => {
3106
- return /* @__PURE__ */ u(IdContext.Provider, { value: { prefix }, children });
3107
- };
3108
- IdProvider.displayName = "IdProvider";
3109
- const usePrefixedId = (providedId) => {
3110
- const baseId = useId();
3111
- const context = useContext(IdContext);
3112
- if (providedId) {
3113
- return providedId;
3114
- }
3115
- const id = context?.prefix ? `${context.prefix}-${baseId}` : baseId;
3116
- return id;
3117
- };
3118
3310
  /**
3119
3311
  * @license lucide-react v0.545.0 - ISC
3120
3312
  *
@@ -3220,19 +3412,27 @@ const createLucideIcon = (iconName, iconNode) => {
3220
3412
  * This source code is licensed under the ISC license.
3221
3413
  * See the LICENSE file in the root directory of this source tree.
3222
3414
  */
3223
- const __iconNode$7 = [
3415
+ const __iconNode$8 = [
3224
3416
  ["path", { d: "M5 12h14", key: "1ays0h" }],
3225
3417
  ["path", { d: "m12 5 7 7-7 7", key: "xquz4c" }]
3226
3418
  ];
3227
- const ArrowRight = createLucideIcon("arrow-right", __iconNode$7);
3419
+ const ArrowRight = createLucideIcon("arrow-right", __iconNode$8);
3420
+ /**
3421
+ * @license lucide-react v0.545.0 - ISC
3422
+ *
3423
+ * This source code is licensed under the ISC license.
3424
+ * See the LICENSE file in the root directory of this source tree.
3425
+ */
3426
+ const __iconNode$7 = [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]];
3427
+ const Check = createLucideIcon("check", __iconNode$7);
3228
3428
  /**
3229
3429
  * @license lucide-react v0.545.0 - ISC
3230
3430
  *
3231
3431
  * This source code is licensed under the ISC license.
3232
3432
  * See the LICENSE file in the root directory of this source tree.
3233
3433
  */
3234
- const __iconNode$6 = [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]];
3235
- const Check = createLucideIcon("check", __iconNode$6);
3434
+ const __iconNode$6 = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
3435
+ const ChevronDown = createLucideIcon("chevron-down", __iconNode$6);
3236
3436
  /**
3237
3437
  * @license lucide-react v0.545.0 - ISC
3238
3438
  *
@@ -3487,96 +3687,746 @@ const IconButton = forwardRef(
3487
3687
  }
3488
3688
  );
3489
3689
  IconButton.displayName = "IconButton";
3490
- const inputContainerVariants = cva(
3690
+ const ComboboxRoot = ({
3691
+ children,
3692
+ open: controlledOpen,
3693
+ onOpenChange,
3694
+ closeOnBlur = true,
3695
+ value,
3696
+ onChange,
3697
+ disabled = false,
3698
+ error = false,
3699
+ selectionMode = "single",
3700
+ selection: controlledSelection,
3701
+ onSelectionChange
3702
+ }) => {
3703
+ const baseId = usePrefixedId();
3704
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
3705
+ const isOpenControlled = controlledOpen !== void 0;
3706
+ const open = isOpenControlled ? controlledOpen : uncontrolledOpen;
3707
+ const setOpen = useCallback(
3708
+ (next) => {
3709
+ if (!isOpenControlled) {
3710
+ setUncontrolledOpen(next);
3711
+ }
3712
+ onOpenChange?.(next);
3713
+ },
3714
+ [isOpenControlled, onOpenChange]
3715
+ );
3716
+ const [uncontrolledSelection, setUncontrolledSelection] = useState([]);
3717
+ const isSelectionControlled = controlledSelection !== void 0;
3718
+ const selectedItems = isSelectionControlled ? controlledSelection : uncontrolledSelection;
3719
+ const onSelectionChangeInner = useCallback(
3720
+ (newSelection) => {
3721
+ if (!isSelectionControlled) {
3722
+ setUncontrolledSelection(newSelection);
3723
+ }
3724
+ onSelectionChange?.(newSelection);
3725
+ if (selectionMode === "single") {
3726
+ setOpen(false);
3727
+ }
3728
+ },
3729
+ [isSelectionControlled, selectionMode, setOpen, onSelectionChange, selectedItems]
3730
+ );
3731
+ const [uncontrolledInput, setUncontrolledInput] = useState("");
3732
+ const isInputControlled = value !== void 0;
3733
+ const inputValue = isInputControlled ? value : uncontrolledInput;
3734
+ const setInputValue = useCallback(
3735
+ (next) => {
3736
+ if (!isInputControlled) {
3737
+ setUncontrolledInput(next);
3738
+ }
3739
+ onChange?.(next);
3740
+ setOpen(true);
3741
+ },
3742
+ [isInputControlled, onChange, setOpen]
3743
+ );
3744
+ const toggleValueSelection = useCallback(
3745
+ (value2) => {
3746
+ let newSelection = [];
3747
+ if (selectionMode === "multiple") {
3748
+ if (selectedItems.includes(value2)) {
3749
+ newSelection = selectedItems.filter((item) => item !== value2);
3750
+ } else {
3751
+ newSelection = [...selectedItems, value2];
3752
+ }
3753
+ } else {
3754
+ newSelection = [value2];
3755
+ }
3756
+ onSelectionChangeInner(newSelection);
3757
+ },
3758
+ [selectionMode, selectedItems, onSelectionChangeInner]
3759
+ );
3760
+ const [active, setActive] = useState(void 0);
3761
+ const innerRef = useRef(null);
3762
+ const getItems = useCallback(() => {
3763
+ const container = innerRef.current;
3764
+ if (!container) {
3765
+ return [];
3766
+ }
3767
+ const optionNodes = container.querySelectorAll('[role="option"][data-value]');
3768
+ return Array.from(optionNodes).map((node) => node.dataset.value).filter((v) => v !== void 0);
3769
+ }, []);
3770
+ const moveActive = useCallback(
3771
+ (delta) => {
3772
+ const items = getItems();
3773
+ if (!items.length) {
3774
+ return;
3775
+ }
3776
+ const currentIndex = active ? items.indexOf(active) : -1;
3777
+ const newIndex = Math.max(0, Math.min(items.length - 1, currentIndex + delta));
3778
+ setActive(items[newIndex]);
3779
+ },
3780
+ [active, setActive, getItems]
3781
+ );
3782
+ const keyHandler = useCallback(
3783
+ (e) => {
3784
+ if (disabled) {
3785
+ return;
3786
+ }
3787
+ const items = getItems();
3788
+ if (e.key === "ArrowDown") {
3789
+ e.preventDefault();
3790
+ setOpen(true);
3791
+ moveActive(1);
3792
+ } else if (e.key === "ArrowUp") {
3793
+ setOpen(true);
3794
+ moveActive(-1);
3795
+ } else if (e.key === "Home") {
3796
+ e.preventDefault();
3797
+ setActive(items[0]);
3798
+ } else if (e.key === "End") {
3799
+ e.preventDefault();
3800
+ setActive(items[items.length - 1]);
3801
+ } else if (e.key === "Enter") {
3802
+ if (open) {
3803
+ e.preventDefault();
3804
+ if (active) {
3805
+ toggleValueSelection(active);
3806
+ }
3807
+ setOpen(false);
3808
+ setActive(items[0]);
3809
+ }
3810
+ } else if (e.key === "Escape") {
3811
+ setOpen(false);
3812
+ setActive(items[0]);
3813
+ }
3814
+ },
3815
+ [disabled, moveActive, setOpen, toggleValueSelection, open]
3816
+ );
3817
+ useEffect(() => {
3818
+ if (open && active === void 0) {
3819
+ const items = getItems();
3820
+ if (items.length > 0) {
3821
+ setActive(items[0]);
3822
+ }
3823
+ }
3824
+ }, [open, active, getItems]);
3825
+ const context = useMemo(
3826
+ () => ({
3827
+ open,
3828
+ setOpen,
3829
+ inputValue,
3830
+ setInputValue,
3831
+ baseId,
3832
+ active,
3833
+ disabled,
3834
+ error,
3835
+ closeOnBlur,
3836
+ keyHandler,
3837
+ selection: selectedItems
3838
+ }),
3839
+ [open, setOpen, closeOnBlur, keyHandler, inputValue, setInputValue, active, baseId, disabled, error, selectedItems]
3840
+ );
3841
+ return /* @__PURE__ */ u("div", { ref: innerRef, children: /* @__PURE__ */ u(ComboboxProvider, { value: context, children: /* @__PURE__ */ u(
3842
+ Listbox.Root,
3843
+ {
3844
+ selectionMode,
3845
+ selection: selectedItems,
3846
+ onSelectionChange: onSelectionChangeInner,
3847
+ disabled,
3848
+ active,
3849
+ focusable: false,
3850
+ baseId,
3851
+ setActive,
3852
+ keyHandler,
3853
+ children
3854
+ }
3855
+ ) }) });
3856
+ };
3857
+ ComboboxRoot.displayName = "Combobox.Root";
3858
+ const ComboboxContent = forwardRef(
3859
+ ({ className, children }, ref) => {
3860
+ const { setOpen, baseId, closeOnBlur } = useCombobox();
3861
+ const innerRef = useRef(null);
3862
+ const handleFocusOut = closeOnBlur ? useCallback(
3863
+ (e) => {
3864
+ const nextTarget = e.relatedTarget;
3865
+ if (nextTarget && innerRef.current?.contains(nextTarget)) {
3866
+ return;
3867
+ }
3868
+ setOpen(false);
3869
+ },
3870
+ [baseId, setOpen]
3871
+ ) : void 0;
3872
+ return (
3873
+ // eslint-disable-next-line react/no-unknown-property
3874
+ /* @__PURE__ */ u("div", { onFocusOut: handleFocusOut, ref: useComposedRefs(ref, innerRef), className, children })
3875
+ );
3876
+ }
3877
+ );
3878
+ const comboboxControlVariants = cva(
3491
3879
  [
3492
- "relative flex rounded-sm overflow-hidden",
3493
- "h-12 border focus-within:border-bdr-solid",
3880
+ "flex items-center",
3881
+ "h-12 rounded-sm border bg-surface-neutral",
3494
3882
  "focus-within:outline-none focus-within:ring-3 focus-within:ring-ring/50 focus-within:ring-offset-0",
3495
3883
  "transition-highlight"
3496
3884
  ],
3497
3885
  {
3498
3886
  variants: {
3499
- state: {
3500
- default: "border-bdr-subtle focus-within:border-bdr-strong",
3501
- error: "border-error focus-within:border-error focus-within:ring-error/50"
3887
+ error: {
3888
+ true: "border-error focus-within:border-error focus-within:ring-error/50",
3889
+ false: "border-bdr-subtle focus-within:border-bdr-strong"
3890
+ },
3891
+ open: {
3892
+ true: "border-bdr-strong",
3893
+ false: null
3502
3894
  },
3503
3895
  disabled: {
3504
- true: "select-none"
3896
+ true: "pointer-events-none select-none opacity-30",
3897
+ false: null
3505
3898
  }
3506
3899
  },
3507
3900
  defaultVariants: {
3508
- state: "default",
3901
+ error: false,
3902
+ open: false,
3509
3903
  disabled: false
3510
3904
  }
3511
3905
  }
3512
3906
  );
3513
- const Addon = ({ children, error }) => /* @__PURE__ */ u(
3514
- "div",
3515
- {
3516
- className: cn(
3517
- "flex items-center justify-center shrink-0 min-w-12 px-4",
3518
- "text-sm text-subtle bg-surface-primary",
3519
- "first:rounded-l-sm first:border-r first:border-bdr-subtle",
3520
- "last:rounded-r-sm last:border-l last:border-bdr-subtle",
3521
- error && "first:border-error last:border-error"
3522
- ),
3523
- children
3907
+ const ComboboxControl = ({ children, className }) => {
3908
+ const { open, disabled, error } = useCombobox();
3909
+ return /* @__PURE__ */ u(
3910
+ "div",
3911
+ {
3912
+ className: cn(
3913
+ comboboxControlVariants({
3914
+ error,
3915
+ open,
3916
+ disabled
3917
+ }),
3918
+ className
3919
+ ),
3920
+ "data-open": open ? "true" : void 0,
3921
+ children
3922
+ }
3923
+ );
3924
+ };
3925
+ ComboboxControl.displayName = "Combobox.Control";
3926
+ const ComboboxInput = forwardRef(
3927
+ ({ className, placeholder, ...props }, ref) => {
3928
+ const { inputValue, setInputValue, open, keyHandler, selection, baseId, active, disabled } = useCombobox();
3929
+ const innerRef = useRef(null);
3930
+ useEffect(() => {
3931
+ if (open && !disabled) {
3932
+ innerRef.current?.focus();
3933
+ }
3934
+ }, [open, selection, disabled]);
3935
+ return /* @__PURE__ */ u(
3936
+ SearchInput,
3937
+ {
3938
+ ref: useComposedRefs(ref, innerRef),
3939
+ id: `${baseId}-input`,
3940
+ className: "border-none focus:outline-none focus-within:outline-none h-auto focus-within:ring-0 grow",
3941
+ value: inputValue,
3942
+ onChange: setInputValue,
3943
+ onKeyDown: keyHandler,
3944
+ placeholder,
3945
+ disabled,
3946
+ "aria-disabled": disabled,
3947
+ role: "combobox",
3948
+ "aria-autocomplete": "list",
3949
+ "aria-expanded": open,
3950
+ "aria-haspopup": "listbox",
3951
+ "aria-controls": `${baseId}-listbox`,
3952
+ "aria-activedescendant": active ? `${baseId}-listbox-option-${active}` : void 0,
3953
+ showClearButton: false,
3954
+ ...props
3955
+ }
3956
+ );
3524
3957
  }
3525
3958
  );
3526
- const Input = forwardRef(
3527
- ({ className, label, description, error, startAddon, endAddon, id, disabled, readOnly, ...props }, ref) => {
3528
- const inputId = usePrefixedId(unwrap(id));
3529
- const state = error ? "error" : "default";
3530
- const hasError = state === "error";
3531
- return /* @__PURE__ */ u("div", { className: cn("w-full", disabled && "opacity-30", className), children: [
3532
- /* @__PURE__ */ u("div", { className: "mb-2", children: [
3533
- label && /* @__PURE__ */ u(
3534
- "label",
3535
- {
3536
- htmlFor: inputId,
3537
- className: cn("block text-base font-semibold text-main", disabled && "opacity-30"),
3538
- children: /* @__PURE__ */ u("div", { className: "flex items-center gap-2", children: [
3539
- readOnly && /* @__PURE__ */ u(LockKeyhole, { size: 16, strokeWidth: 2.5 }),
3540
- label
3541
- ] })
3542
- }
3543
- ),
3544
- description && /* @__PURE__ */ u("div", { className: cn("text-sm text-subtle", disabled && "opacity-30"), children: description })
3545
- ] }),
3546
- /* @__PURE__ */ u("div", { className: cn(inputContainerVariants({ state, disabled })), children: [
3547
- startAddon && /* @__PURE__ */ u(Addon, { error: hasError, children: startAddon }),
3548
- /* @__PURE__ */ u(
3549
- "input",
3550
- {
3551
- ref,
3552
- id: inputId,
3553
- className: cn(
3554
- "flex-1 w-full px-4.5 text-base",
3555
- "text-main bg-surface-neutral placeholder:text-subtle",
3556
- "border-0 focus:outline-none",
3557
- "disabled:select-none read-only:bg-surface-primary",
3558
- startAddon && "rounded-l-none",
3559
- endAddon && "rounded-r-none"
3560
- ),
3561
- disabled,
3562
- readOnly,
3563
- ...props
3564
- }
3565
- ),
3566
- endAddon && /* @__PURE__ */ u(Addon, { error: hasError, children: endAddon })
3567
- ] }),
3568
- error && /* @__PURE__ */ u("div", { className: "flex items-center gap-2 mt-2 leading-5 text-error", children: [
3569
- /* @__PURE__ */ u(OctagonAlert, { size: 16, strokeWidth: 2.5 }),
3570
- error
3571
- ] })
3572
- ] });
3959
+ ComboboxInput.displayName = "Combobox.Input";
3960
+ const ComboboxToggle = ({ className }) => {
3961
+ const { open, setOpen, disabled } = useCombobox();
3962
+ return /* @__PURE__ */ u(
3963
+ IconButton,
3964
+ {
3965
+ type: "button",
3966
+ variant: "text",
3967
+ size: "md",
3968
+ icon: ChevronDown,
3969
+ "aria-label": "Toggle",
3970
+ onClick: () => {
3971
+ if (disabled) {
3972
+ return;
3973
+ }
3974
+ setOpen(!open);
3975
+ },
3976
+ disabled,
3977
+ tabIndex: -1,
3978
+ className: cn(
3979
+ "h-10 w-10 text-subtle transition-transform hover:bg-surface-primary-hover mr-1",
3980
+ open && "rotate-180",
3981
+ className
3982
+ )
3983
+ }
3984
+ );
3985
+ };
3986
+ ComboboxToggle.displayName = "Combobox.Toggle";
3987
+ const ComboboxPopup = ({ children, className }) => {
3988
+ const { open } = useCombobox();
3989
+ if (!open) {
3990
+ return null;
3573
3991
  }
3574
- );
3575
- Input.displayName = "Input";
3576
- function isExternalHref(href) {
3577
- try {
3578
- if (href.startsWith("mailto:") || href.startsWith("tel:")) return true;
3579
- const location = typeof window !== "undefined" ? window.location : void 0;
3992
+ return /* @__PURE__ */ u(
3993
+ "div",
3994
+ {
3995
+ className: cn(
3996
+ "absolute left-0 right-0 z-50 mt-1 rounded-sm bg-surface-neutral shadow-lg ring-1 ring-bdr-subtle",
3997
+ className
3998
+ ),
3999
+ children
4000
+ }
4001
+ );
4002
+ };
4003
+ ComboboxPopup.displayName = "Combobox.Popup";
4004
+ const Combobox = Object.assign(ComboboxRoot, {
4005
+ Root: ComboboxRoot,
4006
+ Content: ComboboxContent,
4007
+ Control: ComboboxControl,
4008
+ Input: ComboboxInput,
4009
+ Toggle: ComboboxToggle,
4010
+ Popup: ComboboxPopup
4011
+ });
4012
+ const useScrollLock = (lock, element) => {
4013
+ useEffect(() => {
4014
+ if (!lock) {
4015
+ return;
4016
+ }
4017
+ const target = element ?? document.body;
4018
+ const originalOverflow = target.style.overflow;
4019
+ target.style.overflow = "hidden";
4020
+ return () => {
4021
+ target.style.overflow = originalOverflow;
4022
+ };
4023
+ }, [lock, element]);
4024
+ };
4025
+ const DialogRoot = ({
4026
+ open: controlledOpen,
4027
+ defaultOpen = false,
4028
+ onOpenChange,
4029
+ children
4030
+ }) => {
4031
+ const isControlled = controlledOpen !== void 0;
4032
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
4033
+ const open = isControlled ? controlledOpen : uncontrolledOpen;
4034
+ const setOpen = useCallback(
4035
+ (next) => {
4036
+ if (!isControlled) {
4037
+ setUncontrolledOpen(next);
4038
+ }
4039
+ onOpenChange?.(next);
4040
+ },
4041
+ [isControlled, onOpenChange]
4042
+ );
4043
+ const value = useMemo(() => ({ open, setOpen }), [open, setOpen]);
4044
+ return /* @__PURE__ */ u(DialogProvider, { value, children });
4045
+ };
4046
+ DialogRoot.displayName = "Dialog.Root";
4047
+ const DialogTrigger = forwardRef(
4048
+ ({ asChild, children, onClick, ...props }, ref) => {
4049
+ const { setOpen } = useDialog();
4050
+ const handleClick = (e) => {
4051
+ onClick?.(e);
4052
+ setOpen(true);
4053
+ };
4054
+ const Comp = asChild ? Slot : "button";
4055
+ return /* @__PURE__ */ u(
4056
+ Comp,
4057
+ {
4058
+ ref,
4059
+ type: asChild ? void 0 : "button",
4060
+ onClick: handleClick,
4061
+ ...props,
4062
+ children
4063
+ }
4064
+ );
4065
+ }
4066
+ );
4067
+ DialogTrigger.displayName = "Dialog.Trigger";
4068
+ const DialogPortal = ({ children, container, forceMount }) => {
4069
+ const { open } = useDialog();
4070
+ const [mounted, setMounted] = useState(false);
4071
+ useEffect(() => {
4072
+ setMounted(true);
4073
+ }, []);
4074
+ if (!mounted || !forceMount && !open) {
4075
+ return null;
4076
+ }
4077
+ return createPortal(children, container ?? document.body);
4078
+ };
4079
+ DialogPortal.displayName = "Dialog.Portal";
4080
+ const DialogOverlay = forwardRef(
4081
+ ({ className, forceMount, ...props }, ref) => {
4082
+ const { open } = useDialog();
4083
+ if (!forceMount && !open) {
4084
+ return null;
4085
+ }
4086
+ return /* @__PURE__ */ u(
4087
+ "div",
4088
+ {
4089
+ ref,
4090
+ "data-state": open ? "open" : "closed",
4091
+ className: cn(
4092
+ "fixed inset-0 z-30 bg-overlay backdrop-blur-xs",
4093
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
4094
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
4095
+ className
4096
+ ),
4097
+ ...props
4098
+ }
4099
+ );
4100
+ }
4101
+ );
4102
+ DialogOverlay.displayName = "Dialog.Overlay";
4103
+ const DialogContent = forwardRef(
4104
+ ({
4105
+ children,
4106
+ className,
4107
+ forceMount,
4108
+ onEscapeKeyDown,
4109
+ onPointerDownOutside,
4110
+ onInteractOutside,
4111
+ onOpenAutoFocus,
4112
+ onCloseAutoFocus,
4113
+ ...props
4114
+ }, ref) => {
4115
+ const { open, setOpen } = useDialog();
4116
+ const contentRef = useRef(null);
4117
+ const titleId = useId();
4118
+ const descriptionId = useId();
4119
+ useEffect(() => {
4120
+ if (typeof ref === "function") {
4121
+ ref(contentRef.current);
4122
+ } else if (ref) {
4123
+ ref.current = contentRef.current;
4124
+ }
4125
+ }, [ref]);
4126
+ useScrollLock(open);
4127
+ const handleEscapeKey = useCallback(
4128
+ (e) => {
4129
+ if (e.key === "Escape") {
4130
+ onEscapeKeyDown?.(e);
4131
+ if (!e.defaultPrevented) {
4132
+ setOpen(false);
4133
+ }
4134
+ }
4135
+ },
4136
+ [onEscapeKeyDown, setOpen]
4137
+ );
4138
+ useEffect(() => {
4139
+ if (!open) {
4140
+ return;
4141
+ }
4142
+ document.addEventListener("keydown", handleEscapeKey);
4143
+ return () => document.removeEventListener("keydown", handleEscapeKey);
4144
+ }, [open, handleEscapeKey]);
4145
+ const handlePointerDownOutside = useCallback(
4146
+ (e) => {
4147
+ const target = e.target;
4148
+ if (contentRef.current && !contentRef.current.contains(target)) {
4149
+ onPointerDownOutside?.(e);
4150
+ onInteractOutside?.(e);
4151
+ if (!e.defaultPrevented) {
4152
+ setOpen(false);
4153
+ }
4154
+ }
4155
+ },
4156
+ [onPointerDownOutside, onInteractOutside, setOpen]
4157
+ );
4158
+ useEffect(() => {
4159
+ if (!open) {
4160
+ return;
4161
+ }
4162
+ document.addEventListener("pointerdown", handlePointerDownOutside);
4163
+ return () => document.removeEventListener("pointerdown", handlePointerDownOutside);
4164
+ }, [open, handlePointerDownOutside]);
4165
+ if (!forceMount && !open) {
4166
+ return null;
4167
+ }
4168
+ return /* @__PURE__ */ u(
4169
+ FocusTrap,
4170
+ {
4171
+ active: open,
4172
+ focusTrapOptions: {
4173
+ initialFocus: false,
4174
+ fallbackFocus: () => contentRef.current ?? document.body,
4175
+ escapeDeactivates: false,
4176
+ clickOutsideDeactivates: false,
4177
+ returnFocusOnDeactivate: true,
4178
+ allowOutsideClick: true,
4179
+ preventScroll: false,
4180
+ onActivate: () => {
4181
+ requestAnimationFrame(() => {
4182
+ const event = new Event("openautofocus");
4183
+ onOpenAutoFocus?.(event);
4184
+ if (!event.defaultPrevented && contentRef.current) {
4185
+ contentRef.current.focus();
4186
+ }
4187
+ });
4188
+ },
4189
+ onDeactivate: () => {
4190
+ requestAnimationFrame(() => {
4191
+ const event = new Event("closeautofocus");
4192
+ onCloseAutoFocus?.(event);
4193
+ });
4194
+ }
4195
+ },
4196
+ children: /* @__PURE__ */ u("div", { className: "fixed inset-0 z-40 flex items-center justify-center p-4", children: /* @__PURE__ */ u(
4197
+ "div",
4198
+ {
4199
+ ref: contentRef,
4200
+ role: "dialog",
4201
+ "aria-modal": "true",
4202
+ "aria-labelledby": titleId,
4203
+ "aria-describedby": descriptionId,
4204
+ "data-state": open ? "open" : "closed",
4205
+ tabIndex: -1,
4206
+ className: cn(
4207
+ "relative rounded-lg shadow-xl bg-surface-neutral",
4208
+ "flex flex-col max-w-lg w-full max-h-[90vh] gap-10 p-10",
4209
+ "border border-bdr-subtle outline-none overflow-hidden",
4210
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
4211
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
4212
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
4213
+ className
4214
+ ),
4215
+ ...props,
4216
+ children
4217
+ }
4218
+ ) })
4219
+ }
4220
+ );
4221
+ }
4222
+ );
4223
+ DialogContent.displayName = "Dialog.Content";
4224
+ const DialogHeader = forwardRef(
4225
+ ({ className, children, ...props }, ref) => {
4226
+ return /* @__PURE__ */ u("header", { ref, className: cn("relative grid gap-2.5 self-stretch", className), ...props, children });
4227
+ }
4228
+ );
4229
+ DialogHeader.displayName = "Dialog.Header";
4230
+ const DialogClose = forwardRef(
4231
+ ({ children, asChild, onClick, ...props }, ref) => {
4232
+ const { setOpen } = useDialog();
4233
+ const handleClick = useCallback(
4234
+ (e) => {
4235
+ onClick?.(e);
4236
+ if (!e.defaultPrevented) {
4237
+ setOpen(false);
4238
+ }
4239
+ },
4240
+ [onClick, setOpen]
4241
+ );
4242
+ const Comp = asChild ? Slot : "button";
4243
+ return /* @__PURE__ */ u(
4244
+ Comp,
4245
+ {
4246
+ ref,
4247
+ type: asChild ? void 0 : "button",
4248
+ onClick: handleClick,
4249
+ ...props,
4250
+ children
4251
+ }
4252
+ );
4253
+ }
4254
+ );
4255
+ DialogClose.displayName = "Dialog.Close";
4256
+ const DialogTitle = forwardRef(
4257
+ ({ children, className, asChild, ...props }, ref) => {
4258
+ const Comp = asChild ? Slot : "h2";
4259
+ return /* @__PURE__ */ u(
4260
+ Comp,
4261
+ {
4262
+ ref,
4263
+ className: cn("text-2xl font-semibold", className),
4264
+ ...props,
4265
+ children
4266
+ }
4267
+ );
4268
+ }
4269
+ );
4270
+ DialogTitle.displayName = "Dialog.Title";
4271
+ const DialogDescription = forwardRef(
4272
+ ({ children, className, asChild, ...props }, ref) => {
4273
+ const Comp = asChild ? Slot : "p";
4274
+ return /* @__PURE__ */ u(
4275
+ Comp,
4276
+ {
4277
+ ref,
4278
+ className: cn("", className),
4279
+ ...props,
4280
+ children
4281
+ }
4282
+ );
4283
+ }
4284
+ );
4285
+ DialogDescription.displayName = "Dialog.Description";
4286
+ const DialogBody = forwardRef(
4287
+ ({ className, children, ...props }, ref) => {
4288
+ return /* @__PURE__ */ u("div", { ref, className: cn("min-h-0 flex-1 overflow-y-auto", className), ...props, children });
4289
+ }
4290
+ );
4291
+ DialogBody.displayName = "Dialog.Body";
4292
+ const DialogFooter = forwardRef(
4293
+ ({ className, children, ...props }, ref) => {
4294
+ return /* @__PURE__ */ u("footer", { ref, className: cn("flex justify-end gap-2.5", className), ...props, children });
4295
+ }
4296
+ );
4297
+ DialogFooter.displayName = "Dialog.Footer";
4298
+ const DialogDefaultClose = forwardRef((props, ref) => {
4299
+ return /* @__PURE__ */ u(DialogClose, { asChild: true, ref, ...props, children: /* @__PURE__ */ u(
4300
+ IconButton,
4301
+ {
4302
+ "aria-label": "Close",
4303
+ "data-area": "close",
4304
+ icon: X,
4305
+ size: "lg",
4306
+ iconSize: 36,
4307
+ iconStrokeWidth: 1,
4308
+ shape: "round",
4309
+ variant: "filled"
4310
+ }
4311
+ ) });
4312
+ });
4313
+ DialogDefaultClose.displayName = "Dialog.DefaultClose";
4314
+ const DialogDefaultHeader = forwardRef(
4315
+ ({ title, description, withClose, className, children, ...props }, ref) => {
4316
+ return /* @__PURE__ */ u(DialogHeader, { ref, className: cn(withClose && "grid-cols-[minmax(0,1fr)_auto]", className), ...props, children: [
4317
+ /* @__PURE__ */ u(DialogTitle, { className: cn(withClose && "col-start-1 row-start-1 min-w-0"), children: title }),
4318
+ withClose && /* @__PURE__ */ u(DialogDefaultClose, { className: "col-start-2 row-start-1 row-span-2 self-start justify-self-end" }),
4319
+ description && /* @__PURE__ */ u(DialogDescription, { className: cn(withClose && "row-start-2"), children: description }),
4320
+ children
4321
+ ] });
4322
+ }
4323
+ );
4324
+ DialogDefaultHeader.displayName = "Dialog.DefaultHeader";
4325
+ const Dialog = Object.assign(DialogRoot, {
4326
+ Root: DialogRoot,
4327
+ Trigger: DialogTrigger,
4328
+ Portal: DialogPortal,
4329
+ Overlay: DialogOverlay,
4330
+ Content: DialogContent,
4331
+ Header: DialogHeader,
4332
+ Close: DialogClose,
4333
+ Title: DialogTitle,
4334
+ Description: DialogDescription,
4335
+ Body: DialogBody,
4336
+ Footer: DialogFooter,
4337
+ DefaultClose: DialogDefaultClose,
4338
+ DefaultHeader: DialogDefaultHeader
4339
+ });
4340
+ const inputContainerVariants = cva(
4341
+ [
4342
+ "relative flex rounded-sm overflow-hidden",
4343
+ "h-12 border focus-within:border-bdr-solid",
4344
+ "focus-within:outline-none focus-within:ring-3 focus-within:ring-ring/50 focus-within:ring-offset-0",
4345
+ "transition-highlight"
4346
+ ],
4347
+ {
4348
+ variants: {
4349
+ state: {
4350
+ default: "border-bdr-subtle focus-within:border-bdr-strong",
4351
+ error: "border-error focus-within:border-error focus-within:ring-error/50"
4352
+ },
4353
+ disabled: {
4354
+ true: "select-none"
4355
+ }
4356
+ },
4357
+ defaultVariants: {
4358
+ state: "default",
4359
+ disabled: false
4360
+ }
4361
+ }
4362
+ );
4363
+ const Addon = ({ children, error }) => /* @__PURE__ */ u(
4364
+ "div",
4365
+ {
4366
+ className: cn(
4367
+ "flex items-center justify-center shrink-0 min-w-12 px-4",
4368
+ "text-sm text-subtle bg-surface-primary",
4369
+ "first:rounded-l-sm first:border-r first:border-bdr-subtle",
4370
+ "last:rounded-r-sm last:border-l last:border-bdr-subtle",
4371
+ error && "first:border-error last:border-error"
4372
+ ),
4373
+ children
4374
+ }
4375
+ );
4376
+ const Input = forwardRef(
4377
+ ({ className, label, description, error, startAddon, endAddon, id, disabled, readOnly, ...props }, ref) => {
4378
+ const inputId = usePrefixedId(unwrap(id));
4379
+ const state = error ? "error" : "default";
4380
+ const hasError = state === "error";
4381
+ return /* @__PURE__ */ u("div", { className: cn("w-full", disabled && "opacity-30", className), children: [
4382
+ /* @__PURE__ */ u("div", { className: "mb-2", children: [
4383
+ label && /* @__PURE__ */ u(
4384
+ "label",
4385
+ {
4386
+ htmlFor: inputId,
4387
+ className: cn("block text-base font-semibold text-main", disabled && "opacity-30"),
4388
+ children: /* @__PURE__ */ u("div", { className: "flex items-center gap-2", children: [
4389
+ readOnly && /* @__PURE__ */ u(LockKeyhole, { size: 16, strokeWidth: 2.5 }),
4390
+ label
4391
+ ] })
4392
+ }
4393
+ ),
4394
+ description && /* @__PURE__ */ u("div", { className: cn("text-sm text-subtle", disabled && "opacity-30"), children: description })
4395
+ ] }),
4396
+ /* @__PURE__ */ u("div", { className: cn(inputContainerVariants({ state, disabled })), children: [
4397
+ startAddon && /* @__PURE__ */ u(Addon, { error: hasError, children: startAddon }),
4398
+ /* @__PURE__ */ u(
4399
+ "input",
4400
+ {
4401
+ ref,
4402
+ id: inputId,
4403
+ className: cn(
4404
+ "flex-1 w-full px-4.5 text-base",
4405
+ "text-main bg-surface-neutral placeholder:text-subtle",
4406
+ "border-0 focus:outline-none",
4407
+ "disabled:select-none read-only:bg-surface-primary",
4408
+ startAddon && "rounded-l-none",
4409
+ endAddon && "rounded-r-none"
4410
+ ),
4411
+ disabled,
4412
+ readOnly,
4413
+ ...props
4414
+ }
4415
+ ),
4416
+ endAddon && /* @__PURE__ */ u(Addon, { error: hasError, children: endAddon })
4417
+ ] }),
4418
+ error && /* @__PURE__ */ u("div", { className: "flex items-center gap-2 mt-2 leading-5 text-error", children: [
4419
+ /* @__PURE__ */ u(OctagonAlert, { size: 16, strokeWidth: 2.5 }),
4420
+ error
4421
+ ] })
4422
+ ] });
4423
+ }
4424
+ );
4425
+ Input.displayName = "Input";
4426
+ function isExternalHref(href) {
4427
+ try {
4428
+ if (href.startsWith("mailto:") || href.startsWith("tel:")) return true;
4429
+ const location = typeof window !== "undefined" ? window.location : void 0;
3580
4430
  if (!location) return true;
3581
4431
  const url = new URL(href, location.origin);
3582
4432
  return url.origin !== location.origin;
@@ -3618,74 +4468,847 @@ const Link = forwardRef(
3618
4468
  );
3619
4469
  }
3620
4470
  );
3621
- Link.displayName = "Link";
3622
- function isOfType(node, Comp) {
3623
- return isValidElement(node) && node.type === Comp;
3624
- }
3625
- function findComponentByType(children, Comp) {
3626
- return Children.toArray(children).find((child) => isOfType(child, Comp));
3627
- }
3628
- const ListItemLeft = ({ children, className, ...props }) => /* @__PURE__ */ u("div", { className: cn("flex items-center gap-2.5 flex-shrink-0", className), ...props, children });
3629
- const ListItemContent = ({
3630
- className,
3631
- children,
3632
- ...props
3633
- }) => {
3634
- return /* @__PURE__ */ u("div", { className: cn("flex-1 min-w-0", className), ...props, children });
3635
- };
3636
- ListItemContent.displayName = "ListItem.Content";
3637
- const ListItemDefaultContent = ({
3638
- className,
3639
- label,
3640
- description,
3641
- metadata,
3642
- icon
3643
- }) => {
3644
- return /* @__PURE__ */ u(ListItemContent, { className: cn(icon && "grid grid-cols-[auto_1fr] gap-2.5 items-center", className), children: [
3645
- icon && /* @__PURE__ */ u("div", { className: "flex items-center justify-center flex-shrink-0 group-data-[tone=inverse]:text-alt", children: icon }),
3646
- /* @__PURE__ */ u("div", { className: "min-w-0 text-left", children: [
3647
- label && /* @__PURE__ */ u("h1", { className: "truncate font-semibold group-data-[tone=inverse]:text-alt", children: label }),
3648
- description && /* @__PURE__ */ u("p", { className: "truncate text-sm text-subtle group-data-[tone=inverse]:text-alt", children: description }),
3649
- metadata && /* @__PURE__ */ u("p", { className: "text-xs text-subtle group-data-[tone=inverse]:text-alt", children: metadata })
3650
- ] })
3651
- ] });
3652
- };
3653
- const ListItemRight = ({
3654
- children,
3655
- className,
3656
- ...props
3657
- }) => /* @__PURE__ */ u("div", { className: cn("flex items-center gap-5 flex-shrink-0", className), ...props, children });
3658
- ListItemRight.displayName = "ListItem.Right";
3659
- const ListItemRoot = ({ children, className, selected, ...props }) => {
3660
- const left = findComponentByType(children, ListItemLeft);
3661
- const content = findComponentByType(children, ListItemContent) ?? findComponentByType(children, ListItemDefaultContent);
3662
- const right = findComponentByType(children, ListItemRight);
3663
- return /* @__PURE__ */ u(
3664
- "div",
3665
- {
3666
- className: cn(
3667
- "group flex items-center px-2.5 py-1 gap-2.5",
3668
- selected && "bg-surface-primary-selected text-alt",
3669
- className
3670
- ),
3671
- "data-tone": selected && "inverse",
3672
- role: "listitem",
3673
- ...props,
3674
- children: [
3675
- left,
3676
- content,
3677
- right
3678
- ]
3679
- }
3680
- );
3681
- };
3682
- ListItemRoot.displayName = "ListItem";
3683
- const ListItem = Object.assign(ListItemRoot, {
3684
- Left: ListItemLeft,
3685
- Content: ListItemContent,
3686
- DefaultContent: ListItemDefaultContent,
3687
- Right: ListItemRight
3688
- });
4471
+ Link.displayName = "Link";
4472
+ function isOfType(node, Comp) {
4473
+ return isValidElement(node) && node.type === Comp;
4474
+ }
4475
+ function findComponentByType(children, Comp) {
4476
+ return Children.toArray(children).find((child) => isOfType(child, Comp));
4477
+ }
4478
+ const ListItemLeft = ({ children, className, ...props }) => /* @__PURE__ */ u("div", { className: cn("flex items-center gap-2.5 flex-shrink-0", className), ...props, children });
4479
+ const ListItemContent = ({
4480
+ className,
4481
+ children,
4482
+ ...props
4483
+ }) => {
4484
+ return /* @__PURE__ */ u("div", { className: cn("flex-1 min-w-0", className), ...props, children });
4485
+ };
4486
+ ListItemContent.displayName = "ListItem.Content";
4487
+ const ListItemDefaultContent = ({
4488
+ className,
4489
+ label,
4490
+ description,
4491
+ metadata,
4492
+ icon
4493
+ }) => {
4494
+ return /* @__PURE__ */ u(ListItemContent, { className: cn(icon && "grid grid-cols-[auto_1fr] gap-2.5 items-center", className), children: [
4495
+ icon && /* @__PURE__ */ u("div", { className: "flex items-center justify-center flex-shrink-0 group-data-[tone=inverse]:text-alt", children: icon }),
4496
+ /* @__PURE__ */ u("div", { className: "min-w-0 text-left", children: [
4497
+ label && /* @__PURE__ */ u("h1", { className: "truncate font-semibold group-data-[tone=inverse]:text-alt", children: label }),
4498
+ description && /* @__PURE__ */ u("p", { className: "truncate text-sm text-subtle group-data-[tone=inverse]:text-alt", children: description }),
4499
+ metadata && /* @__PURE__ */ u("p", { className: "text-xs text-subtle group-data-[tone=inverse]:text-alt", children: metadata })
4500
+ ] })
4501
+ ] });
4502
+ };
4503
+ const ListItemRight = ({
4504
+ children,
4505
+ className,
4506
+ ...props
4507
+ }) => /* @__PURE__ */ u("div", { className: cn("flex items-center gap-5 flex-shrink-0", className), ...props, children });
4508
+ ListItemRight.displayName = "ListItem.Right";
4509
+ const ListItemRoot = ({ children, className, selected, ...props }) => {
4510
+ const left = findComponentByType(children, ListItemLeft);
4511
+ const content = findComponentByType(children, ListItemContent) ?? findComponentByType(children, ListItemDefaultContent);
4512
+ const right = findComponentByType(children, ListItemRight);
4513
+ return /* @__PURE__ */ u(
4514
+ "div",
4515
+ {
4516
+ className: cn(
4517
+ "group flex items-center px-2.5 py-1 gap-2.5",
4518
+ selected && "bg-surface-primary-selected text-alt",
4519
+ className
4520
+ ),
4521
+ "data-tone": selected && "inverse",
4522
+ role: "listitem",
4523
+ ...props,
4524
+ children: [
4525
+ left,
4526
+ content,
4527
+ right
4528
+ ]
4529
+ }
4530
+ );
4531
+ };
4532
+ ListItemRoot.displayName = "ListItem";
4533
+ const ListItem = Object.assign(ListItemRoot, {
4534
+ Left: ListItemLeft,
4535
+ Content: ListItemContent,
4536
+ DefaultContent: ListItemDefaultContent,
4537
+ Right: ListItemRight
4538
+ });
4539
+ const EMPTY_SELECTION = [];
4540
+ const ListboxRoot = ({
4541
+ baseId,
4542
+ selection: controlledSelection,
4543
+ defaultSelection = EMPTY_SELECTION,
4544
+ onSelectionChange,
4545
+ active: controlledActive,
4546
+ defaultActive,
4547
+ setActive,
4548
+ selectionMode = "single",
4549
+ focusable = true,
4550
+ disabled,
4551
+ children,
4552
+ keyHandler
4553
+ }) => {
4554
+ const listboxBaseId = baseId ?? usePrefixedId();
4555
+ const [uncontrolledSelection, setUncontrolledSelection] = useState(() => new Set(defaultSelection));
4556
+ const isSelectionControlled = controlledSelection !== void 0;
4557
+ const selectionSet = useMemo(
4558
+ () => isSelectionControlled ? new Set(controlledSelection) : uncontrolledSelection,
4559
+ [isSelectionControlled, controlledSelection, uncontrolledSelection]
4560
+ );
4561
+ const [uncontrolledActive, setUncontrolledActive] = useState(defaultActive);
4562
+ const isActiveControlled = controlledActive !== void 0;
4563
+ const active = isActiveControlled ? controlledActive : uncontrolledActive;
4564
+ const updateActive = useCallback(
4565
+ (id) => {
4566
+ if (!isActiveControlled) {
4567
+ setUncontrolledActive(id);
4568
+ }
4569
+ setActive?.(id);
4570
+ },
4571
+ [isActiveControlled, setActive]
4572
+ );
4573
+ const toggleValue = useCallback(
4574
+ (value) => {
4575
+ const isSelected = selectionSet.has(value);
4576
+ const newSet = new Set(selectionSet);
4577
+ if (selectionMode === "single") {
4578
+ newSet.clear();
4579
+ if (!isSelected) {
4580
+ newSet.add(value);
4581
+ }
4582
+ } else {
4583
+ if (isSelected) {
4584
+ newSet.delete(value);
4585
+ } else {
4586
+ newSet.add(value);
4587
+ }
4588
+ }
4589
+ if (!isSelectionControlled) {
4590
+ setUncontrolledSelection(newSet);
4591
+ }
4592
+ onSelectionChange?.(Array.from(newSet));
4593
+ updateActive(value);
4594
+ },
4595
+ [selectionSet, selectionMode, isSelectionControlled, onSelectionChange, updateActive]
4596
+ );
4597
+ const contextValue = useMemo(
4598
+ () => ({
4599
+ active,
4600
+ selection: selectionSet,
4601
+ selectionMode,
4602
+ focusable,
4603
+ disabled,
4604
+ setActive: updateActive,
4605
+ toggleValue,
4606
+ baseId: listboxBaseId,
4607
+ keyHandler
4608
+ }),
4609
+ [active, selectionSet, selectionMode, focusable, disabled, updateActive, toggleValue, listboxBaseId, keyHandler]
4610
+ );
4611
+ return /* @__PURE__ */ u(ListboxProvider, { value: contextValue, children });
4612
+ };
4613
+ ListboxRoot.displayName = "ListboxRoot";
4614
+ const ListboxContent = forwardRef(
4615
+ ({ className, label, children, ...props }, ref) => {
4616
+ const {
4617
+ active,
4618
+ disabled,
4619
+ setActive,
4620
+ toggleValue,
4621
+ baseId,
4622
+ selectionMode,
4623
+ keyHandler,
4624
+ focusable = true
4625
+ } = useListbox();
4626
+ const innerRef = useRef(null);
4627
+ const getItems = useCallback(() => {
4628
+ const container = innerRef.current;
4629
+ if (!container) {
4630
+ return [];
4631
+ }
4632
+ const optionNodes = container.querySelectorAll('[role="option"][data-value]');
4633
+ return Array.from(optionNodes).map((node) => node.dataset.value).filter((v) => v !== void 0);
4634
+ }, []);
4635
+ const moveActive = useCallback(
4636
+ (delta) => {
4637
+ const items = getItems();
4638
+ if (!items.length) {
4639
+ return;
4640
+ }
4641
+ const currentIndex = active ? items.indexOf(active) : -1;
4642
+ const newIndex = Math.max(0, Math.min(items.length - 1, currentIndex + delta));
4643
+ setActive(items[newIndex]);
4644
+ },
4645
+ [active, setActive, getItems]
4646
+ );
4647
+ const handleKeyDown = keyHandler ?? useCallback(
4648
+ (e) => {
4649
+ if (disabled) {
4650
+ return;
4651
+ }
4652
+ const items = getItems();
4653
+ if (!items.length) {
4654
+ return;
4655
+ }
4656
+ if (e.key === "ArrowDown") {
4657
+ e.preventDefault();
4658
+ moveActive(1);
4659
+ } else if (e.key === "ArrowUp") {
4660
+ e.preventDefault();
4661
+ moveActive(-1);
4662
+ } else if (e.key === "Home") {
4663
+ e.preventDefault();
4664
+ setActive(items[0]);
4665
+ } else if (e.key === "End") {
4666
+ e.preventDefault();
4667
+ setActive(items[items.length - 1]);
4668
+ } else if (e.key === " " || e.key === "Enter") {
4669
+ e.preventDefault();
4670
+ if (active) {
4671
+ toggleValue(active);
4672
+ }
4673
+ }
4674
+ },
4675
+ [disabled, getItems, active, moveActive, toggleValue, setActive]
4676
+ );
4677
+ useEffect(() => {
4678
+ if (!active || !innerRef.current) {
4679
+ return;
4680
+ }
4681
+ const el = innerRef.current.querySelector(`#${baseId}-listbox-option-${active}`);
4682
+ if (el) {
4683
+ el.scrollIntoView({
4684
+ block: "nearest",
4685
+ behavior: "auto"
4686
+ });
4687
+ }
4688
+ }, [active, baseId]);
4689
+ return /* @__PURE__ */ u(
4690
+ "div",
4691
+ {
4692
+ ref: useComposedRefs(ref, innerRef),
4693
+ id: `${baseId}-listbox`,
4694
+ className: cn(
4695
+ "flex flex-col items-start grow shrink-0 basis-0",
4696
+ "max-h-100 overflow-y-auto",
4697
+ "focus-within:outline-none focus-within:ring-3 focus-within:ring-ring/50",
4698
+ disabled && "pointer-events-none select-none opacity-30",
4699
+ className
4700
+ ),
4701
+ role: "listbox",
4702
+ "aria-disabled": disabled,
4703
+ "aria-label": label,
4704
+ "aria-multiselectable": selectionMode === "multiple" ? true : void 0,
4705
+ "aria-activedescendant": active ? `${baseId}-listbox-option-${active}` : void 0,
4706
+ tabIndex: focusable && !disabled ? 0 : -1,
4707
+ onKeyDown: handleKeyDown,
4708
+ ...props,
4709
+ children
4710
+ }
4711
+ );
4712
+ }
4713
+ );
4714
+ ListboxContent.displayName = "ListboxContent";
4715
+ const listboxItemVariants = cva("flex w-full items-center px-4.5 py-1 gap-x-2.5 cursor-pointer", {
4716
+ variants: {
4717
+ selected: {
4718
+ true: "bg-surface-primary-selected text-alt hover:bg-surface-primary-selected-hover",
4719
+ false: "hover:bg-surface-primary-hover"
4720
+ },
4721
+ active: {
4722
+ true: "bg-surface-primary-hover",
4723
+ false: ""
4724
+ }
4725
+ },
4726
+ compoundVariants: [
4727
+ {
4728
+ selected: true,
4729
+ active: true,
4730
+ class: "bg-surface-primary-selected-hover"
4731
+ }
4732
+ ],
4733
+ defaultVariants: {
4734
+ selected: false,
4735
+ active: false
4736
+ }
4737
+ });
4738
+ const ListboxItem = ({ value, children, className, ...props }) => {
4739
+ const ctx = useListbox();
4740
+ const { disabled, toggleValue, baseId } = ctx;
4741
+ const isSelected = ctx.selection.has(value);
4742
+ const isActive = ctx.active === value;
4743
+ const handleClick = useCallback(() => {
4744
+ if (!disabled) {
4745
+ toggleValue(value);
4746
+ }
4747
+ }, [disabled, toggleValue, value]);
4748
+ return (
4749
+ // ARIA listbox pattern: options are not individually focusable
4750
+ // Parent listbox handles all keyboard interactions via aria-activedescendant
4751
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus
4752
+ /* @__PURE__ */ u(
4753
+ "div",
4754
+ {
4755
+ id: `${baseId}-listbox-option-${value}`,
4756
+ className: cn(listboxItemVariants({ selected: isSelected, active: isActive }), className),
4757
+ role: "option",
4758
+ "aria-selected": isSelected,
4759
+ "data-value": value,
4760
+ "data-active": isActive || void 0,
4761
+ onClick: handleClick,
4762
+ ...props,
4763
+ children
4764
+ }
4765
+ )
4766
+ );
4767
+ };
4768
+ ListboxItem.displayName = "ListboxItem";
4769
+ const Listbox = Object.assign(ListboxRoot, {
4770
+ Root: ListboxRoot,
4771
+ Content: ListboxContent,
4772
+ Item: ListboxItem
4773
+ });
4774
+ const MenuRoot = ({
4775
+ open: controlledOpen,
4776
+ defaultOpen = false,
4777
+ onOpenChange,
4778
+ children
4779
+ }) => {
4780
+ const isControlled = controlledOpen !== void 0;
4781
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
4782
+ const open = isControlled ? controlledOpen : uncontrolledOpen;
4783
+ const setOpen = useCallback(
4784
+ (next) => {
4785
+ if (!isControlled) {
4786
+ setUncontrolledOpen(next);
4787
+ }
4788
+ onOpenChange?.(next);
4789
+ },
4790
+ [isControlled, onOpenChange]
4791
+ );
4792
+ const [active, setActive] = useState(void 0);
4793
+ const itemsRef = useRef(/* @__PURE__ */ new Map());
4794
+ const triggerRef = useRef(null);
4795
+ const triggerId = usePrefixedId(void 0, "menu-trigger");
4796
+ const menuId = usePrefixedId(void 0, "menu");
4797
+ const registerItem = useCallback((id, disabled = false) => {
4798
+ itemsRef.current.set(id, { disabled });
4799
+ }, []);
4800
+ const unregisterItem = useCallback((id) => {
4801
+ itemsRef.current.delete(id);
4802
+ }, []);
4803
+ const getItems = useCallback(() => {
4804
+ return Array.from(itemsRef.current.keys());
4805
+ }, []);
4806
+ const isItemDisabled = useCallback((id) => {
4807
+ return itemsRef.current.get(id)?.disabled ?? false;
4808
+ }, []);
4809
+ const value = useMemo(
4810
+ () => ({
4811
+ open,
4812
+ setOpen,
4813
+ active,
4814
+ setActive,
4815
+ registerItem,
4816
+ unregisterItem,
4817
+ getItems,
4818
+ isItemDisabled,
4819
+ triggerId,
4820
+ menuId,
4821
+ triggerRef
4822
+ }),
4823
+ [open, setOpen, active, setActive, registerItem, unregisterItem, getItems, isItemDisabled, triggerId, menuId]
4824
+ );
4825
+ useEffect(() => {
4826
+ if (!open && triggerRef.current) {
4827
+ triggerRef.current.focus();
4828
+ }
4829
+ }, [open]);
4830
+ return /* @__PURE__ */ u(MenuProvider, { value, children });
4831
+ };
4832
+ MenuRoot.displayName = "Menu.Root";
4833
+ const MenuTrigger = forwardRef(
4834
+ ({ asChild, onClick, onKeyDown, className, children, ...props }, ref) => {
4835
+ const { open, setOpen, triggerId, menuId, triggerRef } = useMenu();
4836
+ const handleClick = (e) => {
4837
+ onClick?.(e);
4838
+ setOpen(!open);
4839
+ };
4840
+ const handleKeyDown = (e) => {
4841
+ onKeyDown?.(e);
4842
+ if (e.key === "ArrowDown" && !open) {
4843
+ e.preventDefault();
4844
+ setOpen(true);
4845
+ }
4846
+ };
4847
+ const Comp = asChild ? Slot : "button";
4848
+ return /* @__PURE__ */ u(
4849
+ Comp,
4850
+ {
4851
+ ref: useComposedRefs(ref, triggerRef),
4852
+ id: triggerId,
4853
+ type: asChild ? void 0 : "button",
4854
+ "aria-haspopup": "menu",
4855
+ "aria-expanded": open,
4856
+ "aria-controls": open ? menuId : void 0,
4857
+ onClick: handleClick,
4858
+ onKeyDown: handleKeyDown,
4859
+ className,
4860
+ ...props,
4861
+ children
4862
+ }
4863
+ );
4864
+ }
4865
+ );
4866
+ MenuTrigger.displayName = "Menu.Trigger";
4867
+ const MenuPortal = ({ container, forceMount, children }) => {
4868
+ const { open } = useMenu();
4869
+ const [mounted, setMounted] = useState(false);
4870
+ useEffect(() => {
4871
+ setMounted(true);
4872
+ }, []);
4873
+ if (!mounted || !forceMount && !open) {
4874
+ return null;
4875
+ }
4876
+ return createPortal(children, container ?? document.body);
4877
+ };
4878
+ MenuPortal.displayName = "Menu.Portal";
4879
+ const VIEWPORT_PADDING = 10;
4880
+ const MenuContent = forwardRef(
4881
+ ({
4882
+ forceMount,
4883
+ align = "start",
4884
+ loop = true,
4885
+ onEscapeKeyDown,
4886
+ onPointerDownOutside,
4887
+ onInteractOutside,
4888
+ className,
4889
+ children,
4890
+ ...props
4891
+ }, ref) => {
4892
+ const { open, setOpen, active, setActive, getItems, isItemDisabled, menuId, triggerRef } = useMenu();
4893
+ const contentRef = useRef(null);
4894
+ const composedRefs = useComposedRefs(ref, contentRef);
4895
+ const [position, setPosition] = useState(
4896
+ null
4897
+ );
4898
+ useEffect(() => {
4899
+ if (!open || !triggerRef?.current || !contentRef.current) {
4900
+ return;
4901
+ }
4902
+ const updatePosition = () => {
4903
+ if (!triggerRef.current || !contentRef.current) return;
4904
+ const triggerRect = triggerRef.current.getBoundingClientRect();
4905
+ const contentRect = contentRef.current.getBoundingClientRect();
4906
+ const viewportWidth = window.innerWidth;
4907
+ const viewportHeight = window.innerHeight;
4908
+ let top = triggerRect.bottom;
4909
+ if (top + contentRect.height > viewportHeight) {
4910
+ top = triggerRect.top - contentRect.height;
4911
+ }
4912
+ if (top < 0) {
4913
+ top = VIEWPORT_PADDING;
4914
+ }
4915
+ if (align === "start") {
4916
+ let left = triggerRect.left;
4917
+ if (left + contentRect.width > viewportWidth) {
4918
+ left = triggerRect.right - contentRect.width;
4919
+ }
4920
+ if (left < 0) {
4921
+ left = VIEWPORT_PADDING;
4922
+ }
4923
+ setPosition({ top, left });
4924
+ } else {
4925
+ let right = viewportWidth - triggerRect.right;
4926
+ if (triggerRect.right - contentRect.width < 0) {
4927
+ right = viewportWidth - (triggerRect.left + contentRect.width);
4928
+ }
4929
+ if (right < 0) {
4930
+ right = VIEWPORT_PADDING;
4931
+ }
4932
+ setPosition({ top, right });
4933
+ }
4934
+ };
4935
+ updatePosition();
4936
+ window.addEventListener("resize", updatePosition);
4937
+ window.addEventListener("scroll", updatePosition, true);
4938
+ return () => {
4939
+ window.removeEventListener("resize", updatePosition);
4940
+ window.removeEventListener("scroll", updatePosition, true);
4941
+ };
4942
+ }, [open, triggerRef, align]);
4943
+ useEffect(() => {
4944
+ if (open && contentRef.current) {
4945
+ contentRef.current.focus();
4946
+ const items = getItems();
4947
+ const firstSelectableItem = items.find((itemId) => !isItemDisabled(itemId));
4948
+ if (firstSelectableItem) {
4949
+ setActive(firstSelectableItem);
4950
+ }
4951
+ }
4952
+ }, [open, getItems, isItemDisabled, setActive]);
4953
+ const moveActive = useCallback(
4954
+ (delta) => {
4955
+ const items = getItems();
4956
+ if (!items.length) {
4957
+ return;
4958
+ }
4959
+ const currentIndex = active ? items.indexOf(active) : -1;
4960
+ let newIndex;
4961
+ let attempts = 0;
4962
+ const maxAttempts = items.length;
4963
+ if (currentIndex === -1) {
4964
+ newIndex = delta > 0 ? 0 : items.length - 1;
4965
+ } else {
4966
+ newIndex = currentIndex + delta;
4967
+ }
4968
+ while (attempts < maxAttempts) {
4969
+ if (loop) {
4970
+ if (newIndex < 0) {
4971
+ newIndex = items.length - 1;
4972
+ } else if (newIndex >= items.length) {
4973
+ newIndex = 0;
4974
+ }
4975
+ } else {
4976
+ if (newIndex < 0 || newIndex >= items.length) {
4977
+ return;
4978
+ }
4979
+ }
4980
+ if (!isItemDisabled(items[newIndex])) {
4981
+ setActive(items[newIndex]);
4982
+ return;
4983
+ }
4984
+ newIndex += delta;
4985
+ attempts++;
4986
+ }
4987
+ },
4988
+ [getItems, active, setActive, loop, isItemDisabled]
4989
+ );
4990
+ const handleKeyDown = useCallback(
4991
+ (e) => {
4992
+ const items = getItems();
4993
+ if (!items.length) {
4994
+ return;
4995
+ }
4996
+ switch (e.key) {
4997
+ case "ArrowDown":
4998
+ e.preventDefault();
4999
+ moveActive(1);
5000
+ break;
5001
+ case "ArrowUp":
5002
+ e.preventDefault();
5003
+ moveActive(-1);
5004
+ break;
5005
+ case "Home":
5006
+ e.preventDefault();
5007
+ {
5008
+ const firstEnabled = items.find((id) => !isItemDisabled(id));
5009
+ if (firstEnabled) {
5010
+ setActive(firstEnabled);
5011
+ }
5012
+ }
5013
+ break;
5014
+ case "End":
5015
+ e.preventDefault();
5016
+ {
5017
+ const lastEnabled = [...items].reverse().find((id) => !isItemDisabled(id));
5018
+ if (lastEnabled) {
5019
+ setActive(lastEnabled);
5020
+ }
5021
+ }
5022
+ break;
5023
+ case "Enter":
5024
+ case " ":
5025
+ e.preventDefault();
5026
+ if (active && !isItemDisabled(active)) {
5027
+ const itemElement = document.getElementById(active);
5028
+ if (itemElement) {
5029
+ itemElement.click();
5030
+ }
5031
+ }
5032
+ break;
5033
+ case "Escape":
5034
+ e.preventDefault();
5035
+ onEscapeKeyDown?.(e);
5036
+ setOpen(false);
5037
+ break;
5038
+ case "Tab":
5039
+ setOpen(false);
5040
+ break;
5041
+ }
5042
+ },
5043
+ [getItems, active, moveActive, setActive, isItemDisabled, onEscapeKeyDown, setOpen]
5044
+ );
5045
+ const handlePointerDownOutside = useCallback(
5046
+ (e) => {
5047
+ const target = e.target;
5048
+ if (triggerRef?.current?.contains(target)) {
5049
+ return;
5050
+ }
5051
+ if (contentRef.current && !contentRef.current.contains(target)) {
5052
+ onPointerDownOutside?.(e);
5053
+ onInteractOutside?.(e);
5054
+ if (!e.defaultPrevented) {
5055
+ setOpen(false);
5056
+ }
5057
+ }
5058
+ },
5059
+ [onPointerDownOutside, onInteractOutside, setOpen, triggerRef]
5060
+ );
5061
+ useEffect(() => {
5062
+ if (!open) {
5063
+ return;
5064
+ }
5065
+ document.addEventListener("pointerdown", handlePointerDownOutside);
5066
+ return () => document.removeEventListener("pointerdown", handlePointerDownOutside);
5067
+ }, [open, handlePointerDownOutside]);
5068
+ useEffect(() => {
5069
+ if (!active || !contentRef.current) {
5070
+ return;
5071
+ }
5072
+ const el = contentRef.current.querySelector(`#${active}`);
5073
+ if (el) {
5074
+ el.scrollIntoView({
5075
+ block: "nearest",
5076
+ behavior: "auto"
5077
+ });
5078
+ }
5079
+ }, [active]);
5080
+ if (!forceMount && !open) {
5081
+ return null;
5082
+ }
5083
+ return /* @__PURE__ */ u(
5084
+ "div",
5085
+ {
5086
+ ref: composedRefs,
5087
+ id: menuId,
5088
+ role: "menu",
5089
+ "aria-orientation": "vertical",
5090
+ "aria-activedescendant": active,
5091
+ tabIndex: -1,
5092
+ "data-state": open ? "open" : "closed",
5093
+ className: cn(
5094
+ "fixed z-40 flex flex-col items-start w-fit py-2.5 mt-2.5 overflow-hidden",
5095
+ "rounded-sm border border-bdr-subtle bg-surface-neutral shadow-lg outline-none",
5096
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
5097
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
5098
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
5099
+ !position && "opacity-0 pointer-events-none",
5100
+ className
5101
+ ),
5102
+ style: { ...position },
5103
+ onKeyDown: handleKeyDown,
5104
+ ...props,
5105
+ children
5106
+ }
5107
+ );
5108
+ }
5109
+ );
5110
+ MenuContent.displayName = "Menu.Content";
5111
+ const menuItemVariants = cva("flex w-full items-center px-4.5 py-2.5 gap-x-1.25 cursor-pointer text-sm outline-none", {
5112
+ variants: {
5113
+ active: {
5114
+ true: "bg-surface-primary-selected",
5115
+ false: ""
5116
+ },
5117
+ disabled: {
5118
+ true: "hover:bg-transparent opacity-30 select-none pointer-events-none",
5119
+ false: ""
5120
+ }
5121
+ },
5122
+ compoundVariants: [
5123
+ {
5124
+ active: true,
5125
+ disabled: false,
5126
+ class: "text-alt outline-none ring-3 ring-inset ring-bdr-strong"
5127
+ }
5128
+ ],
5129
+ defaultVariants: {
5130
+ active: false,
5131
+ disabled: false
5132
+ }
5133
+ });
5134
+ const MenuItem = ({
5135
+ id: providedId,
5136
+ asChild = false,
5137
+ disabled = false,
5138
+ onSelect,
5139
+ className,
5140
+ children,
5141
+ ...props
5142
+ }) => {
5143
+ const id = usePrefixedId(providedId, providedId ? void 0 : "menu-item");
5144
+ const { active, setActive, setOpen, registerItem, unregisterItem } = useMenu();
5145
+ const isActive = active === id;
5146
+ const isDisabled = disabled;
5147
+ useEffect(() => {
5148
+ registerItem(id, disabled);
5149
+ return () => unregisterItem(id);
5150
+ }, [id, disabled, registerItem, unregisterItem]);
5151
+ const handleClick = useCallback(() => {
5152
+ if (isDisabled) {
5153
+ return;
5154
+ }
5155
+ const event = new Event("select", { bubbles: true, cancelable: true });
5156
+ onSelect?.(event);
5157
+ if (!event.defaultPrevented) {
5158
+ setOpen(false);
5159
+ }
5160
+ }, [isDisabled, onSelect, setOpen]);
5161
+ const handlePointerMove = useCallback(() => {
5162
+ if (!isActive) {
5163
+ setActive(id);
5164
+ }
5165
+ }, [isActive, setActive, id]);
5166
+ const handlePointerLeave = useCallback(() => {
5167
+ setActive(void 0);
5168
+ }, [setActive]);
5169
+ const Comp = asChild ? Slot : "div";
5170
+ return /* @__PURE__ */ u(
5171
+ Comp,
5172
+ {
5173
+ id,
5174
+ role: "menuitem",
5175
+ "aria-disabled": isDisabled,
5176
+ "data-active": isActive || void 0,
5177
+ "data-disabled": isDisabled || void 0,
5178
+ className: cn(menuItemVariants({ active: isActive, disabled: isDisabled }), className),
5179
+ onClick: handleClick,
5180
+ onPointerMove: handlePointerMove,
5181
+ onPointerLeave: handlePointerLeave,
5182
+ ...props,
5183
+ children
5184
+ }
5185
+ );
5186
+ };
5187
+ MenuItem.displayName = "Menu.Item";
5188
+ const MenuLabel = forwardRef(({ className, children, ...props }, ref) => {
5189
+ return /* @__PURE__ */ u("div", { ref, className: cn("px-3 py-1.5 text-xs font-semibold text-subtle", className), ...props, children });
5190
+ });
5191
+ MenuLabel.displayName = "Menu.Label";
5192
+ const MenuSeparator = forwardRef(({ className, ...props }, ref) => {
5193
+ return /* @__PURE__ */ u(
5194
+ "div",
5195
+ {
5196
+ ref,
5197
+ role: "separator",
5198
+ "aria-orientation": "horizontal",
5199
+ className: cn("my-1 h-px bg-bdr-subtle", className),
5200
+ ...props
5201
+ }
5202
+ );
5203
+ });
5204
+ MenuSeparator.displayName = "Menu.Separator";
5205
+ const Menu = Object.assign(MenuRoot, {
5206
+ Root: MenuRoot,
5207
+ Trigger: MenuTrigger,
5208
+ Portal: MenuPortal,
5209
+ Content: MenuContent,
5210
+ Item: MenuItem,
5211
+ Label: MenuLabel,
5212
+ Separator: MenuSeparator
5213
+ });
5214
+ const SearchInput = forwardRef(
5215
+ ({
5216
+ id,
5217
+ value,
5218
+ defaultValue = "",
5219
+ placeholder = "Search",
5220
+ clearLabel = "Clear",
5221
+ onChange,
5222
+ disabled,
5223
+ readOnly,
5224
+ showSearchIcon = true,
5225
+ showClearButton = true,
5226
+ className,
5227
+ ...props
5228
+ }, ref) => {
5229
+ const inputId = usePrefixedId(unwrap(id));
5230
+ const [uncontrolledValue, setUncontrolledValue] = useState$1(defaultValue);
5231
+ const isControlled = value !== void 0;
5232
+ const inputValue = isControlled ? value : uncontrolledValue;
5233
+ const isValueSet = inputValue.length > 0;
5234
+ const canClear = showClearButton && isValueSet && !disabled && !readOnly;
5235
+ const handleChange = (e) => {
5236
+ const newValue = e.currentTarget.value;
5237
+ if (!isControlled) {
5238
+ setUncontrolledValue(newValue);
5239
+ }
5240
+ onChange?.(newValue);
5241
+ };
5242
+ const inputRef = useRef(null);
5243
+ const handleClear = () => {
5244
+ const newValue = "";
5245
+ if (!isControlled) {
5246
+ setUncontrolledValue(newValue);
5247
+ }
5248
+ onChange?.(newValue);
5249
+ inputRef.current?.focus();
5250
+ };
5251
+ return /* @__PURE__ */ u(
5252
+ "div",
5253
+ {
5254
+ className: cn(
5255
+ "relative flex items-center rounded-sm overflow-hidden",
5256
+ "h-12 border border-bdr-subtle focus-within:border-bdr-strong",
5257
+ "focus-within:outline-none focus-within:ring-3 focus-within:ring-ring/50 focus-within:ring-offset-0",
5258
+ "transition-highlight",
5259
+ disabled && "pointer-events-none select-none opacity-30",
5260
+ className
5261
+ ),
5262
+ children: [
5263
+ showSearchIcon && /* @__PURE__ */ u(
5264
+ Search,
5265
+ {
5266
+ className: "flex items-center justify-center shrink-0 w-10 text-subtle pointer-events-none",
5267
+ size: 20,
5268
+ strokeWidth: 1.5
5269
+ }
5270
+ ),
5271
+ /* @__PURE__ */ u(
5272
+ "input",
5273
+ {
5274
+ ref: useComposedRefs(ref, inputRef),
5275
+ id: inputId,
5276
+ className: cn(
5277
+ "w-full text-base border-0",
5278
+ "text-main bg-surface-neutral",
5279
+ "placeholder:text-subtle",
5280
+ "focus:outline-none",
5281
+ "read-only:bg-surface-primary",
5282
+ !showSearchIcon && "pl-4.5",
5283
+ "pr-4"
5284
+ ),
5285
+ value: inputValue,
5286
+ onChange: handleChange,
5287
+ readOnly,
5288
+ disabled,
5289
+ placeholder,
5290
+ "aria-label": "Search",
5291
+ "aria-disabled": disabled,
5292
+ ...props
5293
+ }
5294
+ ),
5295
+ canClear && /* @__PURE__ */ u(
5296
+ IconButton,
5297
+ {
5298
+ className: "flex items-center justify-center shrink-0 h-10 w-10 mr-1 text-subtle",
5299
+ size: "lg",
5300
+ icon: X,
5301
+ title: clearLabel,
5302
+ onClick: handleClear,
5303
+ disabled
5304
+ }
5305
+ )
5306
+ ]
5307
+ }
5308
+ );
5309
+ }
5310
+ );
5311
+ SearchInput.displayName = "SearchInput";
3689
5312
  const SelectableListItem = ({
3690
5313
  className,
3691
5314
  selected,
@@ -3725,6 +5348,27 @@ const SelectableListItem = ({
3725
5348
  );
3726
5349
  };
3727
5350
  SelectableListItem.displayName = "SelectableListItem";
5351
+ const Separator = ({ className, label, decorative = false, ...props }) => {
5352
+ const ariaHidden = decorative ? "true" : void 0;
5353
+ if (!label) {
5354
+ return /* @__PURE__ */ u("hr", { "aria-hidden": ariaHidden, className: cn("w-full border-bdr-subtle", className) });
5355
+ }
5356
+ return /* @__PURE__ */ u(
5357
+ "div",
5358
+ {
5359
+ role: "separator",
5360
+ "aria-orientation": "horizontal",
5361
+ "aria-hidden": ariaHidden,
5362
+ className: cn("inline-flex w-full gap-2.5", className),
5363
+ ...props,
5364
+ children: [
5365
+ /* @__PURE__ */ u("span", { className: "min-w-0 truncate text-subtle uppercase", children: label }),
5366
+ /* @__PURE__ */ u("span", { className: "min-w-6 flex-1 border-b-1 border-bdr-subtle" })
5367
+ ]
5368
+ }
5369
+ );
5370
+ };
5371
+ Separator.displayName = "Separator";
3728
5372
  const oppositeSide = {
3729
5373
  top: "bottom",
3730
5374
  bottom: "top",
@@ -3924,7 +5568,7 @@ function Tooltip({
3924
5568
  children
3925
5569
  }
3926
5570
  ),
3927
- isOpen && hasContent && createPortal(
5571
+ isOpen && hasContent && createPortal$1(
3928
5572
  /* @__PURE__ */ u(
3929
5573
  TooltipContent,
3930
5574
  {
@@ -3939,299 +5583,14 @@ function Tooltip({
3939
5583
  )
3940
5584
  ] });
3941
5585
  }
3942
- const DialogContext = createContext(void 0);
3943
- const DialogProvider = ({ value, children }) => {
3944
- return /* @__PURE__ */ u(DialogContext.Provider, { value, children });
3945
- };
3946
- DialogProvider.displayName = "DialogProvider";
3947
- const useDialog = () => {
3948
- const context = useContext(DialogContext);
3949
- if (!context) {
3950
- throw new Error("useDialog must be used within a DialogProvider");
3951
- }
3952
- return context;
3953
- };
3954
- function setRef(ref, value) {
3955
- if (!ref) return;
3956
- if (typeof ref === "function") {
3957
- ref(value);
3958
- } else {
3959
- try {
3960
- ref.current = value;
3961
- } catch {
3962
- }
3963
- }
3964
- }
3965
- function useComposedRefs(...refs) {
3966
- return useCallback((node) => {
3967
- refs.forEach((ref) => setRef(ref, node));
3968
- }, refs);
3969
- }
3970
- const SearchInput = forwardRef(
3971
- ({
3972
- className,
3973
- id,
3974
- value,
3975
- defaultValue = "",
3976
- placeholder = "Search",
3977
- clearLabel = "Clear",
3978
- onChange,
3979
- disabled,
3980
- readOnly,
3981
- ...props
3982
- }, ref) => {
3983
- const inputId = usePrefixedId(unwrap(id));
3984
- const [uncontrolledValue, setUncontrolledValue] = useState$1(defaultValue);
3985
- const isControlled = value !== void 0;
3986
- const inputValue = isControlled ? value : uncontrolledValue;
3987
- const isValueSet = inputValue.length > 0;
3988
- const canClear = isValueSet && !disabled && !readOnly;
3989
- const handleChange = (e) => {
3990
- const newValue = e.currentTarget.value;
3991
- if (!isControlled) {
3992
- setUncontrolledValue(newValue);
3993
- }
3994
- onChange?.(newValue);
3995
- };
3996
- const inputRef = useRef(null);
3997
- const handleClear = () => {
3998
- const newValue = "";
3999
- if (!isControlled) {
4000
- setUncontrolledValue(newValue);
4001
- }
4002
- onChange?.(newValue);
4003
- inputRef.current?.focus();
4004
- };
4005
- return /* @__PURE__ */ u(
4006
- "div",
4007
- {
4008
- className: cn(
4009
- "relative flex rounded-sm overflow-hidden",
4010
- "h-12 border border-bdr-subtle focus-within:border-bdr-strong",
4011
- "focus-within:outline-none focus-within:ring-3 focus-within:ring-ring/50 focus-within:ring-offset-0",
4012
- "transition-highlight",
4013
- disabled && "pointer-events-none select-none opacity-30",
4014
- className
4015
- ),
4016
- children: [
4017
- /* @__PURE__ */ u(
4018
- Search,
4019
- {
4020
- className: "absolute left-4.5 top-1/2 -translate-y-1/2 mt-0.25 text-subtle pointer-events-none",
4021
- size: 20,
4022
- strokeWidth: 1.5
4023
- }
4024
- ),
4025
- /* @__PURE__ */ u(
4026
- "input",
4027
- {
4028
- ref: useComposedRefs(ref, inputRef),
4029
- id: inputId,
4030
- className: cn(
4031
- "w-full text-base pr-1 px-12 border-0",
4032
- "text-main bg-surface-neutral",
4033
- "placeholder:text-subtle",
4034
- "focus:outline-none",
4035
- "read-only:bg-surface-primary"
4036
- ),
4037
- value: inputValue,
4038
- onChange: handleChange,
4039
- readOnly,
4040
- disabled,
4041
- placeholder,
4042
- "aria-label": "Search",
4043
- "aria-disabled": disabled,
4044
- ...props
4045
- }
4046
- ),
4047
- canClear && /* @__PURE__ */ u(
4048
- IconButton,
4049
- {
4050
- className: "absolute right-3 top-1/2 -translate-y-1/2 w-8 h-8 text-subtle",
4051
- size: "lg",
4052
- icon: X,
4053
- title: clearLabel,
4054
- onClick: handleClear,
4055
- disabled
4056
- }
4057
- )
4058
- ]
4059
- }
4060
- );
4061
- }
4062
- );
4063
- SearchInput.displayName = "SearchInput";
4064
- function ListboxImpl({
4065
- className,
4066
- selection: controlledSelection,
4067
- selectionMode = "single",
4068
- disabled = false,
4069
- defaultSelection = /* @__PURE__ */ new Set(),
4070
- setSelection,
4071
- active: controlledActive,
4072
- defaultActive,
4073
- onActiveChange,
4074
- items,
4075
- renderItem,
4076
- getValue,
4077
- ...props
4078
- }, ref) {
4079
- const listboxId = usePrefixedId();
4080
- const innerRef = useRef(null);
4081
- const [uncontrolledSelection, setUncontrolledSelection] = useState(defaultSelection);
4082
- const isSelectionControlled = controlledSelection !== void 0;
4083
- const selection = isSelectionControlled ? controlledSelection : uncontrolledSelection;
4084
- const [uncontrolledActive, setUncontrolledActive] = useState(
4085
- defaultActive ?? (items[0] ? getValue(items[0]) : void 0)
4086
- );
4087
- const isActiveControlled = controlledActive !== void 0;
4088
- const active = isActiveControlled ? controlledActive : uncontrolledActive;
4089
- const valueToIndexMap = useMemo(() => {
4090
- return new Map(items.map((item, idx) => [getValue(item), idx]));
4091
- }, [items, getValue]);
4092
- const updateActive = useCallback(
4093
- (newActive) => {
4094
- if (!isActiveControlled) {
4095
- setUncontrolledActive(newActive);
4096
- }
4097
- onActiveChange?.(newActive);
4098
- },
4099
- [isActiveControlled, onActiveChange]
4100
- );
4101
- useEffect(() => {
4102
- if (!active || !items.some((item) => getValue(item) === active)) {
4103
- updateActive(items[0] ? getValue(items[0]) : void 0);
4104
- }
4105
- }, [items, getValue, active, updateActive]);
4106
- useEffect(() => {
4107
- if (!active || !innerRef.current) return;
4108
- const el = innerRef.current.querySelector(`#${listboxId}-option-${active}`);
4109
- el?.scrollIntoView({ block: "nearest", behavior: "auto" });
4110
- }, [active, listboxId]);
4111
- const handleToggleValue = (value) => {
4112
- const isAlreadySelected = selection.has(value);
4113
- let newSelection;
4114
- if (selectionMode === "single") {
4115
- newSelection = isAlreadySelected ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([value]);
4116
- } else {
4117
- newSelection = new Set(selection);
4118
- if (isAlreadySelected) {
4119
- newSelection.delete(value);
4120
- } else {
4121
- newSelection.add(value);
4122
- }
4123
- }
4124
- if (!isSelectionControlled) {
4125
- setUncontrolledSelection(newSelection);
4126
- }
4127
- setSelection?.(newSelection);
4128
- updateActive(value);
4129
- };
4130
- const handleClickItem = ({ target }) => {
4131
- const li = target instanceof Element ? target.closest("li") : null;
4132
- const value = li?.getAttribute("data-value");
4133
- if (value) {
4134
- handleToggleValue(value);
4135
- }
4136
- };
4137
- const moveActive = (delta) => {
4138
- const activeIndex = active ? valueToIndexMap.get(active) ?? 0 : 0;
4139
- const newIndex = Math.max(0, Math.min(items.length - 1, activeIndex + delta));
4140
- updateActive(getValue(items[newIndex]));
4141
- };
4142
- const handleKeyDown = (e) => {
4143
- if (disabled) {
4144
- return;
4145
- }
4146
- if (e.key === "ArrowDown") {
4147
- e.preventDefault();
4148
- moveActive(1);
4149
- } else if (e.key === "ArrowUp") {
4150
- e.preventDefault();
4151
- moveActive(-1);
4152
- } else if (e.key === "Home") {
4153
- e.preventDefault();
4154
- const firstItem = items[0];
4155
- updateActive(firstItem && getValue(firstItem));
4156
- } else if (e.key === "End") {
4157
- e.preventDefault();
4158
- const lastItem = items.at(-1);
4159
- updateActive(lastItem && getValue(lastItem));
4160
- } else if (e.key === " " || e.key === "Enter") {
4161
- e.preventDefault();
4162
- if (active) {
4163
- handleToggleValue(active);
4164
- }
4165
- }
4166
- };
4167
- return /* @__PURE__ */ u(
4168
- "ul",
4169
- {
4170
- id: listboxId,
4171
- ref: useComposedRefs(ref, innerRef),
4172
- tabIndex: disabled ? -1 : 0,
4173
- className: cn(
4174
- "flex flex-col items-start grow-1 shrink-0 basis-0",
4175
- "max-h-100 px-0 overflow-y-auto",
4176
- "focus-within:border-bdr-solid focus-within:outline-none focus-within:ring-3 focus-within:ring-ring/50 focus-within:ring-offset-0",
4177
- "focus-within:[&>li[data-active=true]]:bg-surface-primary-hover",
4178
- "focus-within:[&>li[data-active=true][aria-selected=true]]:bg-surface-primary-selected-hover",
4179
- "transition-highlight",
4180
- disabled && "pointer-events-none select-none opacity-30",
4181
- className
4182
- ),
4183
- role: "listbox",
4184
- "aria-activedescendant": active ? `${listboxId}-option-${active}` : void 0,
4185
- "aria-disabled": disabled,
4186
- "aria-multiselectable": selectionMode === "multiple",
4187
- "aria-label": props.label ?? void 0,
4188
- "aria-orientation": "vertical",
4189
- onKeyDown: handleKeyDown,
4190
- onClick: handleClickItem,
4191
- children: items.map((item) => {
4192
- const value = getValue(item);
4193
- const selected = selection.has(value);
4194
- const isActive = value === active;
4195
- return /* @__PURE__ */ u(
4196
- "li",
4197
- {
4198
- id: `${listboxId}-option-${value}`,
4199
- "data-value": value,
4200
- "data-active": isActive ? "true" : "false",
4201
- tabIndex: -1,
4202
- className: cn(
4203
- "flex items-center w-full px-4.5 py-1 gap-x-2.5",
4204
- !disabled && "cursor-pointer",
4205
- selected ? "bg-surface-primary-selected text-alt hover:bg-surface-primary-selected-hover" : "hover:bg-surface-primary-hover"
4206
- ),
4207
- role: "option",
4208
- "aria-selected": selected,
4209
- children: renderItem(item, selected, isActive)
4210
- },
4211
- value
4212
- );
4213
- })
4214
- }
4215
- );
4216
- }
4217
- const Listbox = forwardRef(ListboxImpl);
4218
- Listbox.displayName = "Listbox";
4219
- const useScrollLock = (lock, element) => {
4220
- useEffect(() => {
4221
- if (!lock) {
4222
- return;
4223
- }
4224
- const target = element ?? document.body;
4225
- const originalOverflow = target.style.overflow;
4226
- target.style.overflow = "hidden";
4227
- return () => {
4228
- target.style.overflow = originalOverflow;
4229
- };
4230
- }, [lock, element]);
4231
- };
4232
5586
  export {
5587
+ Avatar,
5588
+ AvatarProvider,
4233
5589
  Button,
4234
5590
  Checkbox,
5591
+ Combobox,
5592
+ ComboboxProvider,
5593
+ Dialog,
4235
5594
  DialogProvider,
4236
5595
  IconButton,
4237
5596
  IdProvider,
@@ -4239,12 +5598,22 @@ export {
4239
5598
  Link,
4240
5599
  ListItem,
4241
5600
  Listbox,
5601
+ ListboxProvider,
5602
+ Menu,
5603
+ MenuProvider,
4242
5604
  SearchInput,
4243
5605
  SelectableListItem,
5606
+ Separator,
4244
5607
  Tooltip,
4245
5608
  cn,
5609
+ setRef,
4246
5610
  unwrap,
5611
+ useAvatar,
5612
+ useCombobox,
5613
+ useComposedRefs,
4247
5614
  useDialog,
5615
+ useListbox,
5616
+ useMenu,
4248
5617
  usePrefixedId,
4249
5618
  useScrollLock
4250
5619
  };