@weichenlin/swell-ui 0.2.0 → 0.3.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.
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
- import { Check, ChevronDown, CircleAlert, CircleCheck, CircleDashed, CircleX, Copy, Eye, EyeOff, Info, LoaderCircle, Menu, Moon, Search, Sun, TriangleAlert, X } from "lucide-react";
1
+ import { ArrowDown, ArrowUp, ArrowUpDown, Check, ChevronDown, ChevronLeft, ChevronRight, CircleAlert, CircleCheck, CircleDashed, CircleX, Copy, Eye, EyeOff, Info, LoaderCircle, Menu, Moon, Search, Sun, TriangleAlert, X } from "lucide-react";
2
2
  import { clsx } from "clsx";
3
3
  import { extendTailwindMerge } from "tailwind-merge";
4
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
4
  import { cva } from "class-variance-authority";
6
5
  import { Children, cloneElement, createContext, isValidElement, useCallback, useContext, useEffect, useId, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
7
6
  import { createPortal } from "react-dom";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
8
  //#region src/lib/cn.ts
9
9
  const tone = (name) => [
10
10
  name,
@@ -45,6 +45,8 @@ const twMerge = extendTailwindMerge({ extend: { theme: {
45
45
  "input-halo-invalid-focus",
46
46
  "control-checked",
47
47
  "control-checked-fg",
48
+ "switch-handle",
49
+ "switch-handle-checked",
48
50
  "popover-bg",
49
51
  "popover-border",
50
52
  "popover-highlight",
@@ -76,67 +78,6 @@ function cn(...inputs) {
76
78
  return twMerge(clsx(inputs));
77
79
  }
78
80
  //#endregion
79
- //#region src/components/badge/badge.tsx
80
- const toneClasses$1 = {
81
- neutral: {
82
- subtle: ["border-neutral-border bg-neutral-subtle-bg text-neutral-subtle-fg", "text-neutral"],
83
- outline: ["border-neutral-border bg-transparent text-neutral-subtle-fg", "text-neutral"],
84
- solid: ["border-neutral bg-neutral text-neutral-fg", "text-neutral-fg"]
85
- },
86
- info: {
87
- subtle: ["border-info-border bg-info-subtle-bg text-info-subtle-fg", "text-info"],
88
- outline: ["border-info-border bg-transparent text-info-subtle-fg", "text-info"],
89
- solid: ["border-info bg-info text-info-fg", "text-info-fg"]
90
- },
91
- success: {
92
- subtle: ["border-success-border bg-success-subtle-bg text-success-subtle-fg", "text-success"],
93
- outline: ["border-success-border bg-transparent text-success-subtle-fg", "text-success"],
94
- solid: ["border-success bg-success text-success-fg", "text-success-fg"]
95
- },
96
- warning: {
97
- subtle: ["border-warning-border bg-warning-subtle-bg text-warning-subtle-fg", "text-warning"],
98
- outline: ["border-warning-border bg-transparent text-warning-subtle-fg", "text-warning"],
99
- solid: ["border-warning bg-warning text-warning-fg", "text-warning-fg"]
100
- },
101
- danger: {
102
- subtle: ["border-danger-border bg-danger-subtle-bg text-danger-subtle-fg", "text-danger"],
103
- outline: ["border-danger-border bg-transparent text-danger-subtle-fg", "text-danger"],
104
- solid: ["border-danger bg-danger text-danger-fg", "text-danger-fg"]
105
- }
106
- };
107
- const sizeClasses$3 = {
108
- sm: {
109
- base: "h-5 px-1.5",
110
- withIcon: "ps-1"
111
- },
112
- md: {
113
- base: "h-6 px-2",
114
- withIcon: "ps-1.5"
115
- }
116
- };
117
- function Badge({ tone = "neutral", variant = "subtle", size = "md", icon, busy = false, context, className, children, ...props }) {
118
- const [toneClass, iconClass] = toneClasses$1[tone][variant];
119
- const hasIcon = busy || icon !== void 0 && icon !== null && icon !== false;
120
- return /* @__PURE__ */ jsxs("span", {
121
- ...props,
122
- "data-slot": "badge",
123
- className: cn("inline-flex items-center gap-1 rounded-md border text-xs leading-none font-medium whitespace-nowrap", "forced-colors:border-[CanvasText]", sizeClasses$3[size].base, hasIcon && sizeClasses$3[size].withIcon, toneClass, className),
124
- children: [hasIcon && /* @__PURE__ */ jsx("span", {
125
- "aria-hidden": "true",
126
- "data-slot": "badge-icon",
127
- className: cn("inline-flex shrink-0 items-center [&_svg]:size-3 [&_svg]:shrink-0", iconClass),
128
- children: busy ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "animate-spin motion-reduce:hidden" }), /* @__PURE__ */ jsx(CircleDashed, { className: "hidden motion-reduce:block" })] }) : icon
129
- }), /* @__PURE__ */ jsxs("span", {
130
- "data-slot": "badge-label",
131
- className: "min-w-0 truncate leading-4",
132
- children: [context && /* @__PURE__ */ jsxs("span", {
133
- className: "sr-only",
134
- children: [context, ": "]
135
- }), children]
136
- })]
137
- });
138
- }
139
- //#endregion
140
81
  //#region src/hooks/use-press-feedback.ts
141
82
  const GROW_MS = 450;
142
83
  const FADE_MS = 300;
@@ -221,6 +162,61 @@ function usePressFeedback(ripple = true) {
221
162
  return ref;
222
163
  }
223
164
  //#endregion
165
+ //#region src/lib/slot.tsx
166
+ function setRef(ref, node) {
167
+ if (typeof ref === "function") return ref(node);
168
+ if (ref) ref.current = node;
169
+ }
170
+ function composeRefs(...refs) {
171
+ return (node) => {
172
+ const cleanups = refs.map((ref) => setRef(ref, node));
173
+ return () => {
174
+ for (const [i, ref] of refs.entries()) {
175
+ const cleanup = cleanups[i];
176
+ if (typeof cleanup === "function") cleanup();
177
+ else setRef(ref, null);
178
+ }
179
+ };
180
+ };
181
+ }
182
+ function mergeProps(slot, child) {
183
+ const merged = {
184
+ ...slot,
185
+ ...child
186
+ };
187
+ for (const key of Object.keys(slot)) {
188
+ const own = slot[key];
189
+ const theirs = child[key];
190
+ if (/^on[A-Z]/.test(key) && typeof own === "function" && typeof theirs === "function") merged[key] = (...args) => {
191
+ theirs(...args);
192
+ own(...args);
193
+ };
194
+ else if (key === "className") merged.className = cn(own, theirs);
195
+ else if (key === "style") merged.style = {
196
+ ...own,
197
+ ...theirs
198
+ };
199
+ }
200
+ return merged;
201
+ }
202
+ /** Applies `wrap` to an element's children, or to what a render-prop child returns. */
203
+ function wrapChildren(children, wrap) {
204
+ if (typeof children !== "function") return wrap(children);
205
+ return (...args) => wrap(children(...args));
206
+ }
207
+ /** Renders its only child with these props merged in (Radix Slot style). */
208
+ function Slot({ children, ref, ...props }) {
209
+ const child = Children.only(children);
210
+ const childProps = isValidElement(child) ? child.props : {};
211
+ const childRef = childProps.ref;
212
+ const composedRef = useMemo(() => composeRefs(ref, childRef), [ref, childRef]);
213
+ if (!isValidElement(child)) return null;
214
+ return cloneElement(child, {
215
+ ...mergeProps(props, childProps),
216
+ ref: composedRef
217
+ });
218
+ }
219
+ //#endregion
224
220
  //#region src/components/tooltip.tsx
225
221
  const OPEN_DELAY_MS = 300;
226
222
  const GAP_PX = 6;
@@ -514,22 +510,56 @@ function stateClass(disabled, loading) {
514
510
  if (loading) return "cursor-progress opacity-70";
515
511
  if (disabled) return "cursor-not-allowed opacity-50 forced-colors:opacity-100 forced-colors:text-[GrayText] forced-colors:after:border-[GrayText]";
516
512
  }
517
- function ButtonImpl({ className, intent, size, tone, fullWidth, square, loading = false, loadingText = "Loading", disabled = false, iconLeft, iconRight, ripple, tooltip, type = "button", onClick, children, ...props }) {
513
+ function blockActivation(e) {
514
+ e.preventDefault();
515
+ e.stopPropagation();
516
+ }
517
+ function ButtonImpl({ asChild = false, className, intent, size, tone, fullWidth, square, loading = false, loadingText = "Loading", disabled = false, iconLeft, iconRight, ripple, tooltip, type = "button", onClick, onClickCapture, onAuxClickCapture, children, ...props }) {
518
518
  const s = size ?? "md";
519
519
  const inert = disabled || loading;
520
+ const classes = cn(buttonVariants({
521
+ intent,
522
+ size,
523
+ tone,
524
+ fullWidth,
525
+ square
526
+ }), iconPadding(s, iconLeft, iconRight), stateClass(disabled, loading), className);
527
+ const content = (inner) => /* @__PURE__ */ jsx(ButtonContent, {
528
+ size: s,
529
+ ripple: ripple ?? true,
530
+ loading,
531
+ iconLeft,
532
+ iconRight,
533
+ children: inner
534
+ });
535
+ if (asChild) {
536
+ const child = Children.only(children);
537
+ if (!isValidElement(child)) return null;
538
+ const link = /* @__PURE__ */ jsx(Slot, {
539
+ ...props,
540
+ "data-slot": "button",
541
+ "aria-disabled": disabled || void 0,
542
+ className: classes,
543
+ onClick,
544
+ onClickCapture: disabled ? blockActivation : onClickCapture,
545
+ onAuxClickCapture: disabled ? blockActivation : onAuxClickCapture,
546
+ children: cloneElement(child, { children: wrapChildren(child.props.children, content) })
547
+ });
548
+ return tooltip ? /* @__PURE__ */ jsx(Tooltip, {
549
+ ...tooltip,
550
+ className: fullWidth ? "flex w-full" : void 0,
551
+ children: link
552
+ }) : link;
553
+ }
520
554
  const button = /* @__PURE__ */ jsx("button", {
521
555
  ...props,
522
556
  type,
523
557
  "data-slot": "button",
524
558
  "aria-disabled": inert || void 0,
525
559
  "aria-busy": loading || void 0,
526
- className: cn(buttonVariants({
527
- intent,
528
- size,
529
- tone,
530
- fullWidth,
531
- square
532
- }), iconPadding(s, iconLeft, iconRight), stateClass(disabled, loading), className),
560
+ className: classes,
561
+ onClickCapture,
562
+ onAuxClickCapture,
533
563
  onClick: (e) => {
534
564
  if (inert) {
535
565
  e.preventDefault();
@@ -537,14 +567,7 @@ function ButtonImpl({ className, intent, size, tone, fullWidth, square, loading
537
567
  }
538
568
  onClick?.(e);
539
569
  },
540
- children: /* @__PURE__ */ jsx(ButtonContent, {
541
- size: s,
542
- ripple: ripple ?? true,
543
- loading,
544
- iconLeft,
545
- iconRight,
546
- children
547
- })
570
+ children: content(children)
548
571
  });
549
572
  return /* @__PURE__ */ jsxs(Fragment, { children: [tooltip ? /* @__PURE__ */ jsx(Tooltip, {
550
573
  ...tooltip,
@@ -557,7 +580,7 @@ function ButtonImpl({ className, intent, size, tone, fullWidth, square, loading
557
580
  children: loading ? loadingText : ""
558
581
  })] });
559
582
  }
560
- function IconOnlyButton({ icon, tooltip = true, tooltipSide, disabledReason, className, size, ...props }) {
583
+ function IconOnlyButton({ icon, tooltip = true, tooltipSide, disabledReason, className, size, children, ...props }) {
561
584
  const reason = props.disabled ? disabledReason : void 0;
562
585
  const tip = reason ? {
563
586
  content: reason,
@@ -567,13 +590,14 @@ function IconOnlyButton({ icon, tooltip = true, tooltipSide, disabledReason, cla
567
590
  side: tooltipSide,
568
591
  describe: false
569
592
  } : disabledReason !== void 0 ? { content: void 0 } : void 0;
593
+ const child = Children.toArray(children)[0];
570
594
  return /* @__PURE__ */ jsx(ButtonImpl, {
571
595
  ...props,
572
596
  size,
573
597
  tooltip: tip,
574
598
  square: true,
575
599
  className,
576
- children: icon
600
+ children: props.asChild && isValidElement(child) ? cloneElement(child, void 0, icon) : icon
577
601
  });
578
602
  }
579
603
  function Button(props) {
@@ -586,6 +610,135 @@ function Button(props) {
586
610
  });
587
611
  }
588
612
  //#endregion
613
+ //#region src/components/alert/alert.tsx
614
+ const tones = {
615
+ neutral: {
616
+ Icon: Info,
617
+ iconClass: "text-neutral"
618
+ },
619
+ info: {
620
+ Icon: Info,
621
+ iconClass: "text-info"
622
+ },
623
+ success: {
624
+ Icon: CircleCheck,
625
+ iconClass: "text-success"
626
+ },
627
+ warning: {
628
+ Icon: TriangleAlert,
629
+ iconClass: "text-warning"
630
+ },
631
+ danger: {
632
+ Icon: CircleAlert,
633
+ iconClass: "text-danger"
634
+ }
635
+ };
636
+ /** A persistent message shown next to the content it describes. Use a live region for updates that need announcing. */
637
+ function Alert({ tone = "neutral", title, action, onDismiss, dismissLabel = "Dismiss alert", className, children, ...props }) {
638
+ const { Icon, iconClass } = tones[tone];
639
+ return /* @__PURE__ */ jsxs("div", {
640
+ ...props,
641
+ "data-slot": "alert",
642
+ "data-tone": tone,
643
+ className: cn("@container/alert flex items-start gap-3 rounded-xl border border-ring-deep px-4 py-3", "text-sm text-fg forced-colors:border-[CanvasText]", className),
644
+ children: [/* @__PURE__ */ jsxs("div", {
645
+ className: cn("grid min-w-0 flex-1 grid-cols-[1rem_minmax(0,1fr)] items-start gap-x-2.5 gap-y-3", action && "@md/alert:grid-cols-[1rem_minmax(0,1fr)_auto]"),
646
+ children: [
647
+ /* @__PURE__ */ jsx(Icon, {
648
+ "aria-hidden": "true",
649
+ "data-slot": "alert-icon",
650
+ className: cn("mt-0.5 size-4", iconClass)
651
+ }),
652
+ /* @__PURE__ */ jsxs("div", {
653
+ className: "min-w-0 space-y-0.5 wrap-anywhere",
654
+ children: [title != null && /* @__PURE__ */ jsx("div", {
655
+ "data-slot": "alert-title",
656
+ className: "font-medium",
657
+ children: title
658
+ }), children != null && /* @__PURE__ */ jsx("div", {
659
+ "data-slot": "alert-description",
660
+ className: title != null ? "text-fg-muted" : void 0,
661
+ children
662
+ })]
663
+ }),
664
+ action && /* @__PURE__ */ jsx("div", {
665
+ "data-slot": "alert-action",
666
+ className: "col-start-2 justify-self-start @md/alert:col-start-3 @md/alert:self-center",
667
+ children: action
668
+ })
669
+ ]
670
+ }), onDismiss && /* @__PURE__ */ jsx(Button, {
671
+ "aria-label": dismissLabel,
672
+ className: "-my-1.5 -me-1.5 shrink-0 text-fg-muted @md/alert:self-center",
673
+ icon: /* @__PURE__ */ jsx(X, {}),
674
+ intent: "ghost",
675
+ size: "sm",
676
+ onClick: onDismiss
677
+ })]
678
+ });
679
+ }
680
+ //#endregion
681
+ //#region src/components/badge/badge.tsx
682
+ const toneClasses$1 = {
683
+ neutral: {
684
+ subtle: ["border-neutral-border bg-neutral-subtle-bg text-neutral-subtle-fg", "text-neutral"],
685
+ outline: ["border-neutral-border bg-transparent text-neutral-subtle-fg", "text-neutral"],
686
+ solid: ["border-neutral bg-neutral text-neutral-fg", "text-neutral-fg"]
687
+ },
688
+ info: {
689
+ subtle: ["border-info-border bg-info-subtle-bg text-info-subtle-fg", "text-info"],
690
+ outline: ["border-info-border bg-transparent text-info-subtle-fg", "text-info"],
691
+ solid: ["border-info bg-info text-info-fg", "text-info-fg"]
692
+ },
693
+ success: {
694
+ subtle: ["border-success-border bg-success-subtle-bg text-success-subtle-fg", "text-success"],
695
+ outline: ["border-success-border bg-transparent text-success-subtle-fg", "text-success"],
696
+ solid: ["border-success bg-success text-success-fg", "text-success-fg"]
697
+ },
698
+ warning: {
699
+ subtle: ["border-warning-border bg-warning-subtle-bg text-warning-subtle-fg", "text-warning"],
700
+ outline: ["border-warning-border bg-transparent text-warning-subtle-fg", "text-warning"],
701
+ solid: ["border-warning bg-warning text-warning-fg", "text-warning-fg"]
702
+ },
703
+ danger: {
704
+ subtle: ["border-danger-border bg-danger-subtle-bg text-danger-subtle-fg", "text-danger"],
705
+ outline: ["border-danger-border bg-transparent text-danger-subtle-fg", "text-danger"],
706
+ solid: ["border-danger bg-danger text-danger-fg", "text-danger-fg"]
707
+ }
708
+ };
709
+ const sizeClasses$3 = {
710
+ sm: {
711
+ base: "h-5 px-1.5",
712
+ withIcon: "ps-1"
713
+ },
714
+ md: {
715
+ base: "h-6 px-2",
716
+ withIcon: "ps-1.5"
717
+ }
718
+ };
719
+ function Badge({ tone = "neutral", variant = "subtle", size = "md", icon, busy = false, context, className, children, ...props }) {
720
+ const [toneClass, iconClass] = toneClasses$1[tone][variant];
721
+ const hasIcon = busy || icon !== void 0 && icon !== null && icon !== false;
722
+ return /* @__PURE__ */ jsxs("span", {
723
+ ...props,
724
+ "data-slot": "badge",
725
+ className: cn("inline-flex items-center gap-1 rounded-md border text-xs leading-none font-medium whitespace-nowrap", "forced-colors:border-[CanvasText]", sizeClasses$3[size].base, hasIcon && sizeClasses$3[size].withIcon, toneClass, className),
726
+ children: [hasIcon && /* @__PURE__ */ jsx("span", {
727
+ "aria-hidden": "true",
728
+ "data-slot": "badge-icon",
729
+ className: cn("inline-flex shrink-0 items-center [&_svg]:size-3 [&_svg]:shrink-0", iconClass),
730
+ children: busy ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "animate-spin motion-reduce:hidden" }), /* @__PURE__ */ jsx(CircleDashed, { className: "hidden motion-reduce:block" })] }) : icon
731
+ }), /* @__PURE__ */ jsxs("span", {
732
+ "data-slot": "badge-label",
733
+ className: "min-w-0 truncate leading-4",
734
+ children: [context && /* @__PURE__ */ jsxs("span", {
735
+ className: "sr-only",
736
+ children: [context, ": "]
737
+ }), children]
738
+ })]
739
+ });
740
+ }
741
+ //#endregion
589
742
  //#region src/components/card/card.tsx
590
743
  const CardContext = createContext({
591
744
  titleId: "",
@@ -1802,61 +1955,6 @@ function Select({ options, placeholder, helperText, error, size = "md", start, m
1802
1955
  });
1803
1956
  }
1804
1957
  //#endregion
1805
- //#region src/lib/slot.tsx
1806
- function setRef(ref, node) {
1807
- if (typeof ref === "function") return ref(node);
1808
- if (ref) ref.current = node;
1809
- }
1810
- function composeRefs(...refs) {
1811
- return (node) => {
1812
- const cleanups = refs.map((ref) => setRef(ref, node));
1813
- return () => {
1814
- for (const [i, ref] of refs.entries()) {
1815
- const cleanup = cleanups[i];
1816
- if (typeof cleanup === "function") cleanup();
1817
- else setRef(ref, null);
1818
- }
1819
- };
1820
- };
1821
- }
1822
- function mergeProps(slot, child) {
1823
- const merged = {
1824
- ...slot,
1825
- ...child
1826
- };
1827
- for (const key of Object.keys(slot)) {
1828
- const own = slot[key];
1829
- const theirs = child[key];
1830
- if (/^on[A-Z]/.test(key) && typeof own === "function" && typeof theirs === "function") merged[key] = (...args) => {
1831
- theirs(...args);
1832
- own(...args);
1833
- };
1834
- else if (key === "className") merged.className = cn(own, theirs);
1835
- else if (key === "style") merged.style = {
1836
- ...own,
1837
- ...theirs
1838
- };
1839
- }
1840
- return merged;
1841
- }
1842
- /** Applies `wrap` to an element's children, or to what a render-prop child returns. */
1843
- function wrapChildren(children, wrap) {
1844
- if (typeof children !== "function") return wrap(children);
1845
- return (...args) => wrap(children(...args));
1846
- }
1847
- /** Renders its only child with these props merged in (Radix Slot style). */
1848
- function Slot({ children, ref, ...props }) {
1849
- const child = Children.only(children);
1850
- const childProps = isValidElement(child) ? child.props : {};
1851
- const childRef = childProps.ref;
1852
- const composedRef = useMemo(() => composeRefs(ref, childRef), [ref, childRef]);
1853
- if (!isValidElement(child)) return null;
1854
- return cloneElement(child, {
1855
- ...mergeProps(props, childProps),
1856
- ref: composedRef
1857
- });
1858
- }
1859
- //#endregion
1860
1958
  //#region src/components/tabs/shared.tsx
1861
1959
  const listBase = [
1862
1960
  "relative isolate max-w-full overflow-x-auto overscroll-x-contain scroll-px-6",
@@ -2432,13 +2530,240 @@ function Switch({ label, description, error, size = "md", loading = false, label
2432
2530
  ], "checked:focus-visible:[--switch-ring:transparent] checked:focus-visible:[--switch-halo:transparent]", "checked:focus-visible:outline-solid checked:focus-visible:outline-2 checked:focus-visible:outline-offset-2", row.invalid && "not-forced-colors:checked:focus-visible:outline-danger", "forced-colors:forced-color-adjust-none forced-colors:[box-shadow:none]", "forced-colors:border-[CanvasText] forced-colors:bg-[Canvas]", "forced-colors:checked:border-[Highlight] forced-colors:checked:bg-[Highlight]", "forced-colors:disabled:border-[GrayText] forced-colors:disabled:checked:bg-[GrayText]", "forced-colors:focus-visible:outline-[CanvasText]", inputClassName)
2433
2531
  }), /* @__PURE__ */ jsx("span", {
2434
2532
  "aria-hidden": true,
2435
- className: cn("pointer-events-none absolute inset-s-0.5 top-0.5 grid place-items-center rounded-full", "h-(--switch-thumb) w-(--switch-thumb-w) bg-control-checked-fg text-control-checked shadow-switch-thumb", "[--switch-dir:1] rtl:[--switch-dir:-1] [translate:calc(var(--switch-x)*var(--switch-dir))_0]", "[--switch-x:0px] [--switch-thumb-w:var(--switch-thumb)] peer-checked:[--switch-x:var(--switch-travel)]", "transition-[translate,width] duration-150 ease-in-out", pressable && ["peer-active:[--switch-thumb-w:calc(var(--switch-thumb)+var(--switch-stretch))]", "peer-checked:peer-active:[--switch-x:calc(var(--switch-travel)-var(--switch-stretch))]"], "peer-disabled:shadow-none", "forced-colors:forced-color-adjust-none forced-colors:shadow-none", "forced-colors:bg-[CanvasText] forced-colors:text-[Canvas]", "forced-colors:peer-checked:bg-[HighlightText] forced-colors:peer-checked:text-[Highlight]", "forced-colors:peer-disabled:bg-[GrayText] forced-colors:peer-disabled:peer-checked:bg-[Canvas]", s.icon),
2533
+ className: cn("pointer-events-none absolute inset-s-0.5 top-0.5 grid place-items-center rounded-full", "h-(--switch-thumb) w-(--switch-thumb-w) bg-switch-handle peer-checked:bg-switch-handle-checked text-control-checked shadow-switch-thumb", "[--switch-dir:1] rtl:[--switch-dir:-1] [translate:calc(var(--switch-x)*var(--switch-dir))_0]", "[--switch-x:0px] [--switch-thumb-w:var(--switch-thumb)] peer-checked:[--switch-x:var(--switch-travel)]", "transition-[translate,width] duration-150 ease-in-out", pressable && ["peer-active:[--switch-thumb-w:calc(var(--switch-thumb)+var(--switch-stretch))]", "peer-checked:peer-active:[--switch-x:calc(var(--switch-travel)-var(--switch-stretch))]"], "peer-disabled:shadow-none", "forced-colors:forced-color-adjust-none forced-colors:shadow-none", "forced-colors:bg-[CanvasText] forced-colors:text-[Canvas]", "forced-colors:peer-checked:bg-[HighlightText] forced-colors:peer-checked:text-[Highlight]", "forced-colors:peer-disabled:bg-[GrayText] forced-colors:peer-disabled:peer-checked:bg-[Canvas]", s.icon),
2436
2534
  children: loading && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "animate-spin motion-reduce:hidden" }), /* @__PURE__ */ jsx(CircleDashed, { className: "hidden motion-reduce:block" })] })
2437
2535
  })]
2438
2536
  })
2439
2537
  });
2440
2538
  }
2441
2539
  //#endregion
2540
+ //#region src/components/table/table.tsx
2541
+ const labels = {
2542
+ search: "Search rows",
2543
+ clearSelection: "Clear selection",
2544
+ selectPage: "Select all rows on this page",
2545
+ selectRow: (id) => `Select row ${id}`,
2546
+ selectionCount: (count) => `${count} selected`,
2547
+ scrollRegion: "Table",
2548
+ pagination: "Pagination",
2549
+ previousPage: "Previous page",
2550
+ nextPage: "Next page",
2551
+ page: (page, count) => `${page} / ${count}`,
2552
+ range: (first, last, total) => total === 0 ? "0 rows" : `${first}–${last} of ${total}`,
2553
+ emptyState: "No rows yet",
2554
+ noResults: "No matching rows"
2555
+ };
2556
+ const alignClasses = {
2557
+ start: "text-start",
2558
+ center: "text-center",
2559
+ end: "text-end"
2560
+ };
2561
+ const sortIcons = {
2562
+ asc: ArrowUp,
2563
+ desc: ArrowDown
2564
+ };
2565
+ function Table({ data, columns, getRowId, title, emptyState, noResults, filtered = false, search, sorting, pagination, selection, className, tableClassName, labels: labelOverrides, ...props }) {
2566
+ const titleId = useId();
2567
+ const scrollRef = useRef(null);
2568
+ const searchRef = useRef(null);
2569
+ const emptyRef = useRef(null);
2570
+ const text = {
2571
+ ...labels,
2572
+ ...labelOverrides
2573
+ };
2574
+ const tableName = props["aria-labelledby"] ?? (title != null && !props["aria-label"] ? titleId : void 0);
2575
+ const showEmpty = data.length === 0 && !filtered && !search?.value.trim() && (!pagination || pagination.rowCount === 0);
2576
+ const showSearch = !showEmpty && search != null;
2577
+ const hasControls = showSearch || !!selection?.ids.size;
2578
+ const hasToolbar = title != null || hasControls;
2579
+ const rows = data.map((row) => ({
2580
+ row,
2581
+ id: getRowId(row),
2582
+ canSelect: selection?.canSelectRow?.(row) ?? true
2583
+ }));
2584
+ const selectableIds = selection ? rows.filter((row) => row.canSelect).map((row) => row.id) : [];
2585
+ const selectedOnPage = selectableIds.filter((id) => selection?.ids.has(id)).length;
2586
+ const allOnPageSelected = selectableIds.length > 0 && selectedOnPage === selectableIds.length;
2587
+ const updateSelection = (ids, checked) => {
2588
+ if (!selection) return;
2589
+ const next = new Set(selection.ids);
2590
+ for (const id of ids) if (checked) next.add(id);
2591
+ else next.delete(id);
2592
+ selection.onChange(next);
2593
+ };
2594
+ return /* @__PURE__ */ jsxs("div", {
2595
+ "data-slot": "table-root",
2596
+ className: cn("w-full min-w-0 bg-surface text-fg", className),
2597
+ children: [hasToolbar && /* @__PURE__ */ jsxs("div", {
2598
+ "data-slot": "table-toolbar",
2599
+ className: "flex flex-wrap items-center justify-between gap-3 border-b border-border px-4 py-3",
2600
+ children: [title != null && /* @__PURE__ */ jsx("span", {
2601
+ id: titleId,
2602
+ className: "text-sm font-semibold",
2603
+ children: title
2604
+ }), hasControls && /* @__PURE__ */ jsxs("div", {
2605
+ className: "ms-auto flex min-w-0 flex-wrap items-center justify-end gap-x-3 gap-y-2",
2606
+ children: [
2607
+ selection && /* @__PURE__ */ jsx("output", {
2608
+ className: "text-xs text-fg-muted",
2609
+ children: selection.ids.size > 0 && text.selectionCount(selection.ids.size)
2610
+ }),
2611
+ !!selection?.ids.size && /* @__PURE__ */ jsx(Button, {
2612
+ intent: "ghost",
2613
+ size: "sm",
2614
+ ripple: false,
2615
+ onClick: () => {
2616
+ selection.onChange(/* @__PURE__ */ new Set());
2617
+ (searchRef.current ?? scrollRef.current ?? emptyRef.current)?.focus();
2618
+ },
2619
+ children: text.clearSelection
2620
+ }),
2621
+ showSearch && /* @__PURE__ */ jsx(SearchInput, {
2622
+ ref: searchRef,
2623
+ "aria-label": text.search,
2624
+ placeholder: `${text.search}…`,
2625
+ size: "sm",
2626
+ className: "w-48 max-w-full",
2627
+ value: search.value,
2628
+ onChange: (event) => search.onChange(event.currentTarget.value)
2629
+ })
2630
+ ]
2631
+ })]
2632
+ }), showEmpty ? /* @__PURE__ */ jsx("div", {
2633
+ ref: emptyRef,
2634
+ "data-slot": "table-empty",
2635
+ tabIndex: -1,
2636
+ className: "grid min-h-48 place-items-center px-4 py-8 text-center text-sm text-fg-muted outline-hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus",
2637
+ children: emptyState === void 0 ? text.emptyState : emptyState
2638
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("section", {
2639
+ ref: scrollRef,
2640
+ "data-slot": "table-scroll",
2641
+ "aria-labelledby": tableName,
2642
+ "aria-label": tableName ? void 0 : props["aria-label"] ?? text.scrollRegion,
2643
+ tabIndex: 0,
2644
+ className: "relative w-full overflow-x-auto outline-hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus",
2645
+ children: /* @__PURE__ */ jsxs("table", {
2646
+ ...props,
2647
+ "aria-labelledby": tableName,
2648
+ "data-slot": "table",
2649
+ className: cn("w-full border-separate border-spacing-0 text-sm", tableClassName),
2650
+ children: [/* @__PURE__ */ jsx("thead", {
2651
+ "data-slot": "table-header",
2652
+ children: /* @__PURE__ */ jsxs("tr", { children: [selection && /* @__PURE__ */ jsx("th", {
2653
+ scope: "col",
2654
+ className: "w-12 border-b border-border px-3",
2655
+ children: /* @__PURE__ */ jsx(Checkbox, {
2656
+ "aria-label": text.selectPage,
2657
+ checked: allOnPageSelected,
2658
+ indeterminate: selectedOnPage > 0 && !allOnPageSelected,
2659
+ disabled: selectableIds.length === 0,
2660
+ onChange: (event) => updateSelection(selectableIds, event.currentTarget.checked),
2661
+ className: "mx-auto justify-center"
2662
+ })
2663
+ }), columns.map((column) => {
2664
+ const direction = sorting?.value?.id === column.id ? sorting.value.direction : void 0;
2665
+ const SortIcon = direction ? sortIcons[direction] : ArrowUpDown;
2666
+ const align = column.align ?? (column.numeric ? "end" : "start");
2667
+ return /* @__PURE__ */ jsx("th", {
2668
+ scope: "col",
2669
+ "aria-sort": direction ? direction === "asc" ? "ascending" : "descending" : void 0,
2670
+ className: cn("h-10 border-b border-border px-4 text-xs font-medium whitespace-nowrap text-fg-muted", alignClasses[align], column.headerClassName),
2671
+ children: sorting && column.sortable ? /* @__PURE__ */ jsxs("button", {
2672
+ type: "button",
2673
+ className: cn("inline-flex min-h-8 cursor-pointer items-center gap-1.5 rounded-sm hover:text-fg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus pointer-coarse:min-h-11", direction && "text-fg"),
2674
+ onClick: () => sorting.onChange(direction === "desc" ? null : {
2675
+ id: column.id,
2676
+ direction: direction === "asc" ? "desc" : "asc"
2677
+ }),
2678
+ children: [column.header, /* @__PURE__ */ jsx(SortIcon, {
2679
+ "aria-hidden": true,
2680
+ className: "size-3.5 shrink-0"
2681
+ })]
2682
+ }) : column.header
2683
+ }, column.id);
2684
+ })] })
2685
+ }), /* @__PURE__ */ jsxs("tbody", {
2686
+ "data-slot": "table-body",
2687
+ className: "[&>tr:not(:last-child)>*]:border-b [&>tr>*]:border-border",
2688
+ children: [rows.map(({ row, id, canSelect }) => {
2689
+ const selected = selection?.ids.has(id) ?? false;
2690
+ return /* @__PURE__ */ jsxs("tr", {
2691
+ "data-slot": "table-row",
2692
+ "data-selected": selected || void 0,
2693
+ className: "hover:bg-surface-muted data-selected:bg-surface-muted",
2694
+ children: [selection && /* @__PURE__ */ jsx("td", {
2695
+ className: "w-12 px-3",
2696
+ children: /* @__PURE__ */ jsx(Checkbox, {
2697
+ "aria-label": text.selectRow(id),
2698
+ checked: selected,
2699
+ disabled: !canSelect,
2700
+ onChange: (event) => updateSelection([id], event.currentTarget.checked),
2701
+ className: "mx-auto justify-center"
2702
+ })
2703
+ }), columns.map((column) => {
2704
+ const Cell = column.rowHeader ? "th" : "td";
2705
+ const align = column.align ?? (column.numeric ? "end" : "start");
2706
+ return /* @__PURE__ */ jsx(Cell, {
2707
+ scope: column.rowHeader ? "row" : void 0,
2708
+ className: cn("h-13 px-4 py-2 align-middle", alignClasses[align], column.numeric && "tabular-nums", column.rowHeader && "font-medium", column.className),
2709
+ children: column.cell(row)
2710
+ }, column.id);
2711
+ })]
2712
+ }, id);
2713
+ }), rows.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
2714
+ colSpan: Math.max(1, columns.length + Number(Boolean(selection))),
2715
+ children: /* @__PURE__ */ jsx("div", {
2716
+ "data-slot": "table-no-results",
2717
+ className: "sticky inset-s-0 w-max max-w-[min(100%,18rem)] px-4 py-12 text-sm text-fg-muted",
2718
+ children: noResults === void 0 ? /* @__PURE__ */ jsx("output", { children: text.noResults }) : noResults
2719
+ })
2720
+ }) })]
2721
+ })]
2722
+ })
2723
+ }), pagination && /* @__PURE__ */ jsx(TablePagination, {
2724
+ ...pagination,
2725
+ visibleCount: rows.length,
2726
+ text
2727
+ })] })]
2728
+ });
2729
+ }
2730
+ function TablePagination({ pageIndex, pageSize, rowCount, visibleCount, onPageChange, text }) {
2731
+ const count = Math.ceil(rowCount / pageSize);
2732
+ const first = visibleCount ? pageIndex * pageSize + 1 : 0;
2733
+ const last = first ? first + visibleCount - 1 : 0;
2734
+ const canGoBack = pageIndex > 0;
2735
+ const canGoForward = pageIndex + 1 < count;
2736
+ return /* @__PURE__ */ jsxs("nav", {
2737
+ "data-slot": "table-pagination",
2738
+ "aria-label": text.pagination,
2739
+ className: "flex min-h-12 flex-wrap items-center justify-between gap-x-3 gap-y-1 border-t border-border px-4 py-1 text-xs text-fg-muted tabular-nums",
2740
+ children: [/* @__PURE__ */ jsx("output", { children: text.range(first, last, rowCount) }), /* @__PURE__ */ jsxs("span", {
2741
+ className: "flex items-center gap-2",
2742
+ children: [
2743
+ /* @__PURE__ */ jsx(Button, {
2744
+ icon: /* @__PURE__ */ jsx(ChevronLeft, { className: "rtl:rotate-180" }),
2745
+ "aria-label": text.previousPage,
2746
+ intent: "ghost",
2747
+ size: "sm",
2748
+ ripple: false,
2749
+ disabled: !canGoBack,
2750
+ onClick: () => onPageChange(pageIndex - 1)
2751
+ }),
2752
+ /* @__PURE__ */ jsx("span", { children: count === 0 ? "—" : text.page(pageIndex + 1, count) }),
2753
+ /* @__PURE__ */ jsx(Button, {
2754
+ icon: /* @__PURE__ */ jsx(ChevronRight, { className: "rtl:rotate-180" }),
2755
+ "aria-label": text.nextPage,
2756
+ intent: "ghost",
2757
+ size: "sm",
2758
+ ripple: false,
2759
+ disabled: !canGoForward,
2760
+ onClick: () => onPageChange(pageIndex + 1)
2761
+ })
2762
+ ]
2763
+ })]
2764
+ });
2765
+ }
2766
+ //#endregion
2442
2767
  //#region src/components/tabs/tab-nav.tsx
2443
2768
  const TabNavContext = createContext(null);
2444
2769
  /**
@@ -3450,4 +3775,4 @@ function ToastCard({ record, layout, total, expanded, paused, frontHeight, ownHe
3450
3775
  });
3451
3776
  }
3452
3777
  //#endregion
3453
- export { Badge, Button, Card, CardContent, CardHeader, Checkbox, CheckboxGroup, CopyInput, Dialog, DialogBody, DialogFooter, DialogHeader, Drawer, DrawerBody, DrawerClose, DrawerFooter, DrawerHeader, Input, Meter, PasswordInput, SearchInput, Select, Sidebar, SidebarBadge, SidebarBody, SidebarFooter, SidebarGroup, SidebarHeader, SidebarIcon, SidebarItem, SidebarProvider, SidebarTrigger, Switch, Tab, TabList, TabNav, TabNavLink, TabPanel, Tabs, ThemeSwitcher, Toaster, Tooltip, cn, toast, useTheme };
3778
+ export { Alert, Badge, Button, Card, CardContent, CardHeader, Checkbox, CheckboxGroup, CopyInput, Dialog, DialogBody, DialogFooter, DialogHeader, Drawer, DrawerBody, DrawerClose, DrawerFooter, DrawerHeader, Input, Meter, PasswordInput, SearchInput, Select, Sidebar, SidebarBadge, SidebarBody, SidebarFooter, SidebarGroup, SidebarHeader, SidebarIcon, SidebarItem, SidebarProvider, SidebarTrigger, Switch, Tab, TabList, TabNav, TabNavLink, TabPanel, Table, Tabs, ThemeSwitcher, Toaster, Tooltip, cn, toast, useTheme };