@tollerud/ui 1.0.8 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,12 +2,12 @@
2
2
  import { clsx } from 'clsx';
3
3
  import { twMerge } from 'tailwind-merge';
4
4
  import * as React2 from 'react';
5
- import { forwardRef, useState, useCallback, lazy, useRef, useEffect, useMemo, useId, Suspense } from 'react';
5
+ import { forwardRef, useState, useCallback, lazy, useRef, useEffect, useMemo, useId, Fragment as Fragment$1, createContext, useContext, Suspense } from 'react';
6
6
  import { Slot } from '@radix-ui/react-slot';
7
7
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
8
  import { motion } from 'framer-motion';
9
9
  import * as DialogPrimitive from '@radix-ui/react-dialog';
10
- import { X } from 'lucide-react';
10
+ import { X, ChevronRight, ChevronLeft, MoreHorizontal, Check, ChevronDown, EyeOff, Eye, Calendar, Upload, File } from 'lucide-react';
11
11
  import * as TooltipPrimitive from '@radix-ui/react-tooltip';
12
12
  import * as TabsPrimitive from '@radix-ui/react-tabs';
13
13
  import * as ProgressPrimitive from '@radix-ui/react-progress';
@@ -2650,7 +2650,1166 @@ function MetricBadge({ label, value }) {
2650
2650
  /* @__PURE__ */ jsx("span", { className: "float-right text-tollerud-text-primary font-mono", children: value })
2651
2651
  ] });
2652
2652
  }
2653
+ var Divider = forwardRef(
2654
+ ({ className, orientation = "horizontal", label, ...props }, ref) => {
2655
+ if (orientation === "vertical") {
2656
+ return /* @__PURE__ */ jsx(
2657
+ "div",
2658
+ {
2659
+ ref,
2660
+ role: "separator",
2661
+ "aria-orientation": "vertical",
2662
+ className: cn("w-px self-stretch bg-tollerud-border", className),
2663
+ ...props
2664
+ }
2665
+ );
2666
+ }
2667
+ if (label) {
2668
+ return /* @__PURE__ */ jsxs(
2669
+ "div",
2670
+ {
2671
+ ref,
2672
+ role: "separator",
2673
+ "aria-orientation": "horizontal",
2674
+ className: cn("flex items-center gap-3 text-xs text-tollerud-text-muted", className),
2675
+ ...props,
2676
+ children: [
2677
+ /* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-tollerud-border" }),
2678
+ /* @__PURE__ */ jsx("span", { children: label }),
2679
+ /* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-tollerud-border" })
2680
+ ]
2681
+ }
2682
+ );
2683
+ }
2684
+ return /* @__PURE__ */ jsx(
2685
+ "div",
2686
+ {
2687
+ ref,
2688
+ role: "separator",
2689
+ "aria-orientation": "horizontal",
2690
+ className: cn("h-px w-full bg-tollerud-border", className),
2691
+ ...props
2692
+ }
2693
+ );
2694
+ }
2695
+ );
2696
+ Divider.displayName = "Divider";
2697
+ var pillVariants = {
2698
+ outline: "bg-transparent border border-tollerud-border text-tollerud-text-secondary",
2699
+ solid: "bg-tollerud-surface-raised text-tollerud-text-primary",
2700
+ accent: "bg-tollerud-yellow/15 border border-tollerud-yellow/30 text-tollerud-yellow"
2701
+ };
2702
+ var Pill = forwardRef(
2703
+ ({ className, variant = "outline", ...props }, ref) => {
2704
+ return /* @__PURE__ */ jsx(
2705
+ "span",
2706
+ {
2707
+ ref,
2708
+ className: cn(
2709
+ "inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-full leading-none",
2710
+ pillVariants[variant],
2711
+ className
2712
+ ),
2713
+ ...props
2714
+ }
2715
+ );
2716
+ }
2717
+ );
2718
+ Pill.displayName = "Pill";
2719
+ var avatarSizes = {
2720
+ sm: "h-6 w-6 text-[10px]",
2721
+ md: "h-8 w-8 text-xs",
2722
+ lg: "h-11 w-11 text-sm"
2723
+ };
2724
+ function initialsFrom(name) {
2725
+ const parts = name.trim().split(/\s+/).filter(Boolean);
2726
+ if (parts.length === 0) return "";
2727
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
2728
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
2729
+ }
2730
+ var Avatar = forwardRef(
2731
+ ({ className, src, name, fallback, size = "md", ...props }, ref) => {
2732
+ const [errored, setErrored] = useState(false);
2733
+ const showImage = !!src && !errored;
2734
+ return /* @__PURE__ */ jsx(
2735
+ "span",
2736
+ {
2737
+ ref,
2738
+ className: cn(
2739
+ "relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full select-none",
2740
+ "bg-tollerud-surface-raised text-tollerud-text-secondary font-medium",
2741
+ "ring-1 ring-tollerud-border",
2742
+ avatarSizes[size],
2743
+ className
2744
+ ),
2745
+ ...props,
2746
+ children: showImage ? (
2747
+ // eslint-disable-next-line @next/next/no-img-element
2748
+ /* @__PURE__ */ jsx(
2749
+ "img",
2750
+ {
2751
+ src,
2752
+ alt: name ?? "",
2753
+ className: "h-full w-full object-cover",
2754
+ onError: () => setErrored(true)
2755
+ }
2756
+ )
2757
+ ) : /* @__PURE__ */ jsx("span", { "aria-hidden": !!name, children: fallback ?? (name ? initialsFrom(name) : null) })
2758
+ }
2759
+ );
2760
+ }
2761
+ );
2762
+ Avatar.displayName = "Avatar";
2763
+ var AvatarGroup = forwardRef(
2764
+ ({ className, max, size = "md", children, ...props }, ref) => {
2765
+ const items = Array.isArray(children) ? children : [children];
2766
+ const visible = max ? items.slice(0, max) : items;
2767
+ const overflow = max ? items.length - max : 0;
2768
+ return /* @__PURE__ */ jsxs(
2769
+ "div",
2770
+ {
2771
+ ref,
2772
+ className: cn("flex items-center -space-x-2", className),
2773
+ ...props,
2774
+ children: [
2775
+ visible.map((child, i) => /* @__PURE__ */ jsx("span", { className: "ring-2 ring-tollerud-surface rounded-full", children: child }, i)),
2776
+ overflow > 0 && /* @__PURE__ */ jsxs(
2777
+ "span",
2778
+ {
2779
+ className: cn(
2780
+ "relative inline-flex shrink-0 items-center justify-center rounded-full select-none",
2781
+ "bg-tollerud-surface-raised text-tollerud-text-muted font-medium",
2782
+ "ring-2 ring-tollerud-surface",
2783
+ avatarSizes[size]
2784
+ ),
2785
+ children: [
2786
+ "+",
2787
+ overflow
2788
+ ]
2789
+ }
2790
+ )
2791
+ ]
2792
+ }
2793
+ );
2794
+ }
2795
+ );
2796
+ AvatarGroup.displayName = "AvatarGroup";
2797
+ var Breadcrumb = forwardRef(
2798
+ ({ className, items, separator, ...props }, ref) => {
2799
+ return /* @__PURE__ */ jsx("nav", { ref, "aria-label": "Breadcrumb", className: cn("text-sm", className), ...props, children: /* @__PURE__ */ jsx("ol", { className: "flex items-center gap-1.5 flex-wrap", children: items.map((item, i) => {
2800
+ const isLast = i === items.length - 1;
2801
+ const content = !isLast && (item.href || item.onClick) ? /* @__PURE__ */ jsx(
2802
+ "a",
2803
+ {
2804
+ href: item.href,
2805
+ onClick: item.onClick,
2806
+ className: "text-tollerud-text-secondary hover:text-tollerud-text-primary transition-colors duration-[150ms]",
2807
+ children: item.label
2808
+ }
2809
+ ) : /* @__PURE__ */ jsx(
2810
+ "span",
2811
+ {
2812
+ "aria-current": isLast ? "page" : void 0,
2813
+ className: isLast ? "text-tollerud-text-primary font-medium" : "text-tollerud-text-secondary",
2814
+ children: item.label
2815
+ }
2816
+ );
2817
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
2818
+ /* @__PURE__ */ jsx("li", { className: "flex items-center gap-1.5", children: content }),
2819
+ !isLast && /* @__PURE__ */ jsx("li", { "aria-hidden": "true", className: "flex items-center text-tollerud-text-muted", children: separator ?? /* @__PURE__ */ jsx(ChevronRight, { size: 14 }) })
2820
+ ] }, i);
2821
+ }) }) });
2822
+ }
2823
+ );
2824
+ Breadcrumb.displayName = "Breadcrumb";
2825
+ function getPageRange(page, pageCount, siblingCount) {
2826
+ const totalVisible = siblingCount * 2 + 5;
2827
+ if (pageCount <= totalVisible) {
2828
+ return Array.from({ length: pageCount }, (_, i) => i + 1);
2829
+ }
2830
+ const left = Math.max(page - siblingCount, 1);
2831
+ const right = Math.min(page + siblingCount, pageCount);
2832
+ const range = [1];
2833
+ if (left > 2) range.push("ellipsis");
2834
+ for (let p = left === 1 ? 2 : left; p <= (right === pageCount ? pageCount - 1 : right); p++) {
2835
+ range.push(p);
2836
+ }
2837
+ if (right < pageCount - 1) range.push("ellipsis");
2838
+ if (pageCount > 1) range.push(pageCount);
2839
+ return range;
2840
+ }
2841
+ var navButtonClasses = "inline-flex h-8 min-w-8 items-center justify-center rounded px-2 text-sm transition-colors duration-[150ms] disabled:opacity-40 disabled:pointer-events-none";
2842
+ var Pagination = forwardRef(
2843
+ ({ className, page, pageCount, onChange, siblingCount = 1, ...props }, ref) => {
2844
+ const range = getPageRange(page, pageCount, siblingCount);
2845
+ return /* @__PURE__ */ jsxs("nav", { ref, "aria-label": "Pagination", className: cn("flex items-center gap-1", className), ...props, children: [
2846
+ /* @__PURE__ */ jsx(
2847
+ "button",
2848
+ {
2849
+ type: "button",
2850
+ "aria-label": "Previous page",
2851
+ disabled: page <= 1,
2852
+ onClick: () => onChange(page - 1),
2853
+ className: cn(navButtonClasses, "text-tollerud-text-secondary hover:bg-tollerud-surface-hover"),
2854
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 16 })
2855
+ }
2856
+ ),
2857
+ range.map(
2858
+ (item, i) => item === "ellipsis" ? /* @__PURE__ */ jsx("span", { className: "inline-flex h-8 w-8 items-center justify-center text-tollerud-text-muted", children: /* @__PURE__ */ jsx(MoreHorizontal, { size: 16 }) }, `e-${i}`) : /* @__PURE__ */ jsx(
2859
+ "button",
2860
+ {
2861
+ type: "button",
2862
+ "aria-current": item === page ? "page" : void 0,
2863
+ onClick: () => onChange(item),
2864
+ className: cn(
2865
+ navButtonClasses,
2866
+ item === page ? "bg-tollerud-yellow text-tollerud-noir-black font-medium" : "text-tollerud-text-secondary hover:bg-tollerud-surface-hover"
2867
+ ),
2868
+ children: item
2869
+ },
2870
+ item
2871
+ )
2872
+ ),
2873
+ /* @__PURE__ */ jsx(
2874
+ "button",
2875
+ {
2876
+ type: "button",
2877
+ "aria-label": "Next page",
2878
+ disabled: page >= pageCount,
2879
+ onClick: () => onChange(page + 1),
2880
+ className: cn(navButtonClasses, "text-tollerud-text-secondary hover:bg-tollerud-surface-hover"),
2881
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 16 })
2882
+ }
2883
+ )
2884
+ ] });
2885
+ }
2886
+ );
2887
+ Pagination.displayName = "Pagination";
2888
+ var Segmented = forwardRef(
2889
+ ({ className, options, value, onChange, size = "md", ...props }, ref) => {
2890
+ return /* @__PURE__ */ jsx(
2891
+ "div",
2892
+ {
2893
+ ref,
2894
+ role: "radiogroup",
2895
+ className: cn(
2896
+ "inline-flex items-center gap-0.5 rounded-lg p-1 bg-tollerud-surface-raised border border-tollerud-border",
2897
+ className
2898
+ ),
2899
+ ...props,
2900
+ children: options.map((opt) => {
2901
+ const active = opt.value === value;
2902
+ return /* @__PURE__ */ jsx(
2903
+ "button",
2904
+ {
2905
+ type: "button",
2906
+ role: "radio",
2907
+ "aria-checked": active,
2908
+ disabled: opt.disabled,
2909
+ onClick: () => !opt.disabled && onChange(opt.value),
2910
+ className: cn(
2911
+ "rounded-md font-medium transition-colors duration-[150ms]",
2912
+ size === "sm" ? "px-2.5 py-1 text-xs" : "px-3.5 py-1.5 text-sm",
2913
+ active ? "bg-tollerud-yellow text-tollerud-noir-black" : "text-tollerud-text-secondary hover:text-tollerud-text-primary",
2914
+ opt.disabled && "opacity-40 pointer-events-none"
2915
+ ),
2916
+ children: opt.label
2917
+ },
2918
+ opt.value
2919
+ );
2920
+ })
2921
+ }
2922
+ );
2923
+ }
2924
+ );
2925
+ Segmented.displayName = "Segmented";
2926
+ var Stepper = forwardRef(
2927
+ ({ className, steps, current, orientation = "horizontal", ...props }, ref) => {
2928
+ const vertical = orientation === "vertical";
2929
+ return /* @__PURE__ */ jsx(
2930
+ "ol",
2931
+ {
2932
+ ref,
2933
+ className: cn("flex", vertical ? "flex-col gap-0" : "items-start gap-0 w-full", className),
2934
+ ...props,
2935
+ children: steps.map((step, i) => {
2936
+ const status = i < current ? "complete" : i === current ? "active" : "upcoming";
2937
+ const isLast = i === steps.length - 1;
2938
+ return /* @__PURE__ */ jsxs(
2939
+ "li",
2940
+ {
2941
+ className: cn(
2942
+ "flex",
2943
+ vertical ? "flex-row gap-3" : "flex-col flex-1 gap-2",
2944
+ !vertical && !isLast && "pr-2"
2945
+ ),
2946
+ children: [
2947
+ /* @__PURE__ */ jsxs("div", { className: cn("flex items-center", vertical ? "flex-col" : "flex-row gap-2 w-full"), children: [
2948
+ /* @__PURE__ */ jsx(
2949
+ "span",
2950
+ {
2951
+ className: cn(
2952
+ "flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-xs font-medium transition-colors duration-[150ms]",
2953
+ status === "complete" && "bg-tollerud-yellow text-tollerud-noir-black",
2954
+ status === "active" && "border-2 border-tollerud-yellow text-tollerud-yellow",
2955
+ status === "upcoming" && "border border-tollerud-border text-tollerud-text-muted"
2956
+ ),
2957
+ children: status === "complete" ? /* @__PURE__ */ jsx(Check, { size: 14 }) : i + 1
2958
+ }
2959
+ ),
2960
+ !isLast && /* @__PURE__ */ jsx(
2961
+ "span",
2962
+ {
2963
+ "aria-hidden": "true",
2964
+ className: cn(
2965
+ "bg-tollerud-border",
2966
+ vertical ? "w-px flex-1 my-1" : "h-px flex-1",
2967
+ status === "complete" && "bg-tollerud-yellow"
2968
+ )
2969
+ }
2970
+ )
2971
+ ] }),
2972
+ /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", vertical && "pb-6"), children: [
2973
+ /* @__PURE__ */ jsx(
2974
+ "span",
2975
+ {
2976
+ className: cn(
2977
+ "text-sm font-medium",
2978
+ status === "upcoming" ? "text-tollerud-text-muted" : "text-tollerud-text-primary"
2979
+ ),
2980
+ children: step.label
2981
+ }
2982
+ ),
2983
+ step.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-tollerud-text-muted", children: step.description })
2984
+ ] })
2985
+ ]
2986
+ },
2987
+ i
2988
+ );
2989
+ })
2990
+ }
2991
+ );
2992
+ }
2993
+ );
2994
+ Stepper.displayName = "Stepper";
2995
+ var Panel = forwardRef(
2996
+ ({ className, title, description, actions, children, ...props }, ref) => {
2997
+ const hasHeader = title || description || actions;
2998
+ return /* @__PURE__ */ jsxs(
2999
+ "div",
3000
+ {
3001
+ ref,
3002
+ className: cn(
3003
+ "rounded-lg border border-tollerud-border bg-tollerud-surface overflow-hidden",
3004
+ className
3005
+ ),
3006
+ ...props,
3007
+ children: [
3008
+ hasHeader && /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4 px-5 py-4 border-b border-tollerud-border", children: [
3009
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
3010
+ title && /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-tollerud-text-primary", children: title }),
3011
+ description && /* @__PURE__ */ jsx("p", { className: "text-xs text-tollerud-text-muted", children: description })
3012
+ ] }),
3013
+ actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 shrink-0", children: actions })
3014
+ ] }),
3015
+ children && /* @__PURE__ */ jsx("div", { className: "px-5 py-4", children })
3016
+ ]
3017
+ }
3018
+ );
3019
+ }
3020
+ );
3021
+ Panel.displayName = "Panel";
3022
+ var meterTones = {
3023
+ default: "bg-tollerud-yellow",
3024
+ success: "bg-tollerud-success",
3025
+ warning: "bg-tollerud-warning",
3026
+ error: "bg-tollerud-error"
3027
+ };
3028
+ var Meter = forwardRef(
3029
+ ({ className, value, max = 100, label, showValue, tone = "default", ...props }, ref) => {
3030
+ const pct = Math.min(100, Math.max(0, value / max * 100));
3031
+ return /* @__PURE__ */ jsxs("div", { ref, className: cn("flex flex-col gap-1.5", className), ...props, children: [
3032
+ (label || showValue) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs", children: [
3033
+ label && /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary", children: label }),
3034
+ showValue && /* @__PURE__ */ jsxs("span", { className: "text-tollerud-text-muted tabular-nums", children: [
3035
+ Math.round(pct),
3036
+ "%"
3037
+ ] })
3038
+ ] }),
3039
+ /* @__PURE__ */ jsx(
3040
+ "div",
3041
+ {
3042
+ role: "meter",
3043
+ "aria-valuenow": value,
3044
+ "aria-valuemin": 0,
3045
+ "aria-valuemax": max,
3046
+ className: "h-1.5 w-full overflow-hidden rounded-full bg-tollerud-surface-raised",
3047
+ children: /* @__PURE__ */ jsx(
3048
+ "div",
3049
+ {
3050
+ className: cn("h-full rounded-full transition-[width] duration-300", meterTones[tone]),
3051
+ style: { width: `${pct}%` }
3052
+ }
3053
+ )
3054
+ }
3055
+ )
3056
+ ] });
3057
+ }
3058
+ );
3059
+ Meter.displayName = "Meter";
3060
+ var FormRow = forwardRef(
3061
+ ({ className, label, description, error, required, htmlFor, children, ...props }, ref) => {
3062
+ const autoId = useId();
3063
+ const descriptionId = description ? `${autoId}-description` : void 0;
3064
+ const errorId = error ? `${autoId}-error` : void 0;
3065
+ return /* @__PURE__ */ jsxs("div", { ref, className: cn("flex flex-col gap-1.5", className), ...props, children: [
3066
+ label && /* @__PURE__ */ jsxs("label", { htmlFor, className: "text-sm font-medium text-tollerud-text-primary", children: [
3067
+ label,
3068
+ required && /* @__PURE__ */ jsx("span", { className: "ml-0.5 text-tollerud-error", children: "*" })
3069
+ ] }),
3070
+ description && /* @__PURE__ */ jsx("p", { id: descriptionId, className: "text-xs text-tollerud-text-muted", children: description }),
3071
+ /* @__PURE__ */ jsx("div", { "aria-describedby": [descriptionId, errorId].filter(Boolean).join(" ") || void 0, children }),
3072
+ error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-xs text-tollerud-error", children: error })
3073
+ ] });
3074
+ }
3075
+ );
3076
+ FormRow.displayName = "FormRow";
3077
+ var PricingCard = forwardRef(
3078
+ ({
3079
+ className,
3080
+ name,
3081
+ price,
3082
+ period,
3083
+ description,
3084
+ features = [],
3085
+ ctaLabel = "Get started",
3086
+ onCtaClick,
3087
+ featured,
3088
+ badge,
3089
+ ...props
3090
+ }, ref) => {
3091
+ return /* @__PURE__ */ jsxs(
3092
+ "div",
3093
+ {
3094
+ ref,
3095
+ className: cn(
3096
+ "flex flex-col gap-5 rounded-xl border p-6",
3097
+ featured ? "border-tollerud-yellow bg-tollerud-yellow/[0.04] shadow-[0_0_0_1px_rgba(255,255,0,0.15)]" : "border-tollerud-border bg-tollerud-surface",
3098
+ className
3099
+ ),
3100
+ ...props,
3101
+ children: [
3102
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
3103
+ badge && /* @__PURE__ */ jsx("span", { className: "inline-flex w-fit items-center rounded-full bg-tollerud-yellow/15 px-2.5 py-0.5 text-xs font-medium text-tollerud-yellow", children: badge }),
3104
+ /* @__PURE__ */ jsx("h3", { className: "text-base font-medium text-tollerud-text-primary", children: name }),
3105
+ description && /* @__PURE__ */ jsx("p", { className: "text-sm text-tollerud-text-muted", children: description })
3106
+ ] }),
3107
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-1", children: [
3108
+ /* @__PURE__ */ jsx("span", { className: "text-3xl font-semibold tracking-tight text-tollerud-text-primary", children: price }),
3109
+ period && /* @__PURE__ */ jsx("span", { className: "text-sm text-tollerud-text-muted", children: period })
3110
+ ] }),
3111
+ features.length > 0 && /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-2.5", children: features.map((feature, i) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2.5 text-sm text-tollerud-text-secondary", children: [
3112
+ /* @__PURE__ */ jsx(Check, { size: 16, className: "mt-0.5 shrink-0 text-tollerud-yellow" }),
3113
+ /* @__PURE__ */ jsx("span", { children: feature })
3114
+ ] }, i)) }),
3115
+ /* @__PURE__ */ jsx(Button, { variant: featured ? "primary" : "secondary", onClick: onCtaClick, className: "mt-auto w-full", children: ctaLabel })
3116
+ ]
3117
+ }
3118
+ );
3119
+ }
3120
+ );
3121
+ PricingCard.displayName = "PricingCard";
3122
+ var AccordionContext = createContext(null);
3123
+ function useAccordionContext(component) {
3124
+ const ctx = useContext(AccordionContext);
3125
+ if (!ctx) throw new Error(`<${component}> must be used within <Accordion>`);
3126
+ return ctx;
3127
+ }
3128
+ var Accordion = forwardRef(
3129
+ ({ className, multiple = false, defaultOpen, children, ...props }, ref) => {
3130
+ const [openItems, setOpenItems] = useState(() => {
3131
+ if (!defaultOpen) return /* @__PURE__ */ new Set();
3132
+ return new Set(Array.isArray(defaultOpen) ? defaultOpen : [defaultOpen]);
3133
+ });
3134
+ const toggle = (value) => {
3135
+ setOpenItems((prev) => {
3136
+ const next = multiple ? new Set(prev) : /* @__PURE__ */ new Set();
3137
+ if (prev.has(value)) {
3138
+ next.delete(value);
3139
+ } else {
3140
+ next.add(value);
3141
+ }
3142
+ return next;
3143
+ });
3144
+ };
3145
+ return /* @__PURE__ */ jsx(AccordionContext.Provider, { value: { openItems, toggle }, children: /* @__PURE__ */ jsx("div", { ref, className: cn("flex flex-col divide-y divide-tollerud-border rounded-lg border border-tollerud-border", className), ...props, children }) });
3146
+ }
3147
+ );
3148
+ Accordion.displayName = "Accordion";
3149
+ var AccordionItemContext = createContext(null);
3150
+ var AccordionItem = forwardRef(
3151
+ ({ className, value, children, ...props }, ref) => {
3152
+ const { openItems } = useAccordionContext("AccordionItem");
3153
+ const autoId = useId();
3154
+ const open = openItems.has(value);
3155
+ return /* @__PURE__ */ jsx(
3156
+ AccordionItemContext.Provider,
3157
+ {
3158
+ value: { value, open, triggerId: `${autoId}-trigger`, contentId: `${autoId}-content` },
3159
+ children: /* @__PURE__ */ jsx("div", { ref, className: cn("first:rounded-t-lg last:rounded-b-lg", className), ...props, children })
3160
+ }
3161
+ );
3162
+ }
3163
+ );
3164
+ AccordionItem.displayName = "AccordionItem";
3165
+ function useAccordionItemContext(component) {
3166
+ const ctx = useContext(AccordionItemContext);
3167
+ if (!ctx) throw new Error(`<${component}> must be used within <AccordionItem>`);
3168
+ return ctx;
3169
+ }
3170
+ var AccordionTrigger = forwardRef(
3171
+ ({ className, children, ...props }, ref) => {
3172
+ const { toggle } = useAccordionContext("AccordionTrigger");
3173
+ const { value, open, triggerId, contentId } = useAccordionItemContext("AccordionTrigger");
3174
+ return /* @__PURE__ */ jsxs(
3175
+ "button",
3176
+ {
3177
+ ref,
3178
+ id: triggerId,
3179
+ type: "button",
3180
+ "aria-expanded": open,
3181
+ "aria-controls": contentId,
3182
+ onClick: () => toggle(value),
3183
+ className: cn(
3184
+ "flex w-full items-center justify-between gap-4 px-4 py-3.5 text-left text-sm font-medium",
3185
+ "text-tollerud-text-primary hover:bg-tollerud-surface-hover transition-colors duration-[150ms]",
3186
+ className
3187
+ ),
3188
+ ...props,
3189
+ children: [
3190
+ children,
3191
+ /* @__PURE__ */ jsx(
3192
+ ChevronDown,
3193
+ {
3194
+ size: 16,
3195
+ className: cn("shrink-0 text-tollerud-text-muted transition-transform duration-[200ms]", open && "rotate-180")
3196
+ }
3197
+ )
3198
+ ]
3199
+ }
3200
+ );
3201
+ }
3202
+ );
3203
+ AccordionTrigger.displayName = "AccordionTrigger";
3204
+ var AccordionContent = forwardRef(
3205
+ ({ className, children, ...props }, ref) => {
3206
+ const { open, triggerId, contentId } = useAccordionItemContext("AccordionContent");
3207
+ return /* @__PURE__ */ jsx(
3208
+ "div",
3209
+ {
3210
+ ref,
3211
+ id: contentId,
3212
+ role: "region",
3213
+ "aria-labelledby": triggerId,
3214
+ hidden: !open,
3215
+ className: cn("px-4 pb-4 text-sm text-tollerud-text-secondary", className),
3216
+ ...props,
3217
+ children
3218
+ }
3219
+ );
3220
+ }
3221
+ );
3222
+ AccordionContent.displayName = "AccordionContent";
3223
+ var Slider = forwardRef(
3224
+ ({ className, label, showValue, id: idProp, value, defaultValue, min = 0, max = 100, onChange, ...props }, ref) => {
3225
+ const autoId = useId();
3226
+ const id = idProp ?? autoId;
3227
+ const current = value ?? defaultValue ?? min;
3228
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
3229
+ (label || showValue) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs", children: [
3230
+ label && /* @__PURE__ */ jsx("label", { htmlFor: id, className: "font-medium text-tollerud-text-muted", children: label }),
3231
+ showValue && /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary tabular-nums", children: current })
3232
+ ] }),
3233
+ /* @__PURE__ */ jsx(
3234
+ "input",
3235
+ {
3236
+ ref,
3237
+ id,
3238
+ type: "range",
3239
+ min,
3240
+ max,
3241
+ value,
3242
+ defaultValue,
3243
+ onChange: (e) => onChange?.(Number(e.target.value)),
3244
+ className: cn(
3245
+ "h-1.5 w-full cursor-pointer appearance-none rounded-full bg-tollerud-surface-raised accent-tollerud-yellow",
3246
+ "[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4",
3247
+ "[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-tollerud-yellow [&::-webkit-slider-thumb]:cursor-pointer",
3248
+ "[&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-tollerud-noir-black",
3249
+ "[&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:rounded-full",
3250
+ "[&::-moz-range-thumb]:bg-tollerud-yellow [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-tollerud-noir-black [&::-moz-range-thumb]:cursor-pointer",
3251
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-tollerud-yellow/50",
3252
+ "disabled:opacity-40 disabled:pointer-events-none",
3253
+ className
3254
+ ),
3255
+ ...props
3256
+ }
3257
+ )
3258
+ ] });
3259
+ }
3260
+ );
3261
+ Slider.displayName = "Slider";
3262
+ var PasswordInput = forwardRef(
3263
+ ({ className, label, error, id, ...props }, ref) => {
3264
+ const [visible, setVisible] = useState(false);
3265
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
3266
+ label && /* @__PURE__ */ jsx("label", { htmlFor: id, className: "text-xs font-medium text-tollerud-text-muted", children: label }),
3267
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
3268
+ /* @__PURE__ */ jsx(
3269
+ "input",
3270
+ {
3271
+ ref,
3272
+ id,
3273
+ type: visible ? "text" : "password",
3274
+ className: cn(
3275
+ "w-full font-sans text-base px-3 py-2 pr-10 rounded",
3276
+ "bg-tollerud-surface-raised border",
3277
+ "text-tollerud-text-primary",
3278
+ "placeholder:text-tollerud-text-muted",
3279
+ "transition-[border-color] duration-[150ms]",
3280
+ "focus:outline-none focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_#E8D500]",
3281
+ error ? "border-tollerud-error" : "border-tollerud-border",
3282
+ className
3283
+ ),
3284
+ ...props
3285
+ }
3286
+ ),
3287
+ /* @__PURE__ */ jsx(
3288
+ "button",
3289
+ {
3290
+ type: "button",
3291
+ onClick: () => setVisible((v) => !v),
3292
+ "aria-label": visible ? "Hide password" : "Show password",
3293
+ "aria-pressed": visible,
3294
+ className: "absolute right-2.5 top-1/2 -translate-y-1/2 text-tollerud-text-muted hover:text-tollerud-text-secondary transition-colors duration-[150ms]",
3295
+ children: visible ? /* @__PURE__ */ jsx(EyeOff, { size: 16 }) : /* @__PURE__ */ jsx(Eye, { size: 16 })
3296
+ }
3297
+ )
3298
+ ] }),
3299
+ error && /* @__PURE__ */ jsx("p", { className: "text-xs text-tollerud-error mt-0.5", children: error })
3300
+ ] });
3301
+ }
3302
+ );
3303
+ PasswordInput.displayName = "PasswordInput";
3304
+ var defaultFilter = (option, query) => option.label.toLowerCase().includes(query.toLowerCase());
3305
+ function Combobox({
3306
+ options,
3307
+ value: valueProp,
3308
+ defaultValue,
3309
+ onChange,
3310
+ placeholder = "Search\u2026",
3311
+ label,
3312
+ error,
3313
+ filter = defaultFilter,
3314
+ className,
3315
+ disabled
3316
+ }) {
3317
+ const id = useId();
3318
+ const rootRef = useRef(null);
3319
+ const isControlled = valueProp !== void 0;
3320
+ const [internalValue, setInternalValue] = useState(defaultValue);
3321
+ const value = isControlled ? valueProp : internalValue;
3322
+ const [open, setOpen] = useState(false);
3323
+ const [query, setQuery] = useState("");
3324
+ const [activeIndex, setActiveIndex] = useState(0);
3325
+ const selected = options.find((o) => o.value === value);
3326
+ const filtered = useMemo(() => {
3327
+ if (!query) return options;
3328
+ return options.filter((o) => filter(o, query));
3329
+ }, [options, query, filter]);
3330
+ useEffect(() => {
3331
+ if (!open) return;
3332
+ function onClickOutside(e) {
3333
+ if (rootRef.current && !rootRef.current.contains(e.target)) {
3334
+ setOpen(false);
3335
+ setQuery("");
3336
+ }
3337
+ }
3338
+ function onResize() {
3339
+ setOpen(false);
3340
+ setQuery("");
3341
+ }
3342
+ document.addEventListener("mousedown", onClickOutside);
3343
+ window.addEventListener("resize", onResize);
3344
+ return () => {
3345
+ document.removeEventListener("mousedown", onClickOutside);
3346
+ window.removeEventListener("resize", onResize);
3347
+ };
3348
+ }, [open]);
3349
+ const commit = (option) => {
3350
+ if (option.disabled) return;
3351
+ if (!isControlled) setInternalValue(option.value);
3352
+ onChange?.(option.value);
3353
+ setOpen(false);
3354
+ setQuery("");
3355
+ };
3356
+ const onKeyDown = (e) => {
3357
+ if (e.key === "ArrowDown") {
3358
+ e.preventDefault();
3359
+ setOpen(true);
3360
+ setActiveIndex((i) => Math.min(i + 1, filtered.length - 1));
3361
+ } else if (e.key === "ArrowUp") {
3362
+ e.preventDefault();
3363
+ setActiveIndex((i) => Math.max(i - 1, 0));
3364
+ } else if (e.key === "Enter") {
3365
+ e.preventDefault();
3366
+ const opt = filtered[activeIndex];
3367
+ if (opt) commit(opt);
3368
+ } else if (e.key === "Escape") {
3369
+ setOpen(false);
3370
+ setQuery("");
3371
+ }
3372
+ };
3373
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("relative flex flex-col gap-1", className), children: [
3374
+ label && /* @__PURE__ */ jsx("label", { htmlFor: id, className: "text-xs font-medium text-tollerud-text-muted", children: label }),
3375
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
3376
+ /* @__PURE__ */ jsx(
3377
+ "input",
3378
+ {
3379
+ id,
3380
+ role: "combobox",
3381
+ "aria-expanded": open,
3382
+ "aria-autocomplete": "list",
3383
+ "aria-controls": `${id}-listbox`,
3384
+ disabled,
3385
+ value: open ? query : selected?.label ?? "",
3386
+ placeholder: selected ? selected.label : placeholder,
3387
+ onFocus: () => {
3388
+ setOpen(true);
3389
+ setActiveIndex(0);
3390
+ },
3391
+ onChange: (e) => {
3392
+ setQuery(e.target.value);
3393
+ setActiveIndex(0);
3394
+ if (!open) setOpen(true);
3395
+ },
3396
+ onKeyDown,
3397
+ className: cn(
3398
+ "w-full font-sans text-base px-3 py-2 pr-9 rounded",
3399
+ "bg-tollerud-surface-raised border",
3400
+ "text-tollerud-text-primary placeholder:text-tollerud-text-muted",
3401
+ "transition-[border-color] duration-[150ms]",
3402
+ "focus:outline-none focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_#E8D500]",
3403
+ error ? "border-tollerud-error" : "border-tollerud-border",
3404
+ disabled && "opacity-50 pointer-events-none"
3405
+ )
3406
+ }
3407
+ ),
3408
+ /* @__PURE__ */ jsx(
3409
+ ChevronDown,
3410
+ {
3411
+ size: 15,
3412
+ className: cn(
3413
+ "pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-tollerud-text-muted transition-transform duration-[150ms]",
3414
+ open && "rotate-180"
3415
+ )
3416
+ }
3417
+ )
3418
+ ] }),
3419
+ open && /* @__PURE__ */ jsxs(
3420
+ "ul",
3421
+ {
3422
+ id: `${id}-listbox`,
3423
+ role: "listbox",
3424
+ className: "absolute top-full z-20 mt-1 max-h-64 w-full overflow-auto rounded-lg border border-tollerud-border bg-tollerud-surface-overlay py-1 shadow-lg",
3425
+ children: [
3426
+ filtered.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-3 py-2 text-sm text-tollerud-text-muted", children: "No results" }),
3427
+ filtered.map((option, i) => {
3428
+ const isSelected = option.value === value;
3429
+ return /* @__PURE__ */ jsxs(
3430
+ "li",
3431
+ {
3432
+ role: "option",
3433
+ "aria-selected": isSelected,
3434
+ onMouseDown: (e) => {
3435
+ e.preventDefault();
3436
+ commit(option);
3437
+ },
3438
+ onMouseEnter: () => setActiveIndex(i),
3439
+ className: cn(
3440
+ "flex items-center justify-between gap-2 px-3 py-2 text-sm cursor-pointer",
3441
+ i === activeIndex ? "bg-tollerud-surface-hover text-tollerud-text-primary" : "text-tollerud-text-secondary",
3442
+ option.disabled && "opacity-40 pointer-events-none"
3443
+ ),
3444
+ children: [
3445
+ option.label,
3446
+ isSelected && /* @__PURE__ */ jsx(Check, { size: 14, className: "text-tollerud-yellow" })
3447
+ ]
3448
+ },
3449
+ option.value
3450
+ );
3451
+ })
3452
+ ]
3453
+ }
3454
+ ),
3455
+ error && /* @__PURE__ */ jsx("p", { className: "text-xs text-tollerud-error mt-0.5", children: error })
3456
+ ] });
3457
+ }
3458
+ Combobox.displayName = "Combobox";
3459
+ var defaultFormat = (date) => date.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
3460
+ function isSameDay(a, b) {
3461
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
3462
+ }
3463
+ function startOfMonth(date) {
3464
+ return new Date(date.getFullYear(), date.getMonth(), 1);
3465
+ }
3466
+ function buildCalendarGrid(monthDate) {
3467
+ const first = startOfMonth(monthDate);
3468
+ const startWeekday = first.getDay();
3469
+ const daysInMonth = new Date(monthDate.getFullYear(), monthDate.getMonth() + 1, 0).getDate();
3470
+ const cells = [];
3471
+ for (let i = 0; i < startWeekday; i++) cells.push(null);
3472
+ for (let d = 1; d <= daysInMonth; d++) cells.push(new Date(monthDate.getFullYear(), monthDate.getMonth(), d));
3473
+ return cells;
3474
+ }
3475
+ var WEEKDAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
3476
+ function DatePicker({
3477
+ value: valueProp,
3478
+ defaultValue = null,
3479
+ onChange,
3480
+ label,
3481
+ error,
3482
+ placeholder = "Select a date",
3483
+ formatDate = defaultFormat,
3484
+ className,
3485
+ disabled
3486
+ }) {
3487
+ const id = useId();
3488
+ const rootRef = useRef(null);
3489
+ const isControlled = valueProp !== void 0;
3490
+ const [internalValue, setInternalValue] = useState(defaultValue);
3491
+ const value = isControlled ? valueProp ?? null : internalValue;
3492
+ const [open, setOpen] = useState(false);
3493
+ const [viewMonth, setViewMonth] = useState(() => startOfMonth(value ?? /* @__PURE__ */ new Date()));
3494
+ const cells = useMemo(() => buildCalendarGrid(viewMonth), [viewMonth]);
3495
+ useEffect(() => {
3496
+ if (!open) return;
3497
+ function onClickOutside(e) {
3498
+ if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
3499
+ }
3500
+ function onResize() {
3501
+ setOpen(false);
3502
+ }
3503
+ document.addEventListener("mousedown", onClickOutside);
3504
+ window.addEventListener("resize", onResize);
3505
+ return () => {
3506
+ document.removeEventListener("mousedown", onClickOutside);
3507
+ window.removeEventListener("resize", onResize);
3508
+ };
3509
+ }, [open]);
3510
+ const select = (date) => {
3511
+ if (!isControlled) setInternalValue(date);
3512
+ onChange?.(date);
3513
+ setOpen(false);
3514
+ };
3515
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("relative flex flex-col gap-1", className), children: [
3516
+ label && /* @__PURE__ */ jsx("label", { htmlFor: id, className: "text-xs font-medium text-tollerud-text-muted", children: label }),
3517
+ /* @__PURE__ */ jsxs(
3518
+ "button",
3519
+ {
3520
+ id,
3521
+ type: "button",
3522
+ disabled,
3523
+ onClick: () => {
3524
+ setViewMonth(startOfMonth(value ?? /* @__PURE__ */ new Date()));
3525
+ setOpen((o) => !o);
3526
+ },
3527
+ "aria-haspopup": "dialog",
3528
+ "aria-expanded": open,
3529
+ className: cn(
3530
+ "flex w-full items-center justify-between gap-2 rounded px-3 py-2 text-left text-base",
3531
+ "bg-tollerud-surface-raised border",
3532
+ "transition-[border-color] duration-[150ms]",
3533
+ "focus:outline-none focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_#E8D500]",
3534
+ error ? "border-tollerud-error" : "border-tollerud-border",
3535
+ value ? "text-tollerud-text-primary" : "text-tollerud-text-muted",
3536
+ disabled && "opacity-50 pointer-events-none"
3537
+ ),
3538
+ children: [
3539
+ /* @__PURE__ */ jsx("span", { children: value ? formatDate(value) : placeholder }),
3540
+ /* @__PURE__ */ jsx(Calendar, { size: 15, className: "text-tollerud-text-muted" })
3541
+ ]
3542
+ }
3543
+ ),
3544
+ open && /* @__PURE__ */ jsxs(
3545
+ "div",
3546
+ {
3547
+ role: "dialog",
3548
+ "aria-label": "Choose date",
3549
+ className: "absolute top-full z-20 mt-1 w-72 rounded-lg border border-tollerud-border bg-tollerud-surface-overlay p-3 shadow-lg",
3550
+ children: [
3551
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
3552
+ /* @__PURE__ */ jsx(
3553
+ "button",
3554
+ {
3555
+ type: "button",
3556
+ "aria-label": "Previous month",
3557
+ onClick: () => setViewMonth((m) => new Date(m.getFullYear(), m.getMonth() - 1, 1)),
3558
+ className: "rounded p-1 text-tollerud-text-secondary hover:bg-tollerud-surface-hover",
3559
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 16 })
3560
+ }
3561
+ ),
3562
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-tollerud-text-primary", children: viewMonth.toLocaleDateString(void 0, { month: "long", year: "numeric" }) }),
3563
+ /* @__PURE__ */ jsx(
3564
+ "button",
3565
+ {
3566
+ type: "button",
3567
+ "aria-label": "Next month",
3568
+ onClick: () => setViewMonth((m) => new Date(m.getFullYear(), m.getMonth() + 1, 1)),
3569
+ className: "rounded p-1 text-tollerud-text-secondary hover:bg-tollerud-surface-hover",
3570
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 16 })
3571
+ }
3572
+ )
3573
+ ] }),
3574
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-7 gap-1 text-center", children: [
3575
+ WEEKDAYS.map((d) => /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-tollerud-text-muted py-1", children: d }, d)),
3576
+ cells.map((date, i) => {
3577
+ if (!date) return /* @__PURE__ */ jsx("span", {}, i);
3578
+ const selected = value ? isSameDay(date, value) : false;
3579
+ const today = isSameDay(date, /* @__PURE__ */ new Date());
3580
+ return /* @__PURE__ */ jsx(
3581
+ "button",
3582
+ {
3583
+ type: "button",
3584
+ onClick: () => select(date),
3585
+ className: cn(
3586
+ "h-8 w-8 rounded-full text-sm transition-colors duration-[150ms]",
3587
+ selected ? "bg-tollerud-yellow text-tollerud-noir-black font-medium" : "text-tollerud-text-secondary hover:bg-tollerud-surface-hover",
3588
+ !selected && today && "ring-1 ring-tollerud-yellow/40"
3589
+ ),
3590
+ children: date.getDate()
3591
+ },
3592
+ i
3593
+ );
3594
+ })
3595
+ ] })
3596
+ ]
3597
+ }
3598
+ ),
3599
+ error && /* @__PURE__ */ jsx("p", { className: "text-xs text-tollerud-error mt-0.5", children: error })
3600
+ ] });
3601
+ }
3602
+ DatePicker.displayName = "DatePicker";
3603
+ function formatBytes(bytes) {
3604
+ if (bytes < 1024) return `${bytes} B`;
3605
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
3606
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
3607
+ }
3608
+ function FileUpload({
3609
+ label,
3610
+ description,
3611
+ error,
3612
+ accept,
3613
+ multiple,
3614
+ onFilesChange,
3615
+ className,
3616
+ disabled
3617
+ }) {
3618
+ const id = useId();
3619
+ const inputRef = useRef(null);
3620
+ const [files, setFiles] = useState([]);
3621
+ const [dragging, setDragging] = useState(false);
3622
+ const setAndNotify = (next) => {
3623
+ setFiles(next);
3624
+ onFilesChange?.(next);
3625
+ };
3626
+ const addFiles = (incoming) => {
3627
+ if (!incoming || incoming.length === 0) return;
3628
+ const incomingArr = Array.from(incoming);
3629
+ setAndNotify(multiple ? [...files, ...incomingArr] : [incomingArr[0]]);
3630
+ };
3631
+ const removeAt = (index) => {
3632
+ setAndNotify(files.filter((_, i) => i !== index));
3633
+ };
3634
+ const onDrop = (e) => {
3635
+ e.preventDefault();
3636
+ setDragging(false);
3637
+ if (disabled) return;
3638
+ addFiles(e.dataTransfer.files);
3639
+ };
3640
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-1.5", className), children: [
3641
+ label && /* @__PURE__ */ jsx("label", { htmlFor: id, className: "text-xs font-medium text-tollerud-text-muted", children: label }),
3642
+ /* @__PURE__ */ jsxs(
3643
+ "div",
3644
+ {
3645
+ role: "button",
3646
+ tabIndex: disabled ? -1 : 0,
3647
+ "aria-disabled": disabled,
3648
+ onClick: () => !disabled && inputRef.current?.click(),
3649
+ onKeyDown: (e) => {
3650
+ if (!disabled && (e.key === "Enter" || e.key === " ")) {
3651
+ e.preventDefault();
3652
+ inputRef.current?.click();
3653
+ }
3654
+ },
3655
+ onDragOver: (e) => {
3656
+ e.preventDefault();
3657
+ if (!disabled) setDragging(true);
3658
+ },
3659
+ onDragLeave: () => setDragging(false),
3660
+ onDrop,
3661
+ className: cn(
3662
+ "flex flex-col items-center justify-center gap-2 rounded-lg border border-dashed px-6 py-8 text-center cursor-pointer transition-colors duration-[150ms]",
3663
+ dragging ? "border-tollerud-yellow bg-tollerud-yellow/[0.06]" : "border-tollerud-border bg-tollerud-surface-raised hover:border-tollerud-text-secondary",
3664
+ error && "border-tollerud-error",
3665
+ disabled && "opacity-50 pointer-events-none"
3666
+ ),
3667
+ children: [
3668
+ /* @__PURE__ */ jsx(Upload, { size: 20, className: "text-tollerud-text-muted" }),
3669
+ /* @__PURE__ */ jsxs("div", { className: "text-sm text-tollerud-text-secondary", children: [
3670
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-tollerud-yellow", children: "Click to upload" }),
3671
+ " or drag and drop"
3672
+ ] }),
3673
+ description && /* @__PURE__ */ jsx("p", { className: "text-xs text-tollerud-text-muted", children: description }),
3674
+ /* @__PURE__ */ jsx(
3675
+ "input",
3676
+ {
3677
+ ref: inputRef,
3678
+ id,
3679
+ type: "file",
3680
+ accept,
3681
+ multiple,
3682
+ disabled,
3683
+ onChange: (e) => addFiles(e.target.files),
3684
+ className: "sr-only"
3685
+ }
3686
+ )
3687
+ ]
3688
+ }
3689
+ ),
3690
+ files.length > 0 && /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-1.5", children: files.map((file, i) => /* @__PURE__ */ jsxs(
3691
+ "li",
3692
+ {
3693
+ className: "flex items-center gap-2.5 rounded-md border border-tollerud-border bg-tollerud-surface-raised px-3 py-2 text-sm",
3694
+ children: [
3695
+ /* @__PURE__ */ jsx(File, { size: 15, className: "shrink-0 text-tollerud-text-muted" }),
3696
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-tollerud-text-primary", children: file.name }),
3697
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-tollerud-text-muted", children: formatBytes(file.size) }),
3698
+ /* @__PURE__ */ jsx(
3699
+ "button",
3700
+ {
3701
+ type: "button",
3702
+ "aria-label": `Remove ${file.name}`,
3703
+ onClick: () => removeAt(i),
3704
+ className: "shrink-0 text-tollerud-text-muted hover:text-tollerud-text-primary transition-colors duration-[150ms]",
3705
+ children: /* @__PURE__ */ jsx(X, { size: 14 })
3706
+ }
3707
+ )
3708
+ ]
3709
+ },
3710
+ `${file.name}-${i}`
3711
+ )) }),
3712
+ error && /* @__PURE__ */ jsx("p", { className: "text-xs text-tollerud-error", children: error })
3713
+ ] });
3714
+ }
3715
+ FileUpload.displayName = "FileUpload";
3716
+ function TagInput({
3717
+ value: valueProp,
3718
+ defaultValue = [],
3719
+ onChange,
3720
+ label,
3721
+ error,
3722
+ placeholder = "Add a tag\u2026",
3723
+ max,
3724
+ className,
3725
+ disabled
3726
+ }) {
3727
+ const id = useId();
3728
+ const isControlled = valueProp !== void 0;
3729
+ const [internalTags, setInternalTags] = useState(defaultValue);
3730
+ const tags = isControlled ? valueProp : internalTags;
3731
+ const [draft, setDraft] = useState("");
3732
+ const setAndNotify = (next) => {
3733
+ if (!isControlled) setInternalTags(next);
3734
+ onChange?.(next);
3735
+ };
3736
+ const addTag = (raw) => {
3737
+ const tag = raw.trim();
3738
+ if (!tag || tags.includes(tag)) return;
3739
+ if (max && tags.length >= max) return;
3740
+ setAndNotify([...tags, tag]);
3741
+ setDraft("");
3742
+ };
3743
+ const removeTag = (index) => {
3744
+ setAndNotify(tags.filter((_, i) => i !== index));
3745
+ };
3746
+ const onKeyDown = (e) => {
3747
+ if (e.key === "Enter" || e.key === ",") {
3748
+ e.preventDefault();
3749
+ addTag(draft);
3750
+ } else if (e.key === "Backspace" && draft === "" && tags.length > 0) {
3751
+ removeTag(tags.length - 1);
3752
+ }
3753
+ };
3754
+ const atMax = max ? tags.length >= max : false;
3755
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-1", className), children: [
3756
+ label && /* @__PURE__ */ jsx("label", { htmlFor: id, className: "text-xs font-medium text-tollerud-text-muted", children: label }),
3757
+ /* @__PURE__ */ jsxs(
3758
+ "div",
3759
+ {
3760
+ className: cn(
3761
+ "flex flex-wrap items-center gap-1.5 rounded px-2.5 py-1.5",
3762
+ "bg-tollerud-surface-raised border",
3763
+ "transition-[border-color] duration-[150ms]",
3764
+ "focus-within:border-tollerud-yellow focus-within:shadow-[0_0_0_1px_#E8D500]",
3765
+ error ? "border-tollerud-error" : "border-tollerud-border",
3766
+ disabled && "opacity-50 pointer-events-none"
3767
+ ),
3768
+ children: [
3769
+ tags.map((tag, i) => /* @__PURE__ */ jsxs(
3770
+ "span",
3771
+ {
3772
+ className: "inline-flex items-center gap-1 rounded-full bg-tollerud-surface-hover px-2.5 py-0.5 text-xs font-medium text-tollerud-text-primary",
3773
+ children: [
3774
+ tag,
3775
+ /* @__PURE__ */ jsx(
3776
+ "button",
3777
+ {
3778
+ type: "button",
3779
+ "aria-label": `Remove ${tag}`,
3780
+ onClick: () => removeTag(i),
3781
+ className: "text-tollerud-text-muted hover:text-tollerud-text-primary transition-colors duration-[150ms]",
3782
+ children: /* @__PURE__ */ jsx(X, { size: 12 })
3783
+ }
3784
+ )
3785
+ ]
3786
+ },
3787
+ `${tag}-${i}`
3788
+ )),
3789
+ /* @__PURE__ */ jsx(
3790
+ "input",
3791
+ {
3792
+ id,
3793
+ value: draft,
3794
+ disabled: disabled || atMax,
3795
+ onChange: (e) => setDraft(e.target.value),
3796
+ onKeyDown,
3797
+ onBlur: () => addTag(draft),
3798
+ placeholder: atMax ? "" : placeholder,
3799
+ className: cn(
3800
+ "min-w-[6rem] flex-1 bg-transparent py-0.5 text-sm text-tollerud-text-primary",
3801
+ "placeholder:text-tollerud-text-muted focus:outline-none"
3802
+ )
3803
+ }
3804
+ )
3805
+ ]
3806
+ }
3807
+ ),
3808
+ error && /* @__PURE__ */ jsx("p", { className: "text-xs text-tollerud-error mt-0.5", children: error })
3809
+ ] });
3810
+ }
3811
+ TagInput.displayName = "TagInput";
2653
3812
 
2654
- export { ActionDiff, ActionRow, Alert, AlertInbox, ApprovalCard, BackupStatusPanel, Badge, BentoDashboard, Button, Card, Checkbox, CodeBlock, CommandMenu, Container, DataTable, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DockerStackCard, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyIcon, EmptyTitle, Footer, GlowCard, HostCard, IncidentCard, Input, Kbd, LogViewer, NoirGlowBackground, Progress, Radio, RadioGroup, RollbackPlan, Select, ServiceHealthCard, Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger, Skeleton, StatCard, StatusDot, Switch, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Timeline, Toaster, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, buttonVariants, cn };
3813
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, ActionDiff, ActionRow, Alert, AlertInbox, ApprovalCard, Avatar, AvatarGroup, BackupStatusPanel, Badge, BentoDashboard, Breadcrumb, Button, Card, Checkbox, CodeBlock, Combobox, CommandMenu, Container, DataTable, DatePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Divider, DockerStackCard, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyIcon, EmptyTitle, FileUpload, Footer, FormRow, GlowCard, HostCard, IncidentCard, Input, Kbd, LogViewer, Meter, NoirGlowBackground, Pagination, Panel, PasswordInput, Pill, PricingCard, Progress, Radio, RadioGroup, RollbackPlan, Segmented, Select, ServiceHealthCard, Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger, Skeleton, Slider, StatCard, StatusDot, Stepper, Switch, Tabs, TabsContent, TabsList, TabsTrigger, TagInput, Textarea, Timeline, Toaster, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, buttonVariants, cn };
2655
3814
  //# sourceMappingURL=index.js.map
2656
3815
  //# sourceMappingURL=index.js.map