@seekalfred/primitives 0.5.0 → 0.5.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.
Files changed (92) hide show
  1. package/dist/accordion/accordion.d.ts +5 -5
  2. package/dist/accordion/accordion.js +13 -8
  3. package/dist/accordion/accordion.js.map +1 -1
  4. package/dist/alert/alert.d.ts +1 -1
  5. package/dist/alert/alert.js +4 -2
  6. package/dist/alert/alert.js.map +1 -1
  7. package/dist/alert-dialog/alert-dialog.d.ts +1 -1
  8. package/dist/alert-dialog/alert-dialog.js +4 -2
  9. package/dist/alert-dialog/alert-dialog.js.map +1 -1
  10. package/dist/avatar/avatar.d.ts +1 -1
  11. package/dist/avatar/avatar.js +6 -4
  12. package/dist/avatar/avatar.js.map +1 -1
  13. package/dist/badge/badge.d.ts +3 -3
  14. package/dist/badge/badge.js +4 -2
  15. package/dist/badge/badge.js.map +1 -1
  16. package/dist/breadcrumb/breadcrumb.d.ts +3 -3
  17. package/dist/breadcrumb/breadcrumb.js +7 -4
  18. package/dist/breadcrumb/breadcrumb.js.map +1 -1
  19. package/dist/button/button.d.ts +3 -3
  20. package/dist/button/button.js +4 -2
  21. package/dist/button/button.js.map +1 -1
  22. package/dist/calendar/calendar.d.ts +2 -2
  23. package/dist/calendar/calendar.js +7 -5
  24. package/dist/calendar/calendar.js.map +1 -1
  25. package/dist/card/card.d.ts +6 -6
  26. package/dist/card/card.js +19 -12
  27. package/dist/card/card.js.map +1 -1
  28. package/dist/checkbox/checkbox.d.ts +2 -2
  29. package/dist/checkbox/checkbox.js +4 -2
  30. package/dist/checkbox/checkbox.js.map +1 -1
  31. package/dist/dialog/dialog.d.ts +3 -3
  32. package/dist/dialog/dialog.js +7 -4
  33. package/dist/dialog/dialog.js.map +1 -1
  34. package/dist/input/input.d.ts +1 -1
  35. package/dist/input/input.js +1 -1
  36. package/dist/label/label.d.ts +1 -1
  37. package/dist/label/label.js +4 -2
  38. package/dist/label/label.js.map +1 -1
  39. package/dist/lib/merge-refs.js +22 -0
  40. package/dist/lib/merge-refs.js.map +1 -0
  41. package/dist/menu/menu.d.ts +5 -5
  42. package/dist/menu/menu.js +10 -6
  43. package/dist/menu/menu.js.map +1 -1
  44. package/dist/popover/popover.d.ts +2 -2
  45. package/dist/popover/popover.js +4 -2
  46. package/dist/popover/popover.js.map +1 -1
  47. package/dist/progress/progress.d.ts +2 -2
  48. package/dist/progress/progress.js +4 -2
  49. package/dist/progress/progress.js.map +1 -1
  50. package/dist/radio-group/radio-group.d.ts +3 -3
  51. package/dist/radio-group/radio-group.js +7 -4
  52. package/dist/radio-group/radio-group.js.map +1 -1
  53. package/dist/scroll-area/scroll-area.d.ts +7 -6
  54. package/dist/scroll-area/scroll-area.js +6 -4
  55. package/dist/scroll-area/scroll-area.js.map +1 -1
  56. package/dist/select/select.d.ts +3 -3
  57. package/dist/select/select.js +7 -4
  58. package/dist/select/select.js.map +1 -1
  59. package/dist/separator/separator.d.ts +1 -1
  60. package/dist/separator/separator.js +4 -2
  61. package/dist/separator/separator.js.map +1 -1
  62. package/dist/sheet/sheet.d.ts +1 -1
  63. package/dist/sheet/sheet.js +4 -2
  64. package/dist/sheet/sheet.js.map +1 -1
  65. package/dist/skeleton/skeleton.d.ts +1 -1
  66. package/dist/skeleton/skeleton.js +4 -2
  67. package/dist/skeleton/skeleton.js.map +1 -1
  68. package/dist/slider/slider.d.ts +2 -2
  69. package/dist/slider/slider.js +4 -2
  70. package/dist/slider/slider.js.map +1 -1
  71. package/dist/spinner/spinner.d.ts +1 -1
  72. package/dist/spinner/spinner.js +4 -2
  73. package/dist/spinner/spinner.js.map +1 -1
  74. package/dist/switch/switch.d.ts +2 -2
  75. package/dist/switch/switch.js +4 -2
  76. package/dist/switch/switch.js.map +1 -1
  77. package/dist/table/table.d.ts +4 -4
  78. package/dist/table/table.js +4 -3
  79. package/dist/table/table.js.map +1 -1
  80. package/dist/tabs/tabs.d.ts +4 -4
  81. package/dist/tabs/tabs.js +13 -8
  82. package/dist/tabs/tabs.js.map +1 -1
  83. package/dist/textarea/textarea.d.ts +1 -1
  84. package/dist/textarea/textarea.js +4 -2
  85. package/dist/textarea/textarea.js.map +1 -1
  86. package/dist/toast/toast.d.ts +2 -2
  87. package/dist/toast/toast.js +4 -2
  88. package/dist/toast/toast.js.map +1 -1
  89. package/dist/tooltip/tooltip.d.ts +2 -2
  90. package/dist/tooltip/tooltip.js +4 -2
  91. package/dist/tooltip/tooltip.js.map +1 -1
  92. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"radio-group.js","names":["RadioGroup","AriaRadioGroup","Radio","AriaRadio"],"sources":["../../src/radio-group/radio-group.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport type { ReactNode } from \"react\";\nimport {\n Radio as AriaRadio,\n RadioGroup as AriaRadioGroup,\n type RadioGroupProps as AriaRadioGroupProps,\n type RadioProps as AriaRadioProps,\n} from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { selectionLabelVariants, selectionRingVariants } from \"../lib/selection-control.js\";\n\n/**\n * RadioGroup, ported from the legacy shadcn/Radix component onto React Aria.\n *\n * Census: 1 file, 3 uses -- `RadioGroup` 1, `RadioGroupItem` 2. The\n * smallest real surface in the migration, so the port is deliberately plain.\n *\n * THE LABEL MOVES INSIDE THE ITEM. Radix's `RadioGroupItem` is a bare\n * button and the caller pairs it with a separate `<Label htmlFor>`; React\n * Aria's `Radio` takes its label as `children` and wires the association\n * itself. That is the one rename a call site has to make, and it removes\n * the id bookkeeping.\n *\n * `border-primary` becomes `border-border` for the same reason it did on\n * Checkbox: brand orange is 2.44:1 on white and cannot legally be the only\n * thing describing a control's edge (WCAG 1.4.11). Selected reuses\n * Checkbox's exact pair -- `bg-primary` with a `primary-strong` boundary --\n * so a radio and a checkbox in the same form agree.\n */\nexport const radioGroupVariants = cva([\n \"flex flex-col gap-2\",\n \"box-border font-sans\",\n]);\n\nexport const radioVariants = cva([\n \"inline-flex size-4 shrink-0 items-center justify-center\",\n \"rounded-full border-(length:--border-width) box-border\",\n \"bg-background border-border\",\n \"motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard\",\n \"group-data-[selected]:bg-primary group-data-[selected]:border-primary-strong\",\n \"group-data-[selected]:text-primary-foreground\",\n selectionRingVariants(),\n]);\n\n/**\n * A set of mutually exclusive choices.\n *\n * @example\n * <RadioGroup value={plan} onChange={setPlan} aria-label=\"Plan\">\n * <Radio value=\"free\">Free</Radio>\n * <Radio value=\"pro\">Pro</Radio>\n * </RadioGroup>\n */\nexport interface RadioGroupProps extends Omit<AriaRadioGroupProps, \"className\" | \"style\"> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport function RadioGroup({ className, ...props }: RadioGroupProps) {\n return (\n <AriaRadioGroup\n {...props}\n data-slot=\"radio-group\"\n className={cn(radioGroupVariants(), className)}\n />\n );\n}\n\n/**\n * One choice.\n *\n * @example\n * <Radio value=\"pro\">Pro</Radio>\n */\nexport interface RadioProps extends Omit<AriaRadioProps, \"className\" | \"style\" | \"children\"> {\n /** The label, and this option's accessible name. */\n children?: ReactNode;\n /** Extra classes for the label. Merged last, so they win conflicts. */\n className?: string;\n}\n\nexport function Radio({ className, children, ...props }: RadioProps) {\n return (\n <AriaRadio\n {...props}\n data-slot=\"radio\"\n // justify-start, overriding the shared label's centring: a radio sits\n // in a vertical list where every label must start on the same line,\n // not centre itself in a 24px box.\n className={cn(cnAll(selectionLabelVariants(), \"justify-start\"), className)}\n >\n {({ isSelected }) => (\n <>\n <span aria-hidden=\"true\" data-slot=\"radio-dot\" className={radioVariants()}>\n {isSelected ? (\n // Rendered in JS rather than toggled with CSS, so the\n // precedence is explicit and a test can read it from the DOM.\n <span data-slot=\"radio-indicator\" className=\"size-1.5 rounded-full bg-current\" />\n ) : null}\n </span>\n {children == null ? null : <span data-slot=\"radio-label\">{children}</span>}\n </>\n )}\n </AriaRadio>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,MAAa,qBAAqB,IAAI,CACpC,uBACA,sBACF,CAAC;AAED,MAAa,gBAAgB,IAAI;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA,sBAAsB;AACxB,CAAC;AAgBD,SAAgBA,aAAW,EAAE,WAAW,GAAG,SAA0B;CACnE,OACE,oBAACC,YAAD;EACE,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,mBAAmB,GAAG,SAAS;CAC9C,CAAA;AAEL;AAeA,SAAgBC,QAAM,EAAE,WAAW,UAAU,GAAG,SAAqB;CACnE,OACE,oBAACC,OAAD;EACE,GAAI;EACJ,aAAU;EAIV,WAAW,GAAG,MAAM,uBAAuB,GAAG,eAAe,GAAG,SAAS;EAEvE,WAAA,EAAE,iBACF,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,QAAD;GAAM,eAAY;GAAO,aAAU;GAAY,WAAW,cAAc;GACrE,UAAA,aAGC,oBAAC,QAAD;IAAM,aAAU;IAAkB,WAAU;GAAoC,CAAA,IAC9E;EACA,CAAA,GACL,YAAY,OAAO,OAAO,oBAAC,QAAD;GAAM,aAAU;GAAe;EAAe,CAAA,CACzE,EAAA,CAAA;CAEK,CAAA;AAEf"}
1
+ {"version":3,"file":"radio-group.js","names":["RadioGroup","AriaRadioGroup","Radio","AriaRadio"],"sources":["../../src/radio-group/radio-group.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n Radio as AriaRadio,\n RadioGroup as AriaRadioGroup,\n type RadioGroupProps as AriaRadioGroupProps,\n type RadioProps as AriaRadioProps,\n} from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { selectionLabelVariants, selectionRingVariants } from \"../lib/selection-control.js\";\n\n/**\n * RadioGroup, ported from the legacy shadcn/Radix component onto React Aria.\n *\n * Census: 1 file, 3 uses -- `RadioGroup` 1, `RadioGroupItem` 2. The\n * smallest real surface in the migration, so the port is deliberately plain.\n *\n * THE LABEL MOVES INSIDE THE ITEM. Radix's `RadioGroupItem` is a bare\n * button and the caller pairs it with a separate `<Label htmlFor>`; React\n * Aria's `Radio` takes its label as `children` and wires the association\n * itself. That is the one rename a call site has to make, and it removes\n * the id bookkeeping.\n *\n * `border-primary` becomes `border-border` for the same reason it did on\n * Checkbox: brand orange is 2.44:1 on white and cannot legally be the only\n * thing describing a control's edge (WCAG 1.4.11). Selected reuses\n * Checkbox's exact pair -- `bg-primary` with a `primary-strong` boundary --\n * so a radio and a checkbox in the same form agree.\n */\nexport const radioGroupVariants = cva([\n \"flex flex-col gap-2\",\n \"box-border font-sans\",\n]);\n\nexport const radioVariants = cva([\n \"inline-flex size-4 shrink-0 items-center justify-center\",\n \"rounded-full border-(length:--border-width) box-border\",\n \"bg-background border-border\",\n \"motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard\",\n \"group-data-[selected]:bg-primary group-data-[selected]:border-primary-strong\",\n \"group-data-[selected]:text-primary-foreground\",\n selectionRingVariants(),\n]);\n\n/**\n * A set of mutually exclusive choices.\n *\n * @example\n * <RadioGroup value={plan} onChange={setPlan} aria-label=\"Plan\">\n * <Radio value=\"free\">Free</Radio>\n * <Radio value=\"pro\">Pro</Radio>\n * </RadioGroup>\n */\nexport interface RadioGroupProps extends Omit<AriaRadioGroupProps, \"className\" | \"style\"> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(function RadioGroup(\n { className, ...props },\n ref,\n) {\n return (\n <AriaRadioGroup\n ref={ref}\n {...props}\n data-slot=\"radio-group\"\n className={cn(radioGroupVariants(), className)}\n />\n );\n});\n\n/**\n * One choice.\n *\n * @example\n * <Radio value=\"pro\">Pro</Radio>\n */\nexport interface RadioProps extends Omit<AriaRadioProps, \"className\" | \"style\" | \"children\"> {\n /** The label, and this option's accessible name. */\n children?: ReactNode;\n /** Extra classes for the label. Merged last, so they win conflicts. */\n className?: string;\n}\n\nexport const Radio = forwardRef<HTMLInputElement, RadioProps>(function Radio(\n { className, children, ...props },\n ref,\n) {\n return (\n <AriaRadio\n {...props}\n // inputRef, not ref: AriaRadio's own ref targets the label it renders\n // around a visually-hidden native input, and a label cannot be\n // focused -- ref.current.focus() would silently no-op. inputRef is\n // React Aria's own escape hatch to the real control.\n inputRef={ref}\n data-slot=\"radio\"\n // justify-start, overriding the shared label's centring: a radio sits\n // in a vertical list where every label must start on the same line,\n // not centre itself in a 24px box.\n className={cn(cnAll(selectionLabelVariants(), \"justify-start\"), className)}\n >\n {({ isSelected }) => (\n <>\n <span aria-hidden=\"true\" data-slot=\"radio-dot\" className={radioVariants()}>\n {isSelected ? (\n // Rendered in JS rather than toggled with CSS, so the\n // precedence is explicit and a test can read it from the DOM.\n <span data-slot=\"radio-indicator\" className=\"size-1.5 rounded-full bg-current\" />\n ) : null}\n </span>\n {children == null ? null : <span data-slot=\"radio-label\">{children}</span>}\n </>\n )}\n </AriaRadio>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,MAAa,qBAAqB,IAAI,CACpC,uBACA,sBACF,CAAC;AAED,MAAa,gBAAgB,IAAI;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA,sBAAsB;AACxB,CAAC;AAgBD,MAAaA,eAAa,WAA4C,SAASA,aAC7E,EAAE,WAAW,GAAG,SAChB,KACA;CACA,OACE,oBAACC,YAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,mBAAmB,GAAG,SAAS;CAC9C,CAAA;AAEL,CAAC;AAeD,MAAaC,UAAQ,WAAyC,SAASA,QACrE,EAAE,WAAW,UAAU,GAAG,SAC1B,KACA;CACA,OACE,oBAACC,OAAD;EACE,GAAI;EAKJ,UAAU;EACV,aAAU;EAIV,WAAW,GAAG,MAAM,uBAAuB,GAAG,eAAe,GAAG,SAAS;EAEvE,WAAA,EAAE,iBACF,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,QAAD;GAAM,eAAY;GAAO,aAAU;GAAY,WAAW,cAAc;GACrE,UAAA,aAGC,oBAAC,QAAD;IAAM,aAAU;IAAkB,WAAU;GAAoC,CAAA,IAC9E;EACA,CAAA,GACL,YAAY,OAAO,OAAO,oBAAC,QAAD;GAAM,aAAU;GAAe;EAAe,CAAA,CACzE,EAAA,CAAA;CAEK,CAAA;AAEf,CAAC"}
@@ -57,11 +57,12 @@ export interface ScrollAreaProps extends ComponentPropsWithoutRef<"div"> {
57
57
  * Change this when the contents change, and the region scrolls back to the
58
58
  * top.
59
59
  *
60
- * It lives here rather than on the caller because the caller cannot reach
61
- * this node: no component in this package exposes a `ref`, and it must not
62
- * -- React 18 is a supported peer, where a `ref` passed as a plain prop to a
63
- * function component is silently dropped. The scroller owns its own
64
- * scrollTop, so the reset belongs with it.
60
+ * This stays a declarative prop even though a `ref` now exists: reaching
61
+ * for it would mean the caller writes `ref.current.scrollTop = 0` in their
62
+ * own effect on every content change, which is worse ergonomics than a
63
+ * prop that says "reset when this value changes." That is a design choice
64
+ * independent of whether a ref is available, not a consequence of one
65
+ * being withheld.
65
66
  *
66
67
  * Sheet forwards its own `scrollResetKey` straight through: going a level
67
68
  * deeper in a panel and landing halfway down the new view is disorienting,
@@ -69,6 +70,6 @@ export interface ScrollAreaProps extends ComponentPropsWithoutRef<"div"> {
69
70
  */
70
71
  scrollResetKey?: string | number;
71
72
  }
72
- export declare function ScrollArea({ className, children, label, scrollResetKey, ...props }: ScrollAreaProps): import("react").JSX.Element;
73
+ export declare const ScrollArea: import("react").ForwardRefExoticComponent<ScrollAreaProps & import("react").RefAttributes<HTMLDivElement>>;
73
74
  //#endregion
74
75
  //# sourceMappingURL=scroll-area.d.ts.map
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
+ import { mergeRefs } from "../lib/merge-refs.js";
3
4
  import { cva } from "class-variance-authority";
5
+ import { forwardRef, useEffect, useMemo, useRef } from "react";
4
6
  import { jsx } from "react/jsx-runtime";
5
- import { useEffect, useRef } from "react";
6
7
  //#region src/scroll-area/scroll-area.tsx
7
8
  /**
8
9
  * ScrollArea. React Aria has no equivalent, so this is a plain scrolling
@@ -41,14 +42,15 @@ const scrollAreaVariants = cva([
41
42
  "focus-visible:outline-offset-(--focus-ring-offset)",
42
43
  "focus-visible:outline-(--color-ring)"
43
44
  ]);
44
- function ScrollArea({ className, children, label, scrollResetKey, ...props }) {
45
+ const ScrollArea = forwardRef(function ScrollArea({ className, children, label, scrollResetKey, ...props }, ref) {
45
46
  const region = useRef(null);
46
47
  useEffect(() => {
47
48
  if (region.current) region.current.scrollTop = 0;
48
49
  }, [scrollResetKey]);
50
+ const rootRef = useMemo(() => mergeRefs(ref, region), [ref, region]);
49
51
  return /* @__PURE__ */ jsx("div", {
50
52
  ...props,
51
- ref: region,
53
+ ref: rootRef,
52
54
  tabIndex: 0,
53
55
  role: "region",
54
56
  "aria-label": label,
@@ -56,7 +58,7 @@ function ScrollArea({ className, children, label, scrollResetKey, ...props }) {
56
58
  className: cn(scrollAreaVariants(), className),
57
59
  children
58
60
  });
59
- }
61
+ });
60
62
  //#endregion
61
63
  export { ScrollArea, scrollAreaVariants };
62
64
 
@@ -1 +1 @@
1
- {"version":3,"file":"scroll-area.js","names":[],"sources":["../../src/scroll-area/scroll-area.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { useEffect, useRef, type ComponentPropsWithoutRef, type ReactNode } from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * ScrollArea. React Aria has no equivalent, so this is a plain scrolling\n * div -- and that is the honest answer rather than a shortcut.\n *\n * Census: fourteen paths match, thirteen are counted, ELEVEN render\n * anything, and across them 17 uses. Every single one passes `className`\n * and nothing else, and 13 of the 17 are a height or flex constraint plus\n * padding. `ScrollBar` is exported by the legacy and imported by nobody.\n * `orientation=\"horizontal\"` has zero call sites.\n *\n * So there is no styled-scrollbar requirement to port. The legacy pays for\n * one anyway: Radix injects a `<style>` tag that hides the native\n * scrollbar, then re-draws it in JavaScript with a ResizeObserver, a\n * portal-free overlay and a `display: table` content wrapper. That is a\n * lot of machinery to reproduce a scrollbar, and it is why the legacy\n * `ScrollArea` cannot be scrolled by keyboard at all.\n *\n * Tailwind 4.3.3 ships `scrollbar-width` and `scrollbar-color` utilities\n * (verified in the installed dist, not assumed), which gets the same look\n * in three classes and no JavaScript.\n *\n * WHAT CANNOT BE STYLED, stated rather than glossed: `scrollbar-color`\n * takes a thumb and a track colour and nothing else -- no width in pixels,\n * no radius, no hover state, no margins. `::-webkit-scrollbar` can do\n * those but is non-standard and Firefox has never supported it. This\n * component uses the standard properties only.\n */\nexport const scrollAreaVariants = cva([\n \"overflow-y-auto overscroll-contain\",\n \"box-border\",\n // Standard scrollbar properties. thumb = --color-border (5.25:1 on the\n // page), track transparent so it does not draw a channel.\n \"scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent\",\n // A scrollable region must be reachable by keyboard, or its content is\n // unreachable without a pointer -- WCAG 2.1.1, and axe's\n // scrollable-region-focusable rule. The legacy has no tabIndex at all,\n // so all 17 of its scroll areas are keyboard traps in the other\n // direction: you can see the content but never scroll to it.\n \"outline-none\",\n \"focus-visible:outline-solid\",\n \"focus-visible:outline-(length:--focus-ring-width)\",\n \"focus-visible:outline-offset-(--focus-ring-offset)\",\n \"focus-visible:outline-(--color-ring)\",\n]);\n\n/**\n * A scrolling region.\n *\n * @example\n * <ScrollArea label=\"Chat messages\" className=\"h-64\">...</ScrollArea>\n */\nexport interface ScrollAreaProps extends ComponentPropsWithoutRef<\"div\"> {\n children?: ReactNode;\n /**\n * Names the scrolling region.\n *\n * **Required**, and it took a failing test to get here. A focusable\n * element with no role fails axe `focus-order-semantics`, so the\n * keyboard fix below cannot ship without a role -- and a role with no\n * accessible name is a violation of its own. Naming it is the only\n * combination that is actually correct.\n *\n * `role=\"region\"` specifically. `group` was the first attempt -- it is\n * lighter, since region is a landmark and seventeen landmarks in one app\n * is noise -- but axe does not accept `group` as an appropriate role for\n * a focusable element, and it is right: a named region is the role the\n * ARIA practices name for a scrollable content area, and being able to\n * jump to it is the point.\n */\n label: string;\n /**\n * Change this when the contents change, and the region scrolls back to the\n * top.\n *\n * It lives here rather than on the caller because the caller cannot reach\n * this node: no component in this package exposes a `ref`, and it must not\n * -- React 18 is a supported peer, where a `ref` passed as a plain prop to a\n * function component is silently dropped. The scroller owns its own\n * scrollTop, so the reset belongs with it.\n *\n * Sheet forwards its own `scrollResetKey` straight through: going a level\n * deeper in a panel and landing halfway down the new view is disorienting,\n * and it is exactly the thing every call site forgets.\n */\n scrollResetKey?: string | number;\n}\n\nexport function ScrollArea({\n className,\n children,\n label,\n scrollResetKey,\n ...props\n}: ScrollAreaProps) {\n const region = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n // Unconditional rather than guarded on scrollResetKey being set: with no\n // key this runs once on mount, where scrollTop is already 0.\n if (region.current) region.current.scrollTop = 0;\n }, [scrollResetKey]);\n\n return (\n <div\n {...props}\n ref={region}\n // tabIndex 0 unconditionally: whether the content overflows is a\n // runtime fact that changes as the content does, and a region that\n // becomes scrollable after a fetch would otherwise never gain focus.\n tabIndex={0}\n role=\"region\"\n aria-label={label}\n data-slot=\"scroll-area\"\n className={cn(scrollAreaVariants(), className)}\n >\n {children}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,MAAa,qBAAqB,IAAI;CACpC;CACA;CAGA;CAMA;CACA;CACA;CACA;CACA;AACF,CAAC;AA4CD,SAAgB,WAAW,EACzB,WACA,UACA,OACA,gBACA,GAAG,SACe;CAClB,MAAM,SAAS,OAAuB,IAAI;CAE1C,gBAAgB;EAGd,IAAI,OAAO,SAAS,OAAO,QAAQ,YAAY;CACjD,GAAG,CAAC,cAAc,CAAC;CAEnB,OACE,oBAAC,OAAD;EACE,GAAI;EACJ,KAAK;EAIL,UAAU;EACV,MAAK;EACL,cAAY;EACZ,aAAU;EACV,WAAW,GAAG,mBAAmB,GAAG,SAAS;EAE5C;CACE,CAAA;AAET"}
1
+ {"version":3,"file":"scroll-area.js","names":[],"sources":["../../src/scroll-area/scroll-area.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport {\n forwardRef,\n useEffect,\n useMemo,\n useRef,\n type ComponentPropsWithoutRef,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\n\n/**\n * ScrollArea. React Aria has no equivalent, so this is a plain scrolling\n * div -- and that is the honest answer rather than a shortcut.\n *\n * Census: fourteen paths match, thirteen are counted, ELEVEN render\n * anything, and across them 17 uses. Every single one passes `className`\n * and nothing else, and 13 of the 17 are a height or flex constraint plus\n * padding. `ScrollBar` is exported by the legacy and imported by nobody.\n * `orientation=\"horizontal\"` has zero call sites.\n *\n * So there is no styled-scrollbar requirement to port. The legacy pays for\n * one anyway: Radix injects a `<style>` tag that hides the native\n * scrollbar, then re-draws it in JavaScript with a ResizeObserver, a\n * portal-free overlay and a `display: table` content wrapper. That is a\n * lot of machinery to reproduce a scrollbar, and it is why the legacy\n * `ScrollArea` cannot be scrolled by keyboard at all.\n *\n * Tailwind 4.3.3 ships `scrollbar-width` and `scrollbar-color` utilities\n * (verified in the installed dist, not assumed), which gets the same look\n * in three classes and no JavaScript.\n *\n * WHAT CANNOT BE STYLED, stated rather than glossed: `scrollbar-color`\n * takes a thumb and a track colour and nothing else -- no width in pixels,\n * no radius, no hover state, no margins. `::-webkit-scrollbar` can do\n * those but is non-standard and Firefox has never supported it. This\n * component uses the standard properties only.\n */\nexport const scrollAreaVariants = cva([\n \"overflow-y-auto overscroll-contain\",\n \"box-border\",\n // Standard scrollbar properties. thumb = --color-border (5.25:1 on the\n // page), track transparent so it does not draw a channel.\n \"scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent\",\n // A scrollable region must be reachable by keyboard, or its content is\n // unreachable without a pointer -- WCAG 2.1.1, and axe's\n // scrollable-region-focusable rule. The legacy has no tabIndex at all,\n // so all 17 of its scroll areas are keyboard traps in the other\n // direction: you can see the content but never scroll to it.\n \"outline-none\",\n \"focus-visible:outline-solid\",\n \"focus-visible:outline-(length:--focus-ring-width)\",\n \"focus-visible:outline-offset-(--focus-ring-offset)\",\n \"focus-visible:outline-(--color-ring)\",\n]);\n\n/**\n * A scrolling region.\n *\n * @example\n * <ScrollArea label=\"Chat messages\" className=\"h-64\">...</ScrollArea>\n */\nexport interface ScrollAreaProps extends ComponentPropsWithoutRef<\"div\"> {\n children?: ReactNode;\n /**\n * Names the scrolling region.\n *\n * **Required**, and it took a failing test to get here. A focusable\n * element with no role fails axe `focus-order-semantics`, so the\n * keyboard fix below cannot ship without a role -- and a role with no\n * accessible name is a violation of its own. Naming it is the only\n * combination that is actually correct.\n *\n * `role=\"region\"` specifically. `group` was the first attempt -- it is\n * lighter, since region is a landmark and seventeen landmarks in one app\n * is noise -- but axe does not accept `group` as an appropriate role for\n * a focusable element, and it is right: a named region is the role the\n * ARIA practices name for a scrollable content area, and being able to\n * jump to it is the point.\n */\n label: string;\n /**\n * Change this when the contents change, and the region scrolls back to the\n * top.\n *\n * This stays a declarative prop even though a `ref` now exists: reaching\n * for it would mean the caller writes `ref.current.scrollTop = 0` in their\n * own effect on every content change, which is worse ergonomics than a\n * prop that says \"reset when this value changes.\" That is a design choice\n * independent of whether a ref is available, not a consequence of one\n * being withheld.\n *\n * Sheet forwards its own `scrollResetKey` straight through: going a level\n * deeper in a panel and landing halfway down the new view is disorienting,\n * and it is exactly the thing every call site forgets.\n */\n scrollResetKey?: string | number;\n}\n\nexport const ScrollArea = forwardRef<HTMLDivElement, ScrollAreaProps>(function ScrollArea(\n { className, children, label, scrollResetKey, ...props },\n ref,\n) {\n const region = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n // Unconditional rather than guarded on scrollResetKey being set: with no\n // key this runs once on mount, where scrollTop is already 0.\n if (region.current) region.current.scrollTop = 0;\n }, [scrollResetKey]);\n\n // Memoized, not called inline on the div below: mergeRefs returns a new\n // callback every call, and an inline call is a new callback every render --\n // React would detach and reattach the DOM ref on every re-render instead of\n // only on mount, which matters here more than most, since ScrollArea's own\n // purpose is wrapping content that re-renders often.\n const rootRef = useMemo(() => mergeRefs(ref, region), [ref, region]);\n\n return (\n <div\n {...props}\n ref={rootRef}\n // tabIndex 0 unconditionally: whether the content overflows is a\n // runtime fact that changes as the content does, and a region that\n // becomes scrollable after a fetch would otherwise never gain focus.\n tabIndex={0}\n role=\"region\"\n aria-label={label}\n data-slot=\"scroll-area\"\n className={cn(scrollAreaVariants(), className)}\n >\n {children}\n </div>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,MAAa,qBAAqB,IAAI;CACpC;CACA;CAGA;CAMA;CACA;CACA;CACA;CACA;AACF,CAAC;AA6CD,MAAa,aAAa,WAA4C,SAAS,WAC7E,EAAE,WAAW,UAAU,OAAO,gBAAgB,GAAG,SACjD,KACA;CACA,MAAM,SAAS,OAAuB,IAAI;CAE1C,gBAAgB;EAGd,IAAI,OAAO,SAAS,OAAO,QAAQ,YAAY;CACjD,GAAG,CAAC,cAAc,CAAC;CAOnB,MAAM,UAAU,cAAc,UAAU,KAAK,MAAM,GAAG,CAAC,KAAK,MAAM,CAAC;CAEnE,OACE,oBAAC,OAAD;EACE,GAAI;EACJ,KAAK;EAIL,UAAU;EACV,MAAK;EACL,cAAY;EACZ,aAAU;EACV,WAAW,GAAG,mBAAmB,GAAG,SAAS;EAE5C;CACE,CAAA;AAET,CAAC"}
@@ -1,5 +1,5 @@
1
- import { ListBoxItemProps, SelectProps } from "react-aria-components";
2
1
  import { ReactNode } from "react";
2
+ import { ListBoxItemProps, SelectProps } from "react-aria-components";
3
3
  //#region src/select/select.d.ts
4
4
  /**
5
5
  * Select, ported from the legacy shadcn/Radix component.
@@ -80,7 +80,7 @@ interface SelectProps$1 extends Omit<SelectProps<object>, "className" | "style"
80
80
  /** Extra classes for the wrapper. Merged last. */
81
81
  className?: string;
82
82
  }
83
- declare function Select$1({ children, label, labelHidden, placeholder, errorMessage, size, className, ...props }: SelectProps$1): import("react").JSX.Element;
83
+ declare const Select$1: import("react").ForwardRefExoticComponent<SelectProps$1 & import("react").RefAttributes<HTMLButtonElement>>;
84
84
  /**
85
85
  * One option. Its `id` is the value.
86
86
  *
@@ -99,7 +99,7 @@ export interface SelectItemProps extends Omit<ListBoxItemProps, "className" | "s
99
99
  /** Extra classes. Merged last, so they win conflicts with ours. */
100
100
  className?: string;
101
101
  }
102
- export declare function SelectItem({ className, children, textValue, ...props }: SelectItemProps): import("react").JSX.Element;
102
+ export declare const SelectItem: import("react").ForwardRefExoticComponent<SelectItemProps & import("react").RefAttributes<HTMLDivElement>>;
103
103
  //#endregion
104
104
  export { Select$1 as Select, SelectProps$1 as SelectProps };
105
105
  //# sourceMappingURL=select.d.ts.map
@@ -2,6 +2,7 @@
2
2
  import { cn, cnAll } from "../lib/cn.js";
3
3
  import { fieldSurfaceVariants } from "../lib/field-surface.js";
4
4
  import { cva } from "class-variance-authority";
5
+ import { forwardRef } from "react";
5
6
  import { Button, FieldError, Label, ListBox, ListBoxItem, Popover, Select, SelectValue } from "react-aria-components";
6
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
8
  //#region src/select/select.tsx
@@ -73,7 +74,7 @@ const selectItemVariants = cva([
73
74
  "data-[focus-visible]:outline-(--color-ring)",
74
75
  "data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-disabled)"
75
76
  ]);
76
- function Select$1({ children, label, labelHidden, placeholder, errorMessage, size = "md", className, ...props }) {
77
+ const Select$1 = forwardRef(function Select$2({ children, label, labelHidden, placeholder, errorMessage, size = "md", className, ...props }, ref) {
77
78
  return /* @__PURE__ */ jsxs(Select, {
78
79
  ...props,
79
80
  "data-slot": "select",
@@ -85,6 +86,7 @@ function Select$1({ children, label, labelHidden, placeholder, errorMessage, siz
85
86
  children: label
86
87
  }),
87
88
  /* @__PURE__ */ jsxs(Button, {
89
+ ref,
88
90
  "data-slot": "select-trigger",
89
91
  className: selectTriggerVariants({ size }),
90
92
  children: [/* @__PURE__ */ jsx(SelectValue, {
@@ -122,9 +124,10 @@ function Select$1({ children, label, labelHidden, placeholder, errorMessage, siz
122
124
  })
123
125
  ]
124
126
  });
125
- }
126
- function SelectItem({ className, children, textValue, ...props }) {
127
+ });
128
+ const SelectItem = forwardRef(function SelectItem({ className, children, textValue, ...props }, ref) {
127
129
  return /* @__PURE__ */ jsx(ListBoxItem, {
130
+ ref,
128
131
  ...props,
129
132
  textValue: textValue ?? (typeof children === "string" ? children : void 0),
130
133
  "data-slot": "select-item",
@@ -142,7 +145,7 @@ function SelectItem({ className, children, textValue, ...props }) {
142
145
  children: /* @__PURE__ */ jsx("path", { d: "m3.5 8.5 3 3 6-6" })
143
146
  }) : null, children] })
144
147
  });
145
- }
148
+ });
146
149
  //#endregion
147
150
  export { Select$1 as Select, SelectItem, selectItemVariants, selectPopoverVariants, selectTriggerVariants, selectVariants };
148
151
 
@@ -1 +1 @@
1
- {"version":3,"file":"select.js","names":["Select","AriaSelect","AriaLabel","AriaButton","AriaSelectValue","AriaFieldError","AriaPopover","AriaListBox","AriaListBoxItem"],"sources":["../../src/select/select.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport type { ReactNode } from \"react\";\nimport {\n Button as AriaButton,\n FieldError as AriaFieldError,\n Label as AriaLabel,\n ListBox as AriaListBox,\n ListBoxItem as AriaListBoxItem,\n Popover as AriaPopover,\n Select as AriaSelect,\n SelectValue as AriaSelectValue,\n type ListBoxItemProps as AriaListBoxItemProps,\n type SelectProps as AriaSelectProps,\n} from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { fieldSurfaceVariants } from \"../lib/field-surface.js\";\n\n/**\n * Select, ported from the legacy shadcn/Radix component.\n *\n * FOUR PARTS COLLAPSE INTO ONE. The census reads `SelectItem` 35 and\n * `Select`, `SelectTrigger`, `SelectValue`, `SelectContent` 15 each --\n * those four always appear together in the same shape, exactly like\n * Dialog's header trio, so they are one component and a prop.\n * `SelectGroup`, `SelectLabel`, `SelectSeparator`, `SelectScrollUpButton`\n * and `SelectScrollDownButton` have ZERO call sites and are not ported.\n *\n * `label` IS REQUIRED. The legacy exports a `SelectLabel` that nobody\n * renders and passes no `aria-label` anywhere, so all 15 selects are\n * unnamed comboboxes -- the same finding as Progress, Slider and\n * ScrollArea. Pass `labelHidden` when the surrounding UI already says it.\n *\n * The trigger reuses `fieldSurfaceVariants`, shared with Input and\n * Textarea. A select and a text field sit in the same form row and must\n * be the same box; the legacy styled them separately and drifted --\n * `h-10` here against Input's `h-9`, for no stated reason.\n *\n * The popover is `card`, not a `popover` role: `--popover` is\n * byte-identical to `--card` in the legacy theme. Item hover is `muted`,\n * not `accent`, for the same reason.\n */\n/*\n * `group` is load-bearing, not decoration. React Aria puts `data-invalid` on\n * THIS element, never on the trigger -- so the trigger's own\n * `data-[invalid]:` rule, inherited from the shared field surface, can never\n * match and `isInvalid` painted nothing at all. The trigger reads the state\n * from here instead.\n */\nexport const selectVariants = cva([\"group flex w-full flex-col gap-1.5 box-border font-sans\"]);\n\nexport const selectTriggerVariants = cva(\n [\n // See selectVariants: the invalid state lives on the ROOT, so this reads it\n // from the group rather than from itself.\n \"group-data-[invalid]:border-destructive-strong\",\n fieldSurfaceVariants(),\n \"flex items-center justify-between gap-2 text-start\",\n \"text-base leading-(--leading-normal) cursor-pointer\",\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"[&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n ],\n {\n variants: {\n /**\n * The same rungs and the same tokens as Input and Button. 14 of the\n * product's 32 `SelectTrigger` call sites override the height by hand,\n * and a Select sits in the same form row as a field -- giving Input the\n * axis without giving it here would be exactly the drift\n * `fieldSurfaceVariants` exists to prevent.\n *\n * Type stays `text-base` at every rung, matching Input. A row whose two\n * controls disagree about type size reads as a mistake.\n */\n size: {\n sm: \"h-(--density-control-h-sm) px-(--density-control-pad-sm)\",\n md: \"h-(--density-control-h-md) px-(--density-control-pad-md)\",\n lg: \"h-(--density-control-h-lg) px-(--density-control-pad-lg)\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\nexport const selectPopoverVariants = cva([\n // --trigger-width is set inline by React Aria, so the list matches the\n // control it came from rather than guessing a width.\n \"w-(--trigger-width) min-w-32 overflow-y-auto\",\n \"rounded-md border-(length:--border-width) border-border p-1\",\n \"bg-card text-card-foreground shadow-floating\",\n \"box-border font-sans text-sm leading-(--leading-normal)\",\n]);\n\nexport const selectItemVariants = cva([\n \"relative flex cursor-pointer select-none items-center gap-2\",\n \"rounded-2xs py-1.5 pe-2 ps-8\",\n \"box-border text-sm leading-(--leading-normal) text-card-foreground\",\n // data-focused, not :hover. React Aria drives one highlight from both\n // pointer and keyboard, so the row under the mouse and the row the\n // arrows reached are the same row.\n \"outline-none data-[focused]:bg-muted\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-disabled)\",\n]);\n\n/**\n * A single-choice picker.\n *\n * @example\n * <Select label=\"Country\" placeholder=\"Pick one\"\n * selectedKey={code} onSelectionChange={setCode}>\n * <SelectItem id=\"gb\">United Kingdom</SelectItem>\n * <SelectItem id=\"us\">United States</SelectItem>\n * </Select>\n */\nexport interface SelectProps\n extends Omit<AriaSelectProps<object>, \"className\" | \"style\" | \"children\" | \"items\"> {\n /** `SelectItem` children. */\n children?: ReactNode;\n /**\n * What is being chosen, and the control's accessible name.\n *\n * **Required.** All 15 legacy selects are unnamed: the legacy exports a\n * `SelectLabel` that nobody renders and passes no `aria-label`.\n */\n label: ReactNode;\n /** Render the label for screen readers only. */\n labelHidden?: boolean;\n /** Shown while nothing is selected. */\n placeholder?: string;\n /**\n * Why the selection is wrong. Rendered under the control, and it is what\n * makes `isInvalid` reach a screen reader.\n *\n * React Aria marks the ROOT `data-invalid` and leaves the button untouched,\n * and an `aria-invalid` handed to that button is dropped by\n * `filterDOMProps`, whose allowlist stops at the labelling attributes -- the\n * same silent loss as `aria-current` on a pagination button. So without a\n * message, an invalid select is invalid only in colour, which is WCAG 1.4.1\n * on its own. React Aria wires this into `aria-describedby` for us.\n */\n errorMessage?: ReactNode;\n /**\n * Control scale of the trigger. Height and inline padding come from the\n * same `--density-control-*` tokens as Input and Button, so a Select, a\n * field and a button on one rung are the same height.\n *\n * Type does not scale with it, matching Input.\n *\n * @default \"md\"\n */\n size?: \"sm\" | \"md\" | \"lg\";\n /** Extra classes for the wrapper. Merged last. */\n className?: string;\n}\n\nexport function Select({\n children,\n label,\n labelHidden,\n placeholder,\n errorMessage,\n size = \"md\",\n className,\n ...props\n}: SelectProps) {\n return (\n <AriaSelect {...props} data-slot=\"select\" className={cn(selectVariants(), className)}>\n <AriaLabel\n data-slot=\"select-label\"\n className={cnAll(\n \"text-sm leading-(--leading-normal) text-foreground\",\n labelHidden && \"sr-only\",\n )}\n >\n {label}\n </AriaLabel>\n\n <AriaButton data-slot=\"select-trigger\" className={selectTriggerVariants({ size })}>\n <AriaSelectValue data-slot=\"select-value\">\n {({ isPlaceholder, selectedText }) =>\n isPlaceholder ? (\n <span data-slot=\"select-placeholder\" className=\"text-muted-foreground\">\n {placeholder}\n </span>\n ) : (\n selectedText\n )\n }\n </AriaSelectValue>\n {/*\n aria-hidden: the chevron is decoration. The button already\n announces itself as a combobox, and a second name source only\n makes it announce twice.\n */}\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"text-muted-foreground\"\n >\n <path d=\"m4 6 4 4 4-4\" />\n </svg>\n </AriaButton>\n\n {errorMessage == null ? null : (\n // Not conditional on isInvalid: React Aria renders FieldError only\n // while the field IS invalid, so gating it here as well would just be\n // two sources of truth for one state.\n <AriaFieldError\n data-slot=\"select-error\"\n className=\"text-sm leading-(--leading-normal) text-destructive-strong\"\n >\n {errorMessage}\n </AriaFieldError>\n )}\n\n <AriaPopover data-slot=\"select-popover\" className={selectPopoverVariants()}>\n <AriaListBox data-slot=\"select-list\" className=\"outline-none\">\n {children}\n </AriaListBox>\n </AriaPopover>\n </AriaSelect>\n );\n}\n\n/**\n * One option. Its `id` is the value.\n *\n * @example\n * <SelectItem id=\"gb\">United Kingdom</SelectItem>\n */\nexport interface SelectItemProps\n extends Omit<AriaListBoxItemProps, \"className\" | \"style\" | \"children\"> {\n children?: ReactNode;\n /**\n * The option's text, for the trigger and for typeahead.\n *\n * Derived from `children` when they are a string. Pass it explicitly\n * when the option renders anything richer than text.\n */\n textValue?: string;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport function SelectItem({ className, children, textValue, ...props }: SelectItemProps) {\n return (\n <AriaListBoxItem\n {...props}\n /*\n textValue, derived from string children.\n React Aria infers an item's text from its children -- but ONLY\n when they are a string. This item uses FUNCTION children so the\n tick can be rendered in JS, which defeats that inference: the\n trigger showed an empty value after selecting, and typeahead had\n nothing to match on. Neither is visible in the type system.\n */\n textValue={textValue ?? (typeof children === \"string\" ? children : undefined)}\n data-slot=\"select-item\"\n className={cn(selectItemVariants(), className)}\n >\n {({ isSelected }) => (\n <>\n {/*\n The tick is rendered in JS rather than toggled with CSS, so\n the precedence is explicit and a test can read it from the\n DOM. ps-8 above reserves the gutter, so selecting an option\n does not shift its label.\n */}\n {isSelected ? (\n <svg\n data-slot=\"select-item-check\"\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"absolute start-2 size-3.5\"\n >\n <path d=\"m3.5 8.5 3 3 6-6\" />\n </svg>\n ) : null}\n {children}\n </>\n )}\n </AriaListBoxItem>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,MAAa,iBAAiB,IAAI,CAAC,yDAAyD,CAAC;AAE7F,MAAa,wBAAwB,IACnC;CAGE;CACA,qBAAqB;CACrB;CACA;CACA;CACA;AACF,GACA;CACE,UAAU;;;;;;;;;;;AAWR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;CACN,EACF;CACA,iBAAiB,EAAE,MAAM,KAAK;AAChC,CACF;AAEA,MAAa,wBAAwB,IAAI;CAGvC;CACA;CACA;CACA;AACF,CAAC;AAED,MAAa,qBAAqB,IAAI;CACpC;CACA;CACA;CAIA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAqDD,SAAgBA,SAAO,EACrB,UACA,OACA,aACA,aACA,cACA,OAAO,MACP,WACA,GAAG,SACW;CACd,OACE,qBAACC,QAAD;EAAY,GAAI;EAAO,aAAU;EAAS,WAAW,GAAG,eAAe,GAAG,SAAS;EAAnF,UAAA;GACE,oBAACC,OAAD;IACE,aAAU;IACV,WAAW,MACT,sDACA,eAAe,SACjB;IAEC,UAAA;GACQ,CAAA;GAEX,qBAACC,QAAD;IAAY,aAAU;IAAiB,WAAW,sBAAsB,EAAE,KAAK,CAAC;IAAhF,UAAA,CACE,oBAACC,aAAD;KAAiB,aAAU;KACvB,WAAA,EAAE,eAAe,mBACjB,gBACE,oBAAC,QAAD;MAAM,aAAU;MAAqB,WAAU;MAC5C,UAAA;KACG,CAAA,IAEN;IAGW,CAAA,GAMjB,oBAAC,OAAD;KACE,eAAY;KACZ,SAAQ;KACR,MAAK;KACL,QAAO;KACP,aAAY;KACZ,eAAc;KACd,gBAAe;KACf,WAAU;KAEV,UAAA,oBAAC,QAAD,EAAM,GAAE,eAAgB,CAAA;IACrB,CAAA,CACK;;GAEX,gBAAgB,OAAO,OAItB,oBAACC,YAAD;IACE,aAAU;IACV,WAAU;IAET,UAAA;GACa,CAAA;GAGlB,oBAACC,SAAD;IAAa,aAAU;IAAiB,WAAW,sBAAsB;IACvE,UAAA,oBAACC,SAAD;KAAa,aAAU;KAAc,WAAU;KAC5C;IACU,CAAA;GACF,CAAA;EACH;;AAEhB;AAsBA,SAAgB,WAAW,EAAE,WAAW,UAAU,WAAW,GAAG,SAA0B;CACxF,OACE,oBAACC,aAAD;EACE,GAAI;EASJ,WAAW,cAAc,OAAO,aAAa,WAAW,WAAW,KAAA;EACnE,aAAU;EACV,WAAW,GAAG,mBAAmB,GAAG,SAAS;EAE3C,WAAA,EAAE,iBACF,qBAAA,UAAA,EAAA,UAAA,CAOG,aACC,oBAAC,OAAD;GACE,aAAU;GACV,eAAY;GACZ,SAAQ;GACR,MAAK;GACL,QAAO;GACP,aAAY;GACZ,eAAc;GACd,gBAAe;GACf,WAAU;GAEV,UAAA,oBAAC,QAAD,EAAM,GAAE,mBAAoB,CAAA;EACzB,CAAA,IACH,MACH,QACD,EAAA,CAAA;CAEW,CAAA;AAErB"}
1
+ {"version":3,"file":"select.js","names":["Select","AriaSelect","AriaLabel","AriaButton","AriaSelectValue","AriaFieldError","AriaPopover","AriaListBox","AriaListBoxItem"],"sources":["../../src/select/select.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n Button as AriaButton,\n FieldError as AriaFieldError,\n Label as AriaLabel,\n ListBox as AriaListBox,\n ListBoxItem as AriaListBoxItem,\n Popover as AriaPopover,\n Select as AriaSelect,\n SelectValue as AriaSelectValue,\n type ListBoxItemProps as AriaListBoxItemProps,\n type SelectProps as AriaSelectProps,\n} from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { fieldSurfaceVariants } from \"../lib/field-surface.js\";\n\n/**\n * Select, ported from the legacy shadcn/Radix component.\n *\n * FOUR PARTS COLLAPSE INTO ONE. The census reads `SelectItem` 35 and\n * `Select`, `SelectTrigger`, `SelectValue`, `SelectContent` 15 each --\n * those four always appear together in the same shape, exactly like\n * Dialog's header trio, so they are one component and a prop.\n * `SelectGroup`, `SelectLabel`, `SelectSeparator`, `SelectScrollUpButton`\n * and `SelectScrollDownButton` have ZERO call sites and are not ported.\n *\n * `label` IS REQUIRED. The legacy exports a `SelectLabel` that nobody\n * renders and passes no `aria-label` anywhere, so all 15 selects are\n * unnamed comboboxes -- the same finding as Progress, Slider and\n * ScrollArea. Pass `labelHidden` when the surrounding UI already says it.\n *\n * The trigger reuses `fieldSurfaceVariants`, shared with Input and\n * Textarea. A select and a text field sit in the same form row and must\n * be the same box; the legacy styled them separately and drifted --\n * `h-10` here against Input's `h-9`, for no stated reason.\n *\n * The popover is `card`, not a `popover` role: `--popover` is\n * byte-identical to `--card` in the legacy theme. Item hover is `muted`,\n * not `accent`, for the same reason.\n */\n/*\n * `group` is load-bearing, not decoration. React Aria puts `data-invalid` on\n * THIS element, never on the trigger -- so the trigger's own\n * `data-[invalid]:` rule, inherited from the shared field surface, can never\n * match and `isInvalid` painted nothing at all. The trigger reads the state\n * from here instead.\n */\nexport const selectVariants = cva([\"group flex w-full flex-col gap-1.5 box-border font-sans\"]);\n\nexport const selectTriggerVariants = cva(\n [\n // See selectVariants: the invalid state lives on the ROOT, so this reads it\n // from the group rather than from itself.\n \"group-data-[invalid]:border-destructive-strong\",\n fieldSurfaceVariants(),\n \"flex items-center justify-between gap-2 text-start\",\n \"text-base leading-(--leading-normal) cursor-pointer\",\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"[&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n ],\n {\n variants: {\n /**\n * The same rungs and the same tokens as Input and Button. 14 of the\n * product's 32 `SelectTrigger` call sites override the height by hand,\n * and a Select sits in the same form row as a field -- giving Input the\n * axis without giving it here would be exactly the drift\n * `fieldSurfaceVariants` exists to prevent.\n *\n * Type stays `text-base` at every rung, matching Input. A row whose two\n * controls disagree about type size reads as a mistake.\n */\n size: {\n sm: \"h-(--density-control-h-sm) px-(--density-control-pad-sm)\",\n md: \"h-(--density-control-h-md) px-(--density-control-pad-md)\",\n lg: \"h-(--density-control-h-lg) px-(--density-control-pad-lg)\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\nexport const selectPopoverVariants = cva([\n // --trigger-width is set inline by React Aria, so the list matches the\n // control it came from rather than guessing a width.\n \"w-(--trigger-width) min-w-32 overflow-y-auto\",\n \"rounded-md border-(length:--border-width) border-border p-1\",\n \"bg-card text-card-foreground shadow-floating\",\n \"box-border font-sans text-sm leading-(--leading-normal)\",\n]);\n\nexport const selectItemVariants = cva([\n \"relative flex cursor-pointer select-none items-center gap-2\",\n \"rounded-2xs py-1.5 pe-2 ps-8\",\n \"box-border text-sm leading-(--leading-normal) text-card-foreground\",\n // data-focused, not :hover. React Aria drives one highlight from both\n // pointer and keyboard, so the row under the mouse and the row the\n // arrows reached are the same row.\n \"outline-none data-[focused]:bg-muted\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-disabled)\",\n]);\n\n/**\n * A single-choice picker.\n *\n * @example\n * <Select label=\"Country\" placeholder=\"Pick one\"\n * selectedKey={code} onSelectionChange={setCode}>\n * <SelectItem id=\"gb\">United Kingdom</SelectItem>\n * <SelectItem id=\"us\">United States</SelectItem>\n * </Select>\n */\nexport interface SelectProps\n extends Omit<AriaSelectProps<object>, \"className\" | \"style\" | \"children\" | \"items\"> {\n /** `SelectItem` children. */\n children?: ReactNode;\n /**\n * What is being chosen, and the control's accessible name.\n *\n * **Required.** All 15 legacy selects are unnamed: the legacy exports a\n * `SelectLabel` that nobody renders and passes no `aria-label`.\n */\n label: ReactNode;\n /** Render the label for screen readers only. */\n labelHidden?: boolean;\n /** Shown while nothing is selected. */\n placeholder?: string;\n /**\n * Why the selection is wrong. Rendered under the control, and it is what\n * makes `isInvalid` reach a screen reader.\n *\n * React Aria marks the ROOT `data-invalid` and leaves the button untouched,\n * and an `aria-invalid` handed to that button is dropped by\n * `filterDOMProps`, whose allowlist stops at the labelling attributes -- the\n * same silent loss as `aria-current` on a pagination button. So without a\n * message, an invalid select is invalid only in colour, which is WCAG 1.4.1\n * on its own. React Aria wires this into `aria-describedby` for us.\n */\n errorMessage?: ReactNode;\n /**\n * Control scale of the trigger. Height and inline padding come from the\n * same `--density-control-*` tokens as Input and Button, so a Select, a\n * field and a button on one rung are the same height.\n *\n * Type does not scale with it, matching Input.\n *\n * @default \"md\"\n */\n size?: \"sm\" | \"md\" | \"lg\";\n /** Extra classes for the wrapper. Merged last. */\n className?: string;\n}\n\nexport const Select = forwardRef<HTMLButtonElement, SelectProps>(function Select(\n {\n children,\n label,\n labelHidden,\n placeholder,\n errorMessage,\n size = \"md\",\n className,\n ...props\n },\n ref,\n) {\n return (\n <AriaSelect {...props} data-slot=\"select\" className={cn(selectVariants(), className)}>\n <AriaLabel\n data-slot=\"select-label\"\n className={cnAll(\n \"text-sm leading-(--leading-normal) text-foreground\",\n labelHidden && \"sr-only\",\n )}\n >\n {label}\n </AriaLabel>\n\n {/*\n The ref goes on the TRIGGER, not the AriaSelect root above. The root\n is a field-level wrapper div; the trigger is the always-visible\n interactive surface a caller actually wants to focus or click,\n the same reasoning that puts Input's ref on the real control.\n */}\n <AriaButton ref={ref} data-slot=\"select-trigger\" className={selectTriggerVariants({ size })}>\n <AriaSelectValue data-slot=\"select-value\">\n {({ isPlaceholder, selectedText }) =>\n isPlaceholder ? (\n <span data-slot=\"select-placeholder\" className=\"text-muted-foreground\">\n {placeholder}\n </span>\n ) : (\n selectedText\n )\n }\n </AriaSelectValue>\n {/*\n aria-hidden: the chevron is decoration. The button already\n announces itself as a combobox, and a second name source only\n makes it announce twice.\n */}\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"text-muted-foreground\"\n >\n <path d=\"m4 6 4 4 4-4\" />\n </svg>\n </AriaButton>\n\n {errorMessage == null ? null : (\n // Not conditional on isInvalid: React Aria renders FieldError only\n // while the field IS invalid, so gating it here as well would just be\n // two sources of truth for one state.\n <AriaFieldError\n data-slot=\"select-error\"\n className=\"text-sm leading-(--leading-normal) text-destructive-strong\"\n >\n {errorMessage}\n </AriaFieldError>\n )}\n\n <AriaPopover data-slot=\"select-popover\" className={selectPopoverVariants()}>\n <AriaListBox data-slot=\"select-list\" className=\"outline-none\">\n {children}\n </AriaListBox>\n </AriaPopover>\n </AriaSelect>\n );\n});\n\n/**\n * One option. Its `id` is the value.\n *\n * @example\n * <SelectItem id=\"gb\">United Kingdom</SelectItem>\n */\nexport interface SelectItemProps\n extends Omit<AriaListBoxItemProps, \"className\" | \"style\" | \"children\"> {\n children?: ReactNode;\n /**\n * The option's text, for the trigger and for typeahead.\n *\n * Derived from `children` when they are a string. Pass it explicitly\n * when the option renders anything richer than text.\n */\n textValue?: string;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(function SelectItem(\n { className, children, textValue, ...props },\n ref,\n) {\n return (\n <AriaListBoxItem\n ref={ref}\n {...props}\n /*\n textValue, derived from string children.\n React Aria infers an item's text from its children -- but ONLY\n when they are a string. This item uses FUNCTION children so the\n tick can be rendered in JS, which defeats that inference: the\n trigger showed an empty value after selecting, and typeahead had\n nothing to match on. Neither is visible in the type system.\n */\n textValue={textValue ?? (typeof children === \"string\" ? children : undefined)}\n data-slot=\"select-item\"\n className={cn(selectItemVariants(), className)}\n >\n {({ isSelected }) => (\n <>\n {/*\n The tick is rendered in JS rather than toggled with CSS, so\n the precedence is explicit and a test can read it from the\n DOM. ps-8 above reserves the gutter, so selecting an option\n does not shift its label.\n */}\n {isSelected ? (\n <svg\n data-slot=\"select-item-check\"\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"absolute start-2 size-3.5\"\n >\n <path d=\"m3.5 8.5 3 3 6-6\" />\n </svg>\n ) : null}\n {children}\n </>\n )}\n </AriaListBoxItem>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,MAAa,iBAAiB,IAAI,CAAC,yDAAyD,CAAC;AAE7F,MAAa,wBAAwB,IACnC;CAGE;CACA,qBAAqB;CACrB;CACA;CACA;CACA;AACF,GACA;CACE,UAAU;;;;;;;;;;;AAWR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;CACN,EACF;CACA,iBAAiB,EAAE,MAAM,KAAK;AAChC,CACF;AAEA,MAAa,wBAAwB,IAAI;CAGvC;CACA;CACA;CACA;AACF,CAAC;AAED,MAAa,qBAAqB,IAAI;CACpC;CACA;CACA;CAIA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAqDD,MAAaA,WAAS,WAA2C,SAASA,SACxE,EACE,UACA,OACA,aACA,aACA,cACA,OAAO,MACP,WACA,GAAG,SAEL,KACA;CACA,OACE,qBAACC,QAAD;EAAY,GAAI;EAAO,aAAU;EAAS,WAAW,GAAG,eAAe,GAAG,SAAS;EAAnF,UAAA;GACE,oBAACC,OAAD;IACE,aAAU;IACV,WAAW,MACT,sDACA,eAAe,SACjB;IAEC,UAAA;GACQ,CAAA;GAQX,qBAACC,QAAD;IAAiB;IAAK,aAAU;IAAiB,WAAW,sBAAsB,EAAE,KAAK,CAAC;IAA1F,UAAA,CACE,oBAACC,aAAD;KAAiB,aAAU;KACvB,WAAA,EAAE,eAAe,mBACjB,gBACE,oBAAC,QAAD;MAAM,aAAU;MAAqB,WAAU;MAC5C,UAAA;KACG,CAAA,IAEN;IAGW,CAAA,GAMjB,oBAAC,OAAD;KACE,eAAY;KACZ,SAAQ;KACR,MAAK;KACL,QAAO;KACP,aAAY;KACZ,eAAc;KACd,gBAAe;KACf,WAAU;KAEV,UAAA,oBAAC,QAAD,EAAM,GAAE,eAAgB,CAAA;IACrB,CAAA,CACK;;GAEX,gBAAgB,OAAO,OAItB,oBAACC,YAAD;IACE,aAAU;IACV,WAAU;IAET,UAAA;GACa,CAAA;GAGlB,oBAACC,SAAD;IAAa,aAAU;IAAiB,WAAW,sBAAsB;IACvE,UAAA,oBAACC,SAAD;KAAa,aAAU;KAAc,WAAU;KAC5C;IACU,CAAA;GACF,CAAA;EACH;;AAEhB,CAAC;AAsBD,MAAa,aAAa,WAA4C,SAAS,WAC7E,EAAE,WAAW,UAAU,WAAW,GAAG,SACrC,KACA;CACA,OACE,oBAACC,aAAD;EACO;EACL,GAAI;EASJ,WAAW,cAAc,OAAO,aAAa,WAAW,WAAW,KAAA;EACnE,aAAU;EACV,WAAW,GAAG,mBAAmB,GAAG,SAAS;EAE3C,WAAA,EAAE,iBACF,qBAAA,UAAA,EAAA,UAAA,CAOG,aACC,oBAAC,OAAD;GACE,aAAU;GACV,eAAY;GACZ,SAAQ;GACR,MAAK;GACL,QAAO;GACP,aAAY;GACZ,eAAc;GACd,gBAAe;GACf,WAAU;GAEV,UAAA,oBAAC,QAAD,EAAM,GAAE,mBAAoB,CAAA;EACzB,CAAA,IACH,MACH,QACD,EAAA,CAAA;CAEW,CAAA;AAErB,CAAC"}
@@ -36,7 +36,7 @@ interface SeparatorProps$1 extends Omit<SeparatorProps, "className" | "style"> {
36
36
  /** Extra classes. Merged last, so they win conflicts with ours. */
37
37
  className?: string;
38
38
  }
39
- declare function Separator$1({ className, orientation, ...props }: SeparatorProps$1): import("react").JSX.Element;
39
+ declare const Separator$1: import("react").ForwardRefExoticComponent<SeparatorProps$1 & import("react").RefAttributes<HTMLElement>>;
40
40
  //#endregion
41
41
  export { Separator$1 as Separator, SeparatorProps$1 as SeparatorProps };
42
42
  //# sourceMappingURL=separator.d.ts.map
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { cva } from "class-variance-authority";
4
+ import { forwardRef } from "react";
4
5
  import { Separator } from "react-aria-components";
5
6
  import { jsx } from "react/jsx-runtime";
6
7
  //#region src/separator/separator.tsx
@@ -35,14 +36,15 @@ const separatorVariants = cva([
35
36
  } },
36
37
  defaultVariants: { orientation: "horizontal" }
37
38
  });
38
- function Separator$1({ className, orientation = "horizontal", ...props }) {
39
+ const Separator$1 = forwardRef(function Separator$2({ className, orientation = "horizontal", ...props }, ref) {
39
40
  return /* @__PURE__ */ jsx(Separator, {
41
+ ref,
40
42
  ...props,
41
43
  orientation,
42
44
  "data-slot": "separator",
43
45
  className: cn(separatorVariants({ orientation }), className)
44
46
  });
45
- }
47
+ });
46
48
  //#endregion
47
49
  export { Separator$1 as Separator, separatorVariants };
48
50
 
@@ -1 +1 @@
1
- {"version":3,"file":"separator.js","names":["Separator","AriaSeparator"],"sources":["../../src/separator/separator.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport {\n Separator as AriaSeparator,\n type SeparatorProps as AriaSeparatorProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Separator, ported from the legacy component onto React Aria's Separator.\n *\n * Census: 7 files, 6 uses, one of them `orientation=\"vertical\"`. No other\n * prop is ever passed.\n *\n * `decorative` is NOT ported -- zero call sites pass it, and React Aria has\n * no equivalent. That does change the semantics of all six existing uses,\n * which is worth saying out loud rather than burying: the legacy defaulted\n * to `decorative: true` and therefore rendered `role=\"none\"`, so every\n * divider in the app is currently invisible to assistive technology. React\n * Aria renders an `<hr>` for a horizontal separator, whose implicit role IS\n * separator. A visible line between two groups of content is a separator;\n * announcing it is the correct behaviour, and it is what the element the\n * legacy chose (a `div`) was pretending not to be.\n *\n * Every string in the cva blocks below is a STATIC literal. Tailwind scans\n * source text, so a class assembled at runtime is invisible to it and would\n * simply never be emitted.\n */\nexport const separatorVariants = cva(\n [\n // border-none, not border-0. `border-0` sets the WIDTH to zero but leaves\n // border-style at solid, so the computed style still claims a border that\n // simply measures nothing -- the same bookkeeping the Checkbox parity run\n // turned up on the legacy input. `border-none` says what is true.\n \"shrink-0 bg-border border-none\",\n // The user-agent <hr> ships a block margin and a 1px inset border, and\n // React Aria renders an <hr> for the horizontal case. The reset in\n // @seekalfred/styles is OPT-IN, so a consumer may load none -- unreset,\n // every divider is a grooved double line with space around it.\n \"m-0\",\n \"box-border\",\n ],\n {\n variants: {\n orientation: {\n // h-px / w-px, not the legacy h-[1px] / w-[1px]. Those are bracket\n // values that check:hardcoding rejects, and `px` is a first-class\n // Tailwind step rather than an arbitrary one.\n horizontal: \"h-px w-full\",\n vertical: \"h-full w-px\",\n },\n },\n defaultVariants: { orientation: \"horizontal\" },\n },\n);\n\n/**\n * A visual divider between two groups of content.\n *\n * @example\n * <Separator />\n * <Separator orientation=\"vertical\" />\n */\nexport interface SeparatorProps extends Omit<AriaSeparatorProps, \"className\" | \"style\"> {\n /** @default \"horizontal\" */\n orientation?: \"horizontal\" | \"vertical\";\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport function Separator({ className, orientation = \"horizontal\", ...props }: SeparatorProps) {\n return (\n <AriaSeparator\n {...props}\n orientation={orientation}\n data-slot=\"separator\"\n className={cn(separatorVariants({ orientation }), className)}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,MAAa,oBAAoB,IAC/B;CAKE;CAKA;CACA;AACF,GACA;CACE,UAAU,EACR,aAAa;EAIX,YAAY;EACZ,UAAU;CACZ,EACF;CACA,iBAAiB,EAAE,aAAa,aAAa;AAC/C,CACF;AAgBA,SAAgBA,YAAU,EAAE,WAAW,cAAc,cAAc,GAAG,SAAyB;CAC7F,OACE,oBAACC,WAAD;EACE,GAAI;EACS;EACb,aAAU;EACV,WAAW,GAAG,kBAAkB,EAAE,YAAY,CAAC,GAAG,SAAS;CAC5D,CAAA;AAEL"}
1
+ {"version":3,"file":"separator.js","names":["Separator","AriaSeparator"],"sources":["../../src/separator/separator.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef } from \"react\";\nimport {\n Separator as AriaSeparator,\n type SeparatorProps as AriaSeparatorProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Separator, ported from the legacy component onto React Aria's Separator.\n *\n * Census: 7 files, 6 uses, one of them `orientation=\"vertical\"`. No other\n * prop is ever passed.\n *\n * `decorative` is NOT ported -- zero call sites pass it, and React Aria has\n * no equivalent. That does change the semantics of all six existing uses,\n * which is worth saying out loud rather than burying: the legacy defaulted\n * to `decorative: true` and therefore rendered `role=\"none\"`, so every\n * divider in the app is currently invisible to assistive technology. React\n * Aria renders an `<hr>` for a horizontal separator, whose implicit role IS\n * separator. A visible line between two groups of content is a separator;\n * announcing it is the correct behaviour, and it is what the element the\n * legacy chose (a `div`) was pretending not to be.\n *\n * Every string in the cva blocks below is a STATIC literal. Tailwind scans\n * source text, so a class assembled at runtime is invisible to it and would\n * simply never be emitted.\n */\nexport const separatorVariants = cva(\n [\n // border-none, not border-0. `border-0` sets the WIDTH to zero but leaves\n // border-style at solid, so the computed style still claims a border that\n // simply measures nothing -- the same bookkeeping the Checkbox parity run\n // turned up on the legacy input. `border-none` says what is true.\n \"shrink-0 bg-border border-none\",\n // The user-agent <hr> ships a block margin and a 1px inset border, and\n // React Aria renders an <hr> for the horizontal case. The reset in\n // @seekalfred/styles is OPT-IN, so a consumer may load none -- unreset,\n // every divider is a grooved double line with space around it.\n \"m-0\",\n \"box-border\",\n ],\n {\n variants: {\n orientation: {\n // h-px / w-px, not the legacy h-[1px] / w-[1px]. Those are bracket\n // values that check:hardcoding rejects, and `px` is a first-class\n // Tailwind step rather than an arbitrary one.\n horizontal: \"h-px w-full\",\n vertical: \"h-full w-px\",\n },\n },\n defaultVariants: { orientation: \"horizontal\" },\n },\n);\n\n/**\n * A visual divider between two groups of content.\n *\n * @example\n * <Separator />\n * <Separator orientation=\"vertical\" />\n */\nexport interface SeparatorProps extends Omit<AriaSeparatorProps, \"className\" | \"style\"> {\n /** @default \"horizontal\" */\n orientation?: \"horizontal\" | \"vertical\";\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport const Separator = forwardRef<HTMLElement, SeparatorProps>(function Separator(\n { className, orientation = \"horizontal\", ...props },\n ref,\n) {\n return (\n <AriaSeparator\n ref={ref}\n {...props}\n orientation={orientation}\n data-slot=\"separator\"\n className={cn(separatorVariants({ orientation }), className)}\n />\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,oBAAoB,IAC/B;CAKE;CAKA;CACA;AACF,GACA;CACE,UAAU,EACR,aAAa;EAIX,YAAY;EACZ,UAAU;CACZ,EACF;CACA,iBAAiB,EAAE,aAAa,aAAa;AAC/C,CACF;AAgBA,MAAaA,cAAY,WAAwC,SAASA,YACxE,EAAE,WAAW,cAAc,cAAc,GAAG,SAC5C,KACA;CACA,OACE,oBAACC,WAAD;EACO;EACL,GAAI;EACS;EACb,aAAU;EACV,WAAW,GAAG,kBAAkB,EAAE,YAAY,CAAC,GAAG,SAAS;CAC5D,CAAA;AAEL,CAAC"}
@@ -91,6 +91,6 @@ export interface SheetProps extends Omit<DialogProps, "overlayClassName"> {
91
91
  */
92
92
  bodyLabel?: string;
93
93
  }
94
- export declare function Sheet({ side, className, onBack, backLabel, scrollResetKey, bodyClassName, bodyLabel, title, children, ...props }: SheetProps): import("react").JSX.Element;
94
+ export declare const Sheet: import("react").ForwardRefExoticComponent<SheetProps & import("react").RefAttributes<HTMLElement>>;
95
95
  //#endregion
96
96
  //# sourceMappingURL=sheet.d.ts.map
@@ -4,6 +4,7 @@ import { Dialog } from "../dialog/dialog.js";
4
4
  import { Button } from "../button/button.js";
5
5
  import { ScrollArea } from "../scroll-area/scroll-area.js";
6
6
  import { cva } from "class-variance-authority";
7
+ import { forwardRef } from "react";
7
8
  import { jsx } from "react/jsx-runtime";
8
9
  //#region src/sheet/sheet.tsx
9
10
  /**
@@ -70,9 +71,10 @@ function BackArrowGlyph() {
70
71
  children: /* @__PURE__ */ jsx("path", { d: "M10 3 5 8l5 5" })
71
72
  });
72
73
  }
73
- function Sheet({ side = "right", className, onBack, backLabel = "Back", scrollResetKey, bodyClassName, bodyLabel, title, children, ...props }) {
74
+ const Sheet = forwardRef(function Sheet({ side = "right", className, onBack, backLabel = "Back", scrollResetKey, bodyClassName, bodyLabel, title, children, ...props }, ref) {
74
75
  return /* @__PURE__ */ jsx(Dialog, {
75
76
  ...props,
77
+ ref,
76
78
  title,
77
79
  beforeTitle: onBack ? /* @__PURE__ */ jsx(Button, {
78
80
  variant: "ghost",
@@ -92,7 +94,7 @@ function Sheet({ side = "right", className, onBack, backLabel = "Back", scrollRe
92
94
  children
93
95
  })
94
96
  });
95
- }
97
+ });
96
98
  //#endregion
97
99
  export { Sheet, sheetOverlayVariants, sheetPanelVariants };
98
100
 
@@ -1 +1 @@
1
- {"version":3,"file":"sheet.js","names":[],"sources":["../../src/sheet/sheet.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\n\nimport { Button } from \"../button/button.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { Dialog, type DialogProps } from \"../dialog/dialog.js\";\nimport { ScrollArea } from \"../scroll-area/scroll-area.js\";\n\n/**\n * Sheet: a dialog that arrives from an edge.\n *\n * A PRESET over `Dialog`, like `AlertDialog`. The legacy `sheet.tsx` is\n * `@radix-ui/react-dialog` again -- the same import, the same parts, the\n * same close button -- with one prop changing where it sits. Keeping a\n * second copy is how the legacy `dialog.tsx` and `alert-dialog.tsx`\n * drifted, and this is the same file for the third time.\n *\n * Census: 2 files, 3 uses. `side=\"right\"` twice, `side=\"left\"` once, and\n * `className` four times. `SheetHeader`, `SheetFooter`, `SheetTitle`,\n * `SheetDescription` and `SheetClose` are exported by the legacy and used\n * by NOBODY -- so all three sheets in the app render a `role=\"dialog\"`\n * with no title at all. Radix even warns about it in the console.\n *\n * `title` is required here, inherited from Dialog, so that is no longer\n * expressible.\n *\n * `side` is `\"left\" | \"right\"` only. The legacy also offers `\"top\"` and\n * `\"bottom\"`; both have zero call sites, and each is two lines to add the\n * day something needs one.\n *\n * A REAL BUG THE PORT FIXES: the legacy panel is `w-3/4 sm:max-w-sm`, and\n * `sm:max-w-sm` names `--container-sm`, which this repo does not define --\n * so in Alfred it would compile to NOTHING and the sheet would be 75% of\n * the viewport at every width, on a phone and on a 4K monitor alike. The\n * width here is a spacing-scale step that actually resolves.\n *\n * NOT PORTED: the slide-in animation. It comes from tailwindcss-animate,\n * which this repo does not ship. React Aria exposes `data-entering` and\n * `data-exiting`, and `--animate-*` tokens now exist, so it is a small\n * follow-up rather than a blocker.\n */\nexport const sheetOverlayVariants = cva(\"p-0\", {\n variants: {\n // The overlay is a flex box centring the panel. A sheet replaces that\n // centring with an edge alignment rather than positioning the panel\n // absolutely, so the scrim and the panel stay one layout.\n side: {\n left: \"justify-start items-stretch\",\n right: \"justify-end items-stretch\",\n },\n },\n defaultVariants: { side: \"right\" },\n});\n\nexport const sheetPanelVariants = cva(\n [\n // max-w-96 is 384px and RESOLVES. See the note above on sm:max-w-sm.\n \"h-full w-full max-w-96\",\n // Square against the edge it is attached to: a rounded corner flush\n // with the viewport edge reads as a rendering fault.\n \"rounded-none\",\n \"max-h-full overflow-y-auto\",\n ],\n {\n variants: {\n side: {\n left: \"border-y-0 border-s-0\",\n right: \"border-y-0 border-e-0\",\n },\n },\n defaultVariants: { side: \"right\" },\n },\n);\n\n/**\n * A panel that arrives from the side.\n *\n * @example\n * <Sheet isOpen={open} onOpenChange={setOpen} title=\"Filters\">\n * ...\n * </Sheet>\n */\nexport interface SheetProps extends Omit<DialogProps, \"overlayClassName\"> {\n /**\n * Which edge it is attached to.\n *\n * `\"top\"` and `\"bottom\"` exist in the legacy and have zero call sites.\n *\n * @default \"right\"\n */\n side?: \"left\" | \"right\";\n /**\n * Show a back control, and what it does.\n *\n * A side panel that goes a level deeper REPLACES its own contents rather\n * than stacking a second modal on top, so the browser's back affordance and\n * the escape key both do the wrong thing -- they close the whole panel\n * instead of returning one level. The product's own note calls this the\n * thing that \"makes items 119 and 120 one change rather than two\".\n */\n onBack?: () => void;\n /** Accessible name and label for the back control. @default \"Back\" */\n backLabel?: string;\n /**\n * Change this when the panel's contents change, and the body scrolls back\n * to the top.\n *\n * Going a level deeper and landing halfway down the new view is\n * disorienting, and it is the kind of thing every call site forgets --\n * which is why it belongs here rather than in each of them.\n */\n scrollResetKey?: string | number;\n /** Extra classes for the scrolling body region. */\n bodyClassName?: string;\n /**\n * Names the scrolling body region. Defaults to `title` when that is a\n * string, which it is at every call site in the product.\n *\n * The body is a `ScrollArea`, so it is focusable -- a scrollable region that\n * is not reachable by keyboard is WCAG 2.1.1, and axe caught exactly that\n * when this was a plain div. The product's own `SidePanelBody` still is one.\n * A focusable element needs a role and a role needs a name, so this is the\n * only combination that is actually correct.\n */\n bodyLabel?: string;\n}\n\nfunction BackArrowGlyph() {\n return (\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n // Logical, so it points the right way once the panel mirrors in RTL.\n className=\"size-(--icon-sm) rtl:-scale-x-100\"\n >\n <path d=\"M10 3 5 8l5 5\" />\n </svg>\n );\n}\n\nexport function Sheet({\n side = \"right\",\n className,\n onBack,\n backLabel = \"Back\",\n scrollResetKey,\n bodyClassName,\n bodyLabel,\n title,\n children,\n ...props\n}: SheetProps) {\n return (\n /*\n No data-slot here. Dialog spreads its props onto the ModalOverlay and\n writes data-slot=\"dialog-overlay\" AFTER the spread, so a data-slot\n passed from here is overwritten before it reaches the DOM -- it read as\n working while rendering nothing. A sheet is identified by its side\n variant and its panel classes instead.\n */\n <Dialog\n {...props}\n title={title}\n beforeTitle={\n onBack ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n data-slot=\"sheet-back\"\n className=\"-ms-2 w-fit\"\n startIcon={<BackArrowGlyph />}\n onPress={onBack}\n >\n {backLabel}\n </Button>\n ) : undefined\n }\n overlayClassName={sheetOverlayVariants({ side })}\n className={cn(sheetPanelVariants({ side }), className)}\n >\n {/*\n The scrolling region is its own element so the title and the back\n control stay put while the content moves. A panel that scrolled as one\n block would take its own heading off screen.\n */}\n {/*\n No data-slot=\"sheet-body\" here, for the same reason the overlay has\n none: ScrollArea spreads its props and writes data-slot=\"scroll-area\"\n AFTER the spread, so one passed from here is overwritten before it\n reaches the DOM. It read as wired while selecting nothing.\n */}\n <ScrollArea\n scrollResetKey={scrollResetKey}\n label={bodyLabel ?? (typeof title === \"string\" ? title : \"\")}\n className={cn(\"min-h-0 flex-1\", bodyClassName)}\n >\n {children}\n </ScrollArea>\n </Dialog>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,MAAa,uBAAuB,IAAI,OAAO;CAC7C,UAAU,EAIR,MAAM;EACJ,MAAM;EACN,OAAO;CACT,EACF;CACA,iBAAiB,EAAE,MAAM,QAAQ;AACnC,CAAC;AAED,MAAa,qBAAqB,IAChC;CAEE;CAGA;CACA;AACF,GACA;CACE,UAAU,EACR,MAAM;EACJ,MAAM;EACN,OAAO;CACT,EACF;CACA,iBAAiB,EAAE,MAAM,QAAQ;AACnC,CACF;AAuDA,SAAS,iBAAiB;CACxB,OACE,oBAAC,OAAD;EACE,eAAY;EACZ,SAAQ;EACR,MAAK;EACL,QAAO;EACP,aAAY;EACZ,eAAc;EACd,gBAAe;EAEf,WAAU;EAEV,UAAA,oBAAC,QAAD,EAAM,GAAE,gBAAiB,CAAA;CACtB,CAAA;AAET;AAEA,SAAgB,MAAM,EACpB,OAAO,SACP,WACA,QACA,YAAY,QACZ,gBACA,eACA,WACA,OACA,UACA,GAAG,SACU;CACb,OAQE,oBAAC,QAAD;EACE,GAAI;EACG;EACP,aACE,SACE,oBAAC,QAAD;GACE,SAAQ;GACR,MAAK;GACL,aAAU;GACV,WAAU;GACV,WAAW,oBAAC,gBAAD,CAAiB,CAAA;GAC5B,SAAS;GAER,UAAA;EACK,CAAA,IACN,KAAA;EAEN,kBAAkB,qBAAqB,EAAE,KAAK,CAAC;EAC/C,WAAW,GAAG,mBAAmB,EAAE,KAAK,CAAC,GAAG,SAAS;EAarD,UAAA,oBAAC,YAAD;GACkB;GAChB,OAAO,cAAc,OAAO,UAAU,WAAW,QAAQ;GACzD,WAAW,GAAG,kBAAkB,aAAa;GAE5C;EACS,CAAA;CACN,CAAA;AAEZ"}
1
+ {"version":3,"file":"sheet.js","names":[],"sources":["../../src/sheet/sheet.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef } from \"react\";\n\nimport { Button } from \"../button/button.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { Dialog, type DialogProps } from \"../dialog/dialog.js\";\nimport { ScrollArea } from \"../scroll-area/scroll-area.js\";\n\n/**\n * Sheet: a dialog that arrives from an edge.\n *\n * A PRESET over `Dialog`, like `AlertDialog`. The legacy `sheet.tsx` is\n * `@radix-ui/react-dialog` again -- the same import, the same parts, the\n * same close button -- with one prop changing where it sits. Keeping a\n * second copy is how the legacy `dialog.tsx` and `alert-dialog.tsx`\n * drifted, and this is the same file for the third time.\n *\n * Census: 2 files, 3 uses. `side=\"right\"` twice, `side=\"left\"` once, and\n * `className` four times. `SheetHeader`, `SheetFooter`, `SheetTitle`,\n * `SheetDescription` and `SheetClose` are exported by the legacy and used\n * by NOBODY -- so all three sheets in the app render a `role=\"dialog\"`\n * with no title at all. Radix even warns about it in the console.\n *\n * `title` is required here, inherited from Dialog, so that is no longer\n * expressible.\n *\n * `side` is `\"left\" | \"right\"` only. The legacy also offers `\"top\"` and\n * `\"bottom\"`; both have zero call sites, and each is two lines to add the\n * day something needs one.\n *\n * A REAL BUG THE PORT FIXES: the legacy panel is `w-3/4 sm:max-w-sm`, and\n * `sm:max-w-sm` names `--container-sm`, which this repo does not define --\n * so in Alfred it would compile to NOTHING and the sheet would be 75% of\n * the viewport at every width, on a phone and on a 4K monitor alike. The\n * width here is a spacing-scale step that actually resolves.\n *\n * NOT PORTED: the slide-in animation. It comes from tailwindcss-animate,\n * which this repo does not ship. React Aria exposes `data-entering` and\n * `data-exiting`, and `--animate-*` tokens now exist, so it is a small\n * follow-up rather than a blocker.\n */\nexport const sheetOverlayVariants = cva(\"p-0\", {\n variants: {\n // The overlay is a flex box centring the panel. A sheet replaces that\n // centring with an edge alignment rather than positioning the panel\n // absolutely, so the scrim and the panel stay one layout.\n side: {\n left: \"justify-start items-stretch\",\n right: \"justify-end items-stretch\",\n },\n },\n defaultVariants: { side: \"right\" },\n});\n\nexport const sheetPanelVariants = cva(\n [\n // max-w-96 is 384px and RESOLVES. See the note above on sm:max-w-sm.\n \"h-full w-full max-w-96\",\n // Square against the edge it is attached to: a rounded corner flush\n // with the viewport edge reads as a rendering fault.\n \"rounded-none\",\n \"max-h-full overflow-y-auto\",\n ],\n {\n variants: {\n side: {\n left: \"border-y-0 border-s-0\",\n right: \"border-y-0 border-e-0\",\n },\n },\n defaultVariants: { side: \"right\" },\n },\n);\n\n/**\n * A panel that arrives from the side.\n *\n * @example\n * <Sheet isOpen={open} onOpenChange={setOpen} title=\"Filters\">\n * ...\n * </Sheet>\n */\nexport interface SheetProps extends Omit<DialogProps, \"overlayClassName\"> {\n /**\n * Which edge it is attached to.\n *\n * `\"top\"` and `\"bottom\"` exist in the legacy and have zero call sites.\n *\n * @default \"right\"\n */\n side?: \"left\" | \"right\";\n /**\n * Show a back control, and what it does.\n *\n * A side panel that goes a level deeper REPLACES its own contents rather\n * than stacking a second modal on top, so the browser's back affordance and\n * the escape key both do the wrong thing -- they close the whole panel\n * instead of returning one level. The product's own note calls this the\n * thing that \"makes items 119 and 120 one change rather than two\".\n */\n onBack?: () => void;\n /** Accessible name and label for the back control. @default \"Back\" */\n backLabel?: string;\n /**\n * Change this when the panel's contents change, and the body scrolls back\n * to the top.\n *\n * Going a level deeper and landing halfway down the new view is\n * disorienting, and it is the kind of thing every call site forgets --\n * which is why it belongs here rather than in each of them.\n */\n scrollResetKey?: string | number;\n /** Extra classes for the scrolling body region. */\n bodyClassName?: string;\n /**\n * Names the scrolling body region. Defaults to `title` when that is a\n * string, which it is at every call site in the product.\n *\n * The body is a `ScrollArea`, so it is focusable -- a scrollable region that\n * is not reachable by keyboard is WCAG 2.1.1, and axe caught exactly that\n * when this was a plain div. The product's own `SidePanelBody` still is one.\n * A focusable element needs a role and a role needs a name, so this is the\n * only combination that is actually correct.\n */\n bodyLabel?: string;\n}\n\nfunction BackArrowGlyph() {\n return (\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n // Logical, so it points the right way once the panel mirrors in RTL.\n className=\"size-(--icon-sm) rtl:-scale-x-100\"\n >\n <path d=\"M10 3 5 8l5 5\" />\n </svg>\n );\n}\n\nexport const Sheet = forwardRef<HTMLElement, SheetProps>(function Sheet(\n {\n side = \"right\",\n className,\n onBack,\n backLabel = \"Back\",\n scrollResetKey,\n bodyClassName,\n bodyLabel,\n title,\n children,\n ...props\n },\n ref,\n) {\n return (\n /*\n No data-slot here. Dialog spreads its props onto the ModalOverlay and\n writes data-slot=\"dialog-overlay\" AFTER the spread, so a data-slot\n passed from here is overwritten before it reaches the DOM -- it read as\n working while rendering nothing. A sheet is identified by its side\n variant and its panel classes instead.\n */\n <Dialog\n {...props}\n ref={ref}\n title={title}\n beforeTitle={\n onBack ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n data-slot=\"sheet-back\"\n className=\"-ms-2 w-fit\"\n startIcon={<BackArrowGlyph />}\n onPress={onBack}\n >\n {backLabel}\n </Button>\n ) : undefined\n }\n overlayClassName={sheetOverlayVariants({ side })}\n className={cn(sheetPanelVariants({ side }), className)}\n >\n {/*\n The scrolling region is its own element so the title and the back\n control stay put while the content moves. A panel that scrolled as one\n block would take its own heading off screen.\n */}\n {/*\n No data-slot=\"sheet-body\" here, for the same reason the overlay has\n none: ScrollArea spreads its props and writes data-slot=\"scroll-area\"\n AFTER the spread, so one passed from here is overwritten before it\n reaches the DOM. It read as wired while selecting nothing.\n */}\n <ScrollArea\n scrollResetKey={scrollResetKey}\n label={bodyLabel ?? (typeof title === \"string\" ? title : \"\")}\n className={cn(\"min-h-0 flex-1\", bodyClassName)}\n >\n {children}\n </ScrollArea>\n </Dialog>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,MAAa,uBAAuB,IAAI,OAAO;CAC7C,UAAU,EAIR,MAAM;EACJ,MAAM;EACN,OAAO;CACT,EACF;CACA,iBAAiB,EAAE,MAAM,QAAQ;AACnC,CAAC;AAED,MAAa,qBAAqB,IAChC;CAEE;CAGA;CACA;AACF,GACA;CACE,UAAU,EACR,MAAM;EACJ,MAAM;EACN,OAAO;CACT,EACF;CACA,iBAAiB,EAAE,MAAM,QAAQ;AACnC,CACF;AAuDA,SAAS,iBAAiB;CACxB,OACE,oBAAC,OAAD;EACE,eAAY;EACZ,SAAQ;EACR,MAAK;EACL,QAAO;EACP,aAAY;EACZ,eAAc;EACd,gBAAe;EAEf,WAAU;EAEV,UAAA,oBAAC,QAAD,EAAM,GAAE,gBAAiB,CAAA;CACtB,CAAA;AAET;AAEA,MAAa,QAAQ,WAAoC,SAAS,MAChE,EACE,OAAO,SACP,WACA,QACA,YAAY,QACZ,gBACA,eACA,WACA,OACA,UACA,GAAG,SAEL,KACA;CACA,OAQE,oBAAC,QAAD;EACE,GAAI;EACC;EACE;EACP,aACE,SACE,oBAAC,QAAD;GACE,SAAQ;GACR,MAAK;GACL,aAAU;GACV,WAAU;GACV,WAAW,oBAAC,gBAAD,CAAiB,CAAA;GAC5B,SAAS;GAER,UAAA;EACK,CAAA,IACN,KAAA;EAEN,kBAAkB,qBAAqB,EAAE,KAAK,CAAC;EAC/C,WAAW,GAAG,mBAAmB,EAAE,KAAK,CAAC,GAAG,SAAS;EAarD,UAAA,oBAAC,YAAD;GACkB;GAChB,OAAO,cAAc,OAAO,UAAU,WAAW,QAAQ;GACzD,WAAW,GAAG,kBAAkB,aAAa;GAE5C;EACS,CAAA;CACN,CAAA;AAEZ,CAAC"}
@@ -28,6 +28,6 @@ export declare const skeletonVariants: (props?: import("class-variance-authority
28
28
  * <Skeleton className="h-4 w-32" />
29
29
  */
30
30
  export type SkeletonProps = ComponentPropsWithoutRef<"div">;
31
- export declare function Skeleton({ className, ...props }: SkeletonProps): import("react").JSX.Element;
31
+ export declare const Skeleton: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
32
32
  //#endregion
33
33
  //# sourceMappingURL=skeleton.d.ts.map
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { cva } from "class-variance-authority";
4
+ import { forwardRef } from "react";
4
5
  import { jsx } from "react/jsx-runtime";
5
6
  //#region src/skeleton/skeleton.tsx
6
7
  /**
@@ -28,14 +29,15 @@ const skeletonVariants = cva([
28
29
  "motion-safe:animate-pulse",
29
30
  "box-border"
30
31
  ]);
31
- function Skeleton({ className, ...props }) {
32
+ const Skeleton = forwardRef(function Skeleton({ className, ...props }, ref) {
32
33
  return /* @__PURE__ */ jsx("div", {
34
+ ref,
33
35
  ...props,
34
36
  "aria-hidden": "true",
35
37
  "data-slot": "skeleton",
36
38
  className: cn(skeletonVariants(), className)
37
39
  });
38
- }
40
+ });
39
41
  //#endregion
40
42
  export { Skeleton, skeletonVariants };
41
43
 
@@ -1 +1 @@
1
- {"version":3,"file":"skeleton.js","names":[],"sources":["../../src/skeleton/skeleton.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport type { ComponentPropsWithoutRef } from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Skeleton, ported from the legacy shadcn component.\n *\n * Census: 2 files, 33 uses, and every single one passes `className` and\n * nothing else -- the size always comes from the call site. So there is no\n * variant axis to invent here; the component is a surface and a pulse.\n *\n * THE PULSE DID NOT EXIST. `animate-pulse` names `--animate-pulse`, which\n * this repo does not define: it imports only `tailwindcss/utilities.css`,\n * never Tailwind's default theme, so both the custom property and the\n * `@keyframes pulse` are absent and the utility emits NO RULE AT ALL. The\n * class was present, tsc and ESLint were happy, and nothing moved. Same\n * failure mode as the Dialog's `max-w-lg`. This port adds the token\n * (`scale.json` -> `--animate-pulse`) and the keyframes\n * (`@seekalfred/styles/motion.css`).\n *\n * `bg-accent` becomes `bg-muted`: in the legacy theme `--accent` is\n * character-for-character `--muted` in both themes, so this is a rename to\n * the role Alfred actually has, not a colour change.\n */\nexport const skeletonVariants = cva([\n \"block bg-muted rounded-md\",\n // motion-safe: a full-screen page of these pulsing is exactly what\n // prefers-reduced-motion exists for. Without the animation it is still a\n // visible placeholder, which is the whole job.\n \"motion-safe:animate-pulse\",\n // The reset in @seekalfred/styles is OPT-IN, so a consumer may load none,\n // and every call site sizes this through className -- content-box would\n // add any padding they set on top of the height they asked for.\n \"box-border\",\n]);\n\n/**\n * A loading placeholder.\n *\n * @example\n * <Skeleton className=\"h-4 w-32\" />\n */\nexport type SkeletonProps = ComponentPropsWithoutRef<\"div\">;\n\nexport function Skeleton({ className, ...props }: SkeletonProps) {\n return (\n <div\n {...props}\n // aria-hidden: a placeholder is not content. Thirty-three empty boxes\n // announced while a page loads is noise, and the loading STATE belongs\n // on the region being replaced (aria-busy), which only the consumer\n // can place.\n aria-hidden=\"true\"\n data-slot=\"skeleton\"\n className={cn(skeletonVariants(), className)}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,MAAa,mBAAmB,IAAI;CAClC;CAIA;CAIA;AACF,CAAC;AAUD,SAAgB,SAAS,EAAE,WAAW,GAAG,SAAwB;CAC/D,OACE,oBAAC,OAAD;EACE,GAAI;EAKJ,eAAY;EACZ,aAAU;EACV,WAAW,GAAG,iBAAiB,GAAG,SAAS;CAC5C,CAAA;AAEL"}
1
+ {"version":3,"file":"skeleton.js","names":[],"sources":["../../src/skeleton/skeleton.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ComponentPropsWithoutRef } from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Skeleton, ported from the legacy shadcn component.\n *\n * Census: 2 files, 33 uses, and every single one passes `className` and\n * nothing else -- the size always comes from the call site. So there is no\n * variant axis to invent here; the component is a surface and a pulse.\n *\n * THE PULSE DID NOT EXIST. `animate-pulse` names `--animate-pulse`, which\n * this repo does not define: it imports only `tailwindcss/utilities.css`,\n * never Tailwind's default theme, so both the custom property and the\n * `@keyframes pulse` are absent and the utility emits NO RULE AT ALL. The\n * class was present, tsc and ESLint were happy, and nothing moved. Same\n * failure mode as the Dialog's `max-w-lg`. This port adds the token\n * (`scale.json` -> `--animate-pulse`) and the keyframes\n * (`@seekalfred/styles/motion.css`).\n *\n * `bg-accent` becomes `bg-muted`: in the legacy theme `--accent` is\n * character-for-character `--muted` in both themes, so this is a rename to\n * the role Alfred actually has, not a colour change.\n */\nexport const skeletonVariants = cva([\n \"block bg-muted rounded-md\",\n // motion-safe: a full-screen page of these pulsing is exactly what\n // prefers-reduced-motion exists for. Without the animation it is still a\n // visible placeholder, which is the whole job.\n \"motion-safe:animate-pulse\",\n // The reset in @seekalfred/styles is OPT-IN, so a consumer may load none,\n // and every call site sizes this through className -- content-box would\n // add any padding they set on top of the height they asked for.\n \"box-border\",\n]);\n\n/**\n * A loading placeholder.\n *\n * @example\n * <Skeleton className=\"h-4 w-32\" />\n */\nexport type SkeletonProps = ComponentPropsWithoutRef<\"div\">;\n\nexport const Skeleton = forwardRef<HTMLDivElement, SkeletonProps>(function Skeleton(\n { className, ...props },\n ref,\n) {\n return (\n <div\n ref={ref}\n {...props}\n // aria-hidden: a placeholder is not content. Thirty-three empty boxes\n // announced while a page loads is noise, and the loading STATE belongs\n // on the region being replaced (aria-busy), which only the consumer\n // can place.\n aria-hidden=\"true\"\n data-slot=\"skeleton\"\n className={cn(skeletonVariants(), className)}\n />\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,MAAa,mBAAmB,IAAI;CAClC;CAIA;CAIA;AACF,CAAC;AAUD,MAAa,WAAW,WAA0C,SAAS,SACzE,EAAE,WAAW,GAAG,SAChB,KACA;CACA,OACE,oBAAC,OAAD;EACO;EACL,GAAI;EAKJ,eAAY;EACZ,aAAU;EACV,WAAW,GAAG,iBAAiB,GAAG,SAAS;CAC5C,CAAA;AAEL,CAAC"}
@@ -1,5 +1,5 @@
1
- import { SliderProps } from "react-aria-components";
2
1
  import { ReactNode } from "react";
2
+ import { SliderProps } from "react-aria-components";
3
3
  //#region src/slider/slider.d.ts
4
4
  /**
5
5
  * Slider, ported from the legacy shadcn/Radix component.
@@ -64,7 +64,7 @@ interface SliderProps$1 extends Omit<SliderProps<number>, "className" | "style"
64
64
  /** Extra classes. Merged last, so they win conflicts with ours. */
65
65
  className?: string;
66
66
  }
67
- declare function Slider$1({ label, labelHidden, showValue, className, ...props }: SliderProps$1): import("react").JSX.Element;
67
+ declare const Slider$1: import("react").ForwardRefExoticComponent<SliderProps$1 & import("react").RefAttributes<HTMLDivElement>>;
68
68
  //#endregion
69
69
  export { Slider$1 as Slider, SliderProps$1 as SliderProps };
70
70
  //# sourceMappingURL=slider.d.ts.map
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { cn, cnAll } from "../lib/cn.js";
3
3
  import { cva } from "class-variance-authority";
4
+ import { forwardRef } from "react";
4
5
  import { Label, Slider, SliderOutput, SliderThumb, SliderTrack } from "react-aria-components";
5
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
7
  //#region src/slider/slider.tsx
@@ -50,8 +51,9 @@ const sliderThumbVariants = cva([
50
51
  "data-[focus-visible]:outline-(--color-ring)",
51
52
  "data-[disabled]:cursor-not-allowed"
52
53
  ]);
53
- function Slider$1({ label, labelHidden, showValue = true, className, ...props }) {
54
+ const Slider$1 = forwardRef(function Slider$2({ label, labelHidden, showValue = true, className, ...props }, ref) {
54
55
  return /* @__PURE__ */ jsxs(Slider, {
56
+ ref,
55
57
  ...props,
56
58
  "data-slot": "slider",
57
59
  className: cn(sliderVariants(), className),
@@ -85,7 +87,7 @@ function Slider$1({ label, labelHidden, showValue = true, className, ...props })
85
87
  ] })
86
88
  })]
87
89
  });
88
- }
90
+ });
89
91
  //#endregion
90
92
  export { Slider$1 as Slider, sliderBarVariants, sliderFillVariants, sliderThumbVariants, sliderTrackVariants, sliderVariants };
91
93
 
@@ -1 +1 @@
1
- {"version":3,"file":"slider.js","names":["Slider","AriaSlider","AriaLabel","AriaSliderOutput","AriaSliderTrack","AriaSliderThumb"],"sources":["../../src/slider/slider.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport type { ReactNode } from \"react\";\nimport {\n Label as AriaLabel,\n Slider as AriaSlider,\n SliderOutput as AriaSliderOutput,\n SliderThumb as AriaSliderThumb,\n SliderTrack as AriaSliderTrack,\n type SliderProps as AriaSliderProps,\n} from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\n\n/**\n * Slider, ported from the legacy shadcn/Radix component.\n *\n * Census: 4 call sites, all single-thumb, none of them named. Radix only\n * generates thumb labels at two or more thumbs, so all four are announced\n * as an unnamed slider -- `AudioSettings.tsx` even renders a\n * `<Label>Input Volume</Label>` directly above one, with no `htmlFor` and\n * no `id`, so it is associated with nothing at all.\n *\n * `label` IS REQUIRED here, the same answer Progress and Dialog give.\n *\n * THE PALETTE DELIBERATELY DOES NOT MATCH SWITCH, and the reason is worth\n * stating because the two look like the same problem. A Switch has no\n * fill: its track is one colour and the thumb tells you the state, so\n * `border` unchecked and `primary-strong` checked works. A Slider has a\n * filled portion whose LENGTH is the value -- and `primary-strong` on a\n * `border` track is 1.01:1. The two halves would be the same grey and the\n * value would be invisible to anyone not reading the thumb position.\n *\n * So the track is `muted` with a `border` boundary and the fill is\n * `primary-strong`: fill against track 4.61:1, track against page 5.25:1,\n * thumb against fill 5.20:1, thumb edge against track 4.65:1. All four\n * pairs are in the build-failing contrast contract.\n *\n * Three legacy defects go with it: the focus ring was `ring-1`, a 1px\n * box-shadow that forced-colors discards entirely; the thumb border was\n * `border-primary/50`, an alpha edge with no measurable contrast; and the\n * disabled styling used `disabled:`, a pseudo-class that only applies to\n * form elements -- Radix renders a `<span role=\"slider\">`, so a disabled\n * slider looked exactly like an enabled one.\n */\nexport const sliderVariants = cva([\"flex w-full flex-col gap-2 box-border font-sans\"]);\n\nexport const sliderTrackVariants = cva([\n \"relative flex w-full items-center\",\n // h-6 is the TRACK ROW, not the visible bar -- it gives the 24px\n // pointer target WCAG 2.5.8 wants. The bar itself is drawn inside.\n \"h-6\",\n]);\n\nexport const sliderBarVariants = cva([\n \"absolute h-2 w-full rounded-pill box-border\",\n \"bg-muted border-(length:--border-width) border-border\",\n]);\n\nexport const sliderFillVariants = cva([\"absolute h-2 rounded-pill bg-primary-strong\"]);\n\nexport const sliderThumbVariants = cva([\n // size-6 is 24px: WCAG 2.5.8 target size. The legacy thumb was 16px at\n // all four call sites, and one of them crams the whole control into 80px.\n \"size-6 rounded-full box-border\",\n \"bg-background border-(length:--border-width) border-border shadow-surface\",\n \"cursor-grab data-[dragging]:cursor-grabbing\",\n // A real outline, not the legacy ring-1 box-shadow.\n \"outline-none\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n // data-[disabled], NOT disabled:. The legacy used the CSS :disabled\n // pseudo-class, which applies only to form elements -- React Aria and\n // Radix both render a span, so that styling never fired.\n \"data-[disabled]:cursor-not-allowed\",\n]);\n\n/**\n * A value along a range.\n *\n * @example\n * <Slider label=\"Input volume\" defaultValue={40} />\n * <Slider label=\"Playback position\" value={t} onChange={seek} formatOptions={{ style: \"percent\" }} />\n */\nexport interface SliderProps\n extends Omit<AriaSliderProps<number>, \"className\" | \"style\" | \"children\"> {\n /**\n * What the slider controls, and its accessible name.\n *\n * **Required.** All four legacy call sites are unnamed sliders.\n */\n label: ReactNode;\n /** Render the label for screen readers only. */\n labelHidden?: boolean;\n /**\n * Show the current value as text beside the label.\n *\n * On by default. It is the text equivalent of the fill length, which\n * matters because in greyscale the filled and unfilled halves are\n * distinguishable only by the thumb.\n */\n showValue?: boolean;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport function Slider({\n label,\n labelHidden,\n showValue = true,\n className,\n ...props\n}: SliderProps) {\n return (\n <AriaSlider {...props} data-slot=\"slider\" className={cn(sliderVariants(), className)}>\n <div className={cnAll(\"flex items-center justify-between gap-2\", labelHidden && \"sr-only\")}>\n <AriaLabel\n data-slot=\"slider-label\"\n className=\"text-sm leading-(--leading-normal) text-foreground\"\n >\n {label}\n </AriaLabel>\n {showValue ? (\n <AriaSliderOutput\n data-slot=\"slider-output\"\n className=\"text-sm leading-(--leading-normal) text-muted-foreground\"\n />\n ) : null}\n </div>\n\n <AriaSliderTrack data-slot=\"slider-track\" className={sliderTrackVariants()}>\n {({ state }) => (\n <>\n <div data-slot=\"slider-bar\" className={sliderBarVariants()} />\n <div\n data-slot=\"slider-fill\"\n className={sliderFillVariants()}\n style={{ width: `${state.getThumbPercent(0) * 100}%` }}\n />\n <AriaSliderThumb data-slot=\"slider-thumb\" className={sliderThumbVariants()} />\n </>\n )}\n </AriaSliderTrack>\n </AriaSlider>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,MAAa,iBAAiB,IAAI,CAAC,iDAAiD,CAAC;AAErF,MAAa,sBAAsB,IAAI,CACrC,qCAGA,KACF,CAAC;AAED,MAAa,oBAAoB,IAAI,CACnC,+CACA,uDACF,CAAC;AAED,MAAa,qBAAqB,IAAI,CAAC,6CAA6C,CAAC;AAErF,MAAa,sBAAsB,IAAI;CAGrC;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CAIA;AACF,CAAC;AA+BD,SAAgBA,SAAO,EACrB,OACA,aACA,YAAY,MACZ,WACA,GAAG,SACW;CACd,OACE,qBAACC,QAAD;EAAY,GAAI;EAAO,aAAU;EAAS,WAAW,GAAG,eAAe,GAAG,SAAS;EAAnF,UAAA,CACE,qBAAC,OAAD;GAAK,WAAW,MAAM,2CAA2C,eAAe,SAAS;GAAzF,UAAA,CACE,oBAACC,OAAD;IACE,aAAU;IACV,WAAU;IAET,UAAA;GACQ,CAAA,GACV,YACC,oBAACC,cAAD;IACE,aAAU;IACV,WAAU;GACX,CAAA,IACC,IACD;EAEL,CAAA,GAAA,oBAACC,aAAD;GAAiB,aAAU;GAAe,WAAW,oBAAoB;GACrE,WAAA,EAAE,YACF,qBAAA,UAAA,EAAA,UAAA;IACE,oBAAC,OAAD;KAAK,aAAU;KAAa,WAAW,kBAAkB;IAAI,CAAA;IAC7D,oBAAC,OAAD;KACE,aAAU;KACV,WAAW,mBAAmB;KAC9B,OAAO,EAAE,OAAO,GAAG,MAAM,gBAAgB,CAAC,IAAI,IAAI,GAAG;IACtD,CAAA;IACD,oBAACC,aAAD;KAAiB,aAAU;KAAe,WAAW,oBAAoB;IAAI,CAAA;GAC7E,EAAA,CAAA;EAEW,CAAA,CACP;;AAEhB"}
1
+ {"version":3,"file":"slider.js","names":["Slider","AriaSlider","AriaLabel","AriaSliderOutput","AriaSliderTrack","AriaSliderThumb"],"sources":["../../src/slider/slider.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n Label as AriaLabel,\n Slider as AriaSlider,\n SliderOutput as AriaSliderOutput,\n SliderThumb as AriaSliderThumb,\n SliderTrack as AriaSliderTrack,\n type SliderProps as AriaSliderProps,\n} from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\n\n/**\n * Slider, ported from the legacy shadcn/Radix component.\n *\n * Census: 4 call sites, all single-thumb, none of them named. Radix only\n * generates thumb labels at two or more thumbs, so all four are announced\n * as an unnamed slider -- `AudioSettings.tsx` even renders a\n * `<Label>Input Volume</Label>` directly above one, with no `htmlFor` and\n * no `id`, so it is associated with nothing at all.\n *\n * `label` IS REQUIRED here, the same answer Progress and Dialog give.\n *\n * THE PALETTE DELIBERATELY DOES NOT MATCH SWITCH, and the reason is worth\n * stating because the two look like the same problem. A Switch has no\n * fill: its track is one colour and the thumb tells you the state, so\n * `border` unchecked and `primary-strong` checked works. A Slider has a\n * filled portion whose LENGTH is the value -- and `primary-strong` on a\n * `border` track is 1.01:1. The two halves would be the same grey and the\n * value would be invisible to anyone not reading the thumb position.\n *\n * So the track is `muted` with a `border` boundary and the fill is\n * `primary-strong`: fill against track 4.61:1, track against page 5.25:1,\n * thumb against fill 5.20:1, thumb edge against track 4.65:1. All four\n * pairs are in the build-failing contrast contract.\n *\n * Three legacy defects go with it: the focus ring was `ring-1`, a 1px\n * box-shadow that forced-colors discards entirely; the thumb border was\n * `border-primary/50`, an alpha edge with no measurable contrast; and the\n * disabled styling used `disabled:`, a pseudo-class that only applies to\n * form elements -- Radix renders a `<span role=\"slider\">`, so a disabled\n * slider looked exactly like an enabled one.\n */\nexport const sliderVariants = cva([\"flex w-full flex-col gap-2 box-border font-sans\"]);\n\nexport const sliderTrackVariants = cva([\n \"relative flex w-full items-center\",\n // h-6 is the TRACK ROW, not the visible bar -- it gives the 24px\n // pointer target WCAG 2.5.8 wants. The bar itself is drawn inside.\n \"h-6\",\n]);\n\nexport const sliderBarVariants = cva([\n \"absolute h-2 w-full rounded-pill box-border\",\n \"bg-muted border-(length:--border-width) border-border\",\n]);\n\nexport const sliderFillVariants = cva([\"absolute h-2 rounded-pill bg-primary-strong\"]);\n\nexport const sliderThumbVariants = cva([\n // size-6 is 24px: WCAG 2.5.8 target size. The legacy thumb was 16px at\n // all four call sites, and one of them crams the whole control into 80px.\n \"size-6 rounded-full box-border\",\n \"bg-background border-(length:--border-width) border-border shadow-surface\",\n \"cursor-grab data-[dragging]:cursor-grabbing\",\n // A real outline, not the legacy ring-1 box-shadow.\n \"outline-none\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n // data-[disabled], NOT disabled:. The legacy used the CSS :disabled\n // pseudo-class, which applies only to form elements -- React Aria and\n // Radix both render a span, so that styling never fired.\n \"data-[disabled]:cursor-not-allowed\",\n]);\n\n/**\n * A value along a range.\n *\n * @example\n * <Slider label=\"Input volume\" defaultValue={40} />\n * <Slider label=\"Playback position\" value={t} onChange={seek} formatOptions={{ style: \"percent\" }} />\n */\nexport interface SliderProps\n extends Omit<AriaSliderProps<number>, \"className\" | \"style\" | \"children\"> {\n /**\n * What the slider controls, and its accessible name.\n *\n * **Required.** All four legacy call sites are unnamed sliders.\n */\n label: ReactNode;\n /** Render the label for screen readers only. */\n labelHidden?: boolean;\n /**\n * Show the current value as text beside the label.\n *\n * On by default. It is the text equivalent of the fill length, which\n * matters because in greyscale the filled and unfilled halves are\n * distinguishable only by the thumb.\n */\n showValue?: boolean;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport const Slider = forwardRef<HTMLDivElement, SliderProps>(function Slider(\n { label, labelHidden, showValue = true, className, ...props },\n ref,\n) {\n return (\n <AriaSlider\n ref={ref}\n {...props}\n data-slot=\"slider\"\n className={cn(sliderVariants(), className)}\n >\n <div className={cnAll(\"flex items-center justify-between gap-2\", labelHidden && \"sr-only\")}>\n <AriaLabel\n data-slot=\"slider-label\"\n className=\"text-sm leading-(--leading-normal) text-foreground\"\n >\n {label}\n </AriaLabel>\n {showValue ? (\n <AriaSliderOutput\n data-slot=\"slider-output\"\n className=\"text-sm leading-(--leading-normal) text-muted-foreground\"\n />\n ) : null}\n </div>\n\n <AriaSliderTrack data-slot=\"slider-track\" className={sliderTrackVariants()}>\n {({ state }) => (\n <>\n <div data-slot=\"slider-bar\" className={sliderBarVariants()} />\n <div\n data-slot=\"slider-fill\"\n className={sliderFillVariants()}\n style={{ width: `${state.getThumbPercent(0) * 100}%` }}\n />\n <AriaSliderThumb data-slot=\"slider-thumb\" className={sliderThumbVariants()} />\n </>\n )}\n </AriaSliderTrack>\n </AriaSlider>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,MAAa,iBAAiB,IAAI,CAAC,iDAAiD,CAAC;AAErF,MAAa,sBAAsB,IAAI,CACrC,qCAGA,KACF,CAAC;AAED,MAAa,oBAAoB,IAAI,CACnC,+CACA,uDACF,CAAC;AAED,MAAa,qBAAqB,IAAI,CAAC,6CAA6C,CAAC;AAErF,MAAa,sBAAsB,IAAI;CAGrC;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CAIA;AACF,CAAC;AA+BD,MAAaA,WAAS,WAAwC,SAASA,SACrE,EAAE,OAAO,aAAa,YAAY,MAAM,WAAW,GAAG,SACtD,KACA;CACA,OACE,qBAACC,QAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,eAAe,GAAG,SAAS;EAJ3C,UAAA,CAME,qBAAC,OAAD;GAAK,WAAW,MAAM,2CAA2C,eAAe,SAAS;GAAzF,UAAA,CACE,oBAACC,OAAD;IACE,aAAU;IACV,WAAU;IAET,UAAA;GACQ,CAAA,GACV,YACC,oBAACC,cAAD;IACE,aAAU;IACV,WAAU;GACX,CAAA,IACC,IACD;EAEL,CAAA,GAAA,oBAACC,aAAD;GAAiB,aAAU;GAAe,WAAW,oBAAoB;GACrE,WAAA,EAAE,YACF,qBAAA,UAAA,EAAA,UAAA;IACE,oBAAC,OAAD;KAAK,aAAU;KAAa,WAAW,kBAAkB;IAAI,CAAA;IAC7D,oBAAC,OAAD;KACE,aAAU;KACV,WAAW,mBAAmB;KAC9B,OAAO,EAAE,OAAO,GAAG,MAAM,gBAAgB,CAAC,IAAI,IAAI,GAAG;IACtD,CAAA;IACD,oBAACC,aAAD;KAAiB,aAAU;KAAe,WAAW,oBAAoB;IAAI,CAAA;GAC7E,EAAA,CAAA;EAEW,CAAA,CACP;;AAEhB,CAAC"}
@@ -70,6 +70,6 @@ export interface SpinnerProps extends Omit<ComponentPropsWithoutRef<"span">, "ch
70
70
  /** Extra classes. Merged last, so they win conflicts with ours. */
71
71
  className?: string;
72
72
  }
73
- export declare function Spinner({ className, size, label, ...props }: SpinnerProps): import("react").JSX.Element;
73
+ export declare const Spinner: import("react").ForwardRefExoticComponent<SpinnerProps & import("react").RefAttributes<HTMLSpanElement>>;
74
74
  //#endregion
75
75
  //# sourceMappingURL=spinner.d.ts.map
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { cva } from "class-variance-authority";
4
+ import { forwardRef } from "react";
4
5
  import { jsx } from "react/jsx-runtime";
5
6
  //#region src/spinner/spinner.tsx
6
7
  /**
@@ -39,8 +40,9 @@ const spinnerVariants = cva([
39
40
  } },
40
41
  defaultVariants: { size: "md" }
41
42
  });
42
- function Spinner({ className, size, label, ...props }) {
43
+ const Spinner = forwardRef(function Spinner({ className, size, label, ...props }, ref) {
43
44
  return /* @__PURE__ */ jsx("span", {
45
+ ref,
44
46
  ...props,
45
47
  role: "status",
46
48
  "aria-label": label,
@@ -61,7 +63,7 @@ function Spinner({ className, size, label, ...props }) {
61
63
  children: label
62
64
  })
63
65
  });
64
- }
66
+ });
65
67
  //#endregion
66
68
  export { Spinner, spinnerVariants };
67
69