@seekalfred/primitives 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/README.md +975 -13
  2. package/dist/accordion/accordion.d.ts +107 -0
  3. package/dist/accordion/accordion.js +84 -0
  4. package/dist/accordion/accordion.js.map +1 -0
  5. package/dist/accordion/index.d.ts +2 -0
  6. package/dist/accordion/index.js +2 -0
  7. package/dist/alert/alert.d.ts +68 -0
  8. package/dist/alert/alert.js +67 -0
  9. package/dist/alert/alert.js.map +1 -0
  10. package/dist/alert/index.d.ts +2 -0
  11. package/dist/alert/index.js +2 -0
  12. package/dist/alert-dialog/alert-dialog.d.ts +53 -0
  13. package/dist/alert-dialog/alert-dialog.js +16 -0
  14. package/dist/alert-dialog/alert-dialog.js.map +1 -0
  15. package/dist/alert-dialog/index.d.ts +2 -0
  16. package/dist/alert-dialog/index.js +2 -0
  17. package/dist/avatar/avatar.d.ts +50 -0
  18. package/dist/avatar/avatar.js +91 -0
  19. package/dist/avatar/avatar.js.map +1 -0
  20. package/dist/avatar/index.d.ts +2 -0
  21. package/dist/avatar/index.js +2 -0
  22. package/dist/badge/badge.d.ts +47 -0
  23. package/dist/badge/badge.js +189 -0
  24. package/dist/badge/badge.js.map +1 -0
  25. package/dist/badge/index.d.ts +2 -0
  26. package/dist/badge/index.js +2 -0
  27. package/dist/breadcrumb/breadcrumb.d.ts +98 -0
  28. package/dist/breadcrumb/breadcrumb.js +93 -0
  29. package/dist/breadcrumb/breadcrumb.js.map +1 -0
  30. package/dist/breadcrumb/index.d.ts +2 -0
  31. package/dist/breadcrumb/index.js +2 -0
  32. package/dist/button/button.d.ts +1 -1
  33. package/dist/button/button.js +7 -0
  34. package/dist/button/button.js.map +1 -1
  35. package/dist/calendar/calendar.d.ts +154 -0
  36. package/dist/calendar/calendar.js +282 -0
  37. package/dist/calendar/calendar.js.map +1 -0
  38. package/dist/calendar/index.d.ts +2 -0
  39. package/dist/calendar/index.js +2 -0
  40. package/dist/card/card.d.ts +93 -0
  41. package/dist/card/card.js +105 -0
  42. package/dist/card/card.js.map +1 -0
  43. package/dist/card/index.d.ts +2 -0
  44. package/dist/card/index.js +2 -0
  45. package/dist/checkbox/checkbox.d.ts +63 -0
  46. package/dist/checkbox/checkbox.js +94 -0
  47. package/dist/checkbox/checkbox.js.map +1 -0
  48. package/dist/checkbox/index.d.ts +2 -0
  49. package/dist/checkbox/index.js +2 -0
  50. package/dist/dialog/dialog.d.ts +140 -0
  51. package/dist/dialog/dialog.js +127 -0
  52. package/dist/dialog/dialog.js.map +1 -0
  53. package/dist/dialog/index.d.ts +2 -0
  54. package/dist/dialog/index.js +2 -0
  55. package/dist/index.d.ts +30 -1
  56. package/dist/index.js +30 -1
  57. package/dist/input/index.d.ts +2 -0
  58. package/dist/input/index.js +2 -0
  59. package/dist/input/input.d.ts +80 -0
  60. package/dist/input/input.js +195 -0
  61. package/dist/input/input.js.map +1 -0
  62. package/dist/label/index.d.ts +2 -0
  63. package/dist/label/index.js +2 -0
  64. package/dist/label/label.d.ts +41 -0
  65. package/dist/label/label.js +44 -0
  66. package/dist/label/label.js.map +1 -0
  67. package/dist/lib/field-surface.js +40 -0
  68. package/dist/lib/field-surface.js.map +1 -0
  69. package/dist/lib/selection-control.js +57 -0
  70. package/dist/lib/selection-control.js.map +1 -0
  71. package/dist/menu/index.d.ts +2 -0
  72. package/dist/menu/index.js +2 -0
  73. package/dist/menu/menu.d.ts +98 -0
  74. package/dist/menu/menu.js +99 -0
  75. package/dist/menu/menu.js.map +1 -0
  76. package/dist/popover/index.d.ts +2 -0
  77. package/dist/popover/index.js +2 -0
  78. package/dist/popover/popover.d.ts +50 -0
  79. package/dist/popover/popover.js +46 -0
  80. package/dist/popover/popover.js.map +1 -0
  81. package/dist/progress/index.d.ts +2 -0
  82. package/dist/progress/index.js +2 -0
  83. package/dist/progress/progress.d.ts +82 -0
  84. package/dist/progress/progress.js +82 -0
  85. package/dist/progress/progress.js.map +1 -0
  86. package/dist/radio-group/index.d.ts +2 -0
  87. package/dist/radio-group/index.js +2 -0
  88. package/dist/radio-group/radio-group.d.ts +53 -0
  89. package/dist/radio-group/radio-group.js +65 -0
  90. package/dist/radio-group/radio-group.js.map +1 -0
  91. package/dist/scroll-area/index.d.ts +2 -0
  92. package/dist/scroll-area/index.js +2 -0
  93. package/dist/scroll-area/scroll-area.d.ts +74 -0
  94. package/dist/scroll-area/scroll-area.js +63 -0
  95. package/dist/scroll-area/scroll-area.js.map +1 -0
  96. package/dist/select/index.d.ts +2 -0
  97. package/dist/select/index.js +2 -0
  98. package/dist/select/select.d.ts +105 -0
  99. package/dist/select/select.js +149 -0
  100. package/dist/select/select.js.map +1 -0
  101. package/dist/separator/index.d.ts +2 -0
  102. package/dist/separator/index.js +2 -0
  103. package/dist/separator/separator.d.ts +42 -0
  104. package/dist/separator/separator.js +49 -0
  105. package/dist/separator/separator.js.map +1 -0
  106. package/dist/sheet/index.d.ts +2 -0
  107. package/dist/sheet/index.js +2 -0
  108. package/dist/sheet/sheet.d.ts +96 -0
  109. package/dist/sheet/sheet.js +98 -0
  110. package/dist/sheet/sheet.js.map +1 -0
  111. package/dist/skeleton/index.d.ts +2 -0
  112. package/dist/skeleton/index.js +2 -0
  113. package/dist/skeleton/skeleton.d.ts +33 -0
  114. package/dist/skeleton/skeleton.js +42 -0
  115. package/dist/skeleton/skeleton.js.map +1 -0
  116. package/dist/slider/index.d.ts +2 -0
  117. package/dist/slider/index.js +2 -0
  118. package/dist/slider/slider.d.ts +70 -0
  119. package/dist/slider/slider.js +92 -0
  120. package/dist/slider/slider.js.map +1 -0
  121. package/dist/spinner/index.d.ts +2 -0
  122. package/dist/spinner/index.js +2 -0
  123. package/dist/spinner/spinner.d.ts +75 -0
  124. package/dist/spinner/spinner.js +68 -0
  125. package/dist/spinner/spinner.js.map +1 -0
  126. package/dist/styles.css +1 -1
  127. package/dist/switch/index.d.ts +2 -0
  128. package/dist/switch/index.js +2 -0
  129. package/dist/switch/switch.d.ts +50 -0
  130. package/dist/switch/switch.js +66 -0
  131. package/dist/switch/switch.js.map +1 -0
  132. package/dist/table/index.d.ts +2 -0
  133. package/dist/table/index.js +2 -0
  134. package/dist/table/table.d.ts +288 -0
  135. package/dist/table/table.js +513 -0
  136. package/dist/table/table.js.map +1 -0
  137. package/dist/tabs/index.d.ts +2 -0
  138. package/dist/tabs/index.js +2 -0
  139. package/dist/tabs/tabs.d.ts +79 -0
  140. package/dist/tabs/tabs.js +103 -0
  141. package/dist/tabs/tabs.js.map +1 -0
  142. package/dist/textarea/index.d.ts +2 -0
  143. package/dist/textarea/index.js +2 -0
  144. package/dist/textarea/textarea.d.ts +27 -0
  145. package/dist/textarea/textarea.js +37 -0
  146. package/dist/textarea/textarea.js.map +1 -0
  147. package/dist/toast/index.d.ts +2 -0
  148. package/dist/toast/index.js +2 -0
  149. package/dist/toast/toast.d.ts +93 -0
  150. package/dist/toast/toast.js +145 -0
  151. package/dist/toast/toast.js.map +1 -0
  152. package/dist/tooltip/index.d.ts +2 -0
  153. package/dist/tooltip/index.js +2 -0
  154. package/dist/tooltip/tooltip.d.ts +70 -0
  155. package/dist/tooltip/tooltip.js +65 -0
  156. package/dist/tooltip/tooltip.js.map +1 -0
  157. package/package.json +125 -7
@@ -0,0 +1,65 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { selectionLabelVariants, selectionRingVariants } from "../lib/selection-control.js";
4
+ import { cva } from "class-variance-authority";
5
+ import { Radio, RadioGroup } from "react-aria-components";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/radio-group/radio-group.tsx
8
+ /**
9
+ * RadioGroup, ported from the legacy shadcn/Radix component onto React Aria.
10
+ *
11
+ * Census: 1 file, 3 uses -- `RadioGroup` 1, `RadioGroupItem` 2. The
12
+ * smallest real surface in the migration, so the port is deliberately plain.
13
+ *
14
+ * THE LABEL MOVES INSIDE THE ITEM. Radix's `RadioGroupItem` is a bare
15
+ * button and the caller pairs it with a separate `<Label htmlFor>`; React
16
+ * Aria's `Radio` takes its label as `children` and wires the association
17
+ * itself. That is the one rename a call site has to make, and it removes
18
+ * the id bookkeeping.
19
+ *
20
+ * `border-primary` becomes `border-border` for the same reason it did on
21
+ * Checkbox: brand orange is 2.44:1 on white and cannot legally be the only
22
+ * thing describing a control's edge (WCAG 1.4.11). Selected reuses
23
+ * Checkbox's exact pair -- `bg-primary` with a `primary-strong` boundary --
24
+ * so a radio and a checkbox in the same form agree.
25
+ */
26
+ const radioGroupVariants = cva(["flex flex-col gap-2", "box-border font-sans"]);
27
+ const radioVariants = cva([
28
+ "inline-flex size-4 shrink-0 items-center justify-center",
29
+ "rounded-full border-(length:--border-width) box-border",
30
+ "bg-background border-border",
31
+ "motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard",
32
+ "group-data-[selected]:bg-primary group-data-[selected]:border-primary-strong",
33
+ "group-data-[selected]:text-primary-foreground",
34
+ selectionRingVariants()
35
+ ]);
36
+ function RadioGroup$1({ className, ...props }) {
37
+ return /* @__PURE__ */ jsx(RadioGroup, {
38
+ ...props,
39
+ "data-slot": "radio-group",
40
+ className: cn(radioGroupVariants(), className)
41
+ });
42
+ }
43
+ function Radio$1({ className, children, ...props }) {
44
+ return /* @__PURE__ */ jsx(Radio, {
45
+ ...props,
46
+ "data-slot": "radio",
47
+ className: cn(selectionLabelVariants(), "justify-start", className),
48
+ children: ({ isSelected }) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
49
+ "aria-hidden": "true",
50
+ "data-slot": "radio-dot",
51
+ className: radioVariants(),
52
+ children: isSelected ? /* @__PURE__ */ jsx("span", {
53
+ "data-slot": "radio-indicator",
54
+ className: "size-1.5 rounded-full bg-current"
55
+ }) : null
56
+ }), children == null ? null : /* @__PURE__ */ jsx("span", {
57
+ "data-slot": "radio-label",
58
+ children
59
+ })] })
60
+ });
61
+ }
62
+ //#endregion
63
+ export { Radio$1 as Radio, RadioGroup$1 as RadioGroup, radioGroupVariants, radioVariants };
64
+
65
+ //# sourceMappingURL=radio-group.js.map
@@ -0,0 +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 } 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(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,uBAAuB,GAAG,iBAAiB,SAAS;EAEhE,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"}
@@ -0,0 +1,2 @@
1
+ import { ScrollArea, ScrollAreaProps, scrollAreaVariants } from "./scroll-area.js";
2
+ export { ScrollArea, type ScrollAreaProps, scrollAreaVariants };
@@ -0,0 +1,2 @@
1
+ import { ScrollArea, scrollAreaVariants } from "./scroll-area.js";
2
+ export { ScrollArea, scrollAreaVariants };
@@ -0,0 +1,74 @@
1
+ import { ComponentPropsWithoutRef, ReactNode } from "react";
2
+ //#region src/scroll-area/scroll-area.d.ts
3
+ /**
4
+ * ScrollArea. React Aria has no equivalent, so this is a plain scrolling
5
+ * div -- and that is the honest answer rather than a shortcut.
6
+ *
7
+ * Census: fourteen paths match, thirteen are counted, ELEVEN render
8
+ * anything, and across them 17 uses. Every single one passes `className`
9
+ * and nothing else, and 13 of the 17 are a height or flex constraint plus
10
+ * padding. `ScrollBar` is exported by the legacy and imported by nobody.
11
+ * `orientation="horizontal"` has zero call sites.
12
+ *
13
+ * So there is no styled-scrollbar requirement to port. The legacy pays for
14
+ * one anyway: Radix injects a `<style>` tag that hides the native
15
+ * scrollbar, then re-draws it in JavaScript with a ResizeObserver, a
16
+ * portal-free overlay and a `display: table` content wrapper. That is a
17
+ * lot of machinery to reproduce a scrollbar, and it is why the legacy
18
+ * `ScrollArea` cannot be scrolled by keyboard at all.
19
+ *
20
+ * Tailwind 4.3.3 ships `scrollbar-width` and `scrollbar-color` utilities
21
+ * (verified in the installed dist, not assumed), which gets the same look
22
+ * in three classes and no JavaScript.
23
+ *
24
+ * WHAT CANNOT BE STYLED, stated rather than glossed: `scrollbar-color`
25
+ * takes a thumb and a track colour and nothing else -- no width in pixels,
26
+ * no radius, no hover state, no margins. `::-webkit-scrollbar` can do
27
+ * those but is non-standard and Firefox has never supported it. This
28
+ * component uses the standard properties only.
29
+ */
30
+ export declare const scrollAreaVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
31
+ /**
32
+ * A scrolling region.
33
+ *
34
+ * @example
35
+ * <ScrollArea label="Chat messages" className="h-64">...</ScrollArea>
36
+ */
37
+ export interface ScrollAreaProps extends ComponentPropsWithoutRef<"div"> {
38
+ children?: ReactNode;
39
+ /**
40
+ * Names the scrolling region.
41
+ *
42
+ * **Required**, and it took a failing test to get here. A focusable
43
+ * element with no role fails axe `focus-order-semantics`, so the
44
+ * keyboard fix below cannot ship without a role -- and a role with no
45
+ * accessible name is a violation of its own. Naming it is the only
46
+ * combination that is actually correct.
47
+ *
48
+ * `role="region"` specifically. `group` was the first attempt -- it is
49
+ * lighter, since region is a landmark and seventeen landmarks in one app
50
+ * is noise -- but axe does not accept `group` as an appropriate role for
51
+ * a focusable element, and it is right: a named region is the role the
52
+ * ARIA practices name for a scrollable content area, and being able to
53
+ * jump to it is the point.
54
+ */
55
+ label: string;
56
+ /**
57
+ * Change this when the contents change, and the region scrolls back to the
58
+ * top.
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.
65
+ *
66
+ * Sheet forwards its own `scrollResetKey` straight through: going a level
67
+ * deeper in a panel and landing halfway down the new view is disorienting,
68
+ * and it is exactly the thing every call site forgets.
69
+ */
70
+ scrollResetKey?: string | number;
71
+ }
72
+ export declare function ScrollArea({ className, children, label, scrollResetKey, ...props }: ScrollAreaProps): import("react").JSX.Element;
73
+ //#endregion
74
+ //# sourceMappingURL=scroll-area.d.ts.map
@@ -0,0 +1,63 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { cva } from "class-variance-authority";
4
+ import { jsx } from "react/jsx-runtime";
5
+ import { useEffect, useRef } from "react";
6
+ //#region src/scroll-area/scroll-area.tsx
7
+ /**
8
+ * ScrollArea. React Aria has no equivalent, so this is a plain scrolling
9
+ * div -- and that is the honest answer rather than a shortcut.
10
+ *
11
+ * Census: fourteen paths match, thirteen are counted, ELEVEN render
12
+ * anything, and across them 17 uses. Every single one passes `className`
13
+ * and nothing else, and 13 of the 17 are a height or flex constraint plus
14
+ * padding. `ScrollBar` is exported by the legacy and imported by nobody.
15
+ * `orientation="horizontal"` has zero call sites.
16
+ *
17
+ * So there is no styled-scrollbar requirement to port. The legacy pays for
18
+ * one anyway: Radix injects a `<style>` tag that hides the native
19
+ * scrollbar, then re-draws it in JavaScript with a ResizeObserver, a
20
+ * portal-free overlay and a `display: table` content wrapper. That is a
21
+ * lot of machinery to reproduce a scrollbar, and it is why the legacy
22
+ * `ScrollArea` cannot be scrolled by keyboard at all.
23
+ *
24
+ * Tailwind 4.3.3 ships `scrollbar-width` and `scrollbar-color` utilities
25
+ * (verified in the installed dist, not assumed), which gets the same look
26
+ * in three classes and no JavaScript.
27
+ *
28
+ * WHAT CANNOT BE STYLED, stated rather than glossed: `scrollbar-color`
29
+ * takes a thumb and a track colour and nothing else -- no width in pixels,
30
+ * no radius, no hover state, no margins. `::-webkit-scrollbar` can do
31
+ * those but is non-standard and Firefox has never supported it. This
32
+ * component uses the standard properties only.
33
+ */
34
+ const scrollAreaVariants = cva([
35
+ "overflow-y-auto overscroll-contain",
36
+ "box-border",
37
+ "scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent",
38
+ "outline-none",
39
+ "focus-visible:outline-solid",
40
+ "focus-visible:outline-(length:--focus-ring-width)",
41
+ "focus-visible:outline-offset-(--focus-ring-offset)",
42
+ "focus-visible:outline-(--color-ring)"
43
+ ]);
44
+ function ScrollArea({ className, children, label, scrollResetKey, ...props }) {
45
+ const region = useRef(null);
46
+ useEffect(() => {
47
+ if (region.current) region.current.scrollTop = 0;
48
+ }, [scrollResetKey]);
49
+ return /* @__PURE__ */ jsx("div", {
50
+ ...props,
51
+ ref: region,
52
+ tabIndex: 0,
53
+ role: "region",
54
+ "aria-label": label,
55
+ "data-slot": "scroll-area",
56
+ className: cn(scrollAreaVariants(), className),
57
+ children
58
+ });
59
+ }
60
+ //#endregion
61
+ export { ScrollArea, scrollAreaVariants };
62
+
63
+ //# sourceMappingURL=scroll-area.js.map
@@ -0,0 +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"}
@@ -0,0 +1,2 @@
1
+ import { Select, SelectItem, SelectItemProps, SelectProps, selectItemVariants, selectPopoverVariants, selectTriggerVariants, selectVariants } from "./select.js";
2
+ export { Select, SelectItem, type SelectItemProps, type SelectProps, selectItemVariants, selectPopoverVariants, selectTriggerVariants, selectVariants };
@@ -0,0 +1,2 @@
1
+ import { Select, SelectItem, selectItemVariants, selectPopoverVariants, selectTriggerVariants, selectVariants } from "./select.js";
2
+ export { Select, SelectItem, selectItemVariants, selectPopoverVariants, selectTriggerVariants, selectVariants };
@@ -0,0 +1,105 @@
1
+ import { ListBoxItemProps, SelectProps } from "react-aria-components";
2
+ import { ReactNode } from "react";
3
+ //#region src/select/select.d.ts
4
+ /**
5
+ * Select, ported from the legacy shadcn/Radix component.
6
+ *
7
+ * FOUR PARTS COLLAPSE INTO ONE. The census reads `SelectItem` 35 and
8
+ * `Select`, `SelectTrigger`, `SelectValue`, `SelectContent` 15 each --
9
+ * those four always appear together in the same shape, exactly like
10
+ * Dialog's header trio, so they are one component and a prop.
11
+ * `SelectGroup`, `SelectLabel`, `SelectSeparator`, `SelectScrollUpButton`
12
+ * and `SelectScrollDownButton` have ZERO call sites and are not ported.
13
+ *
14
+ * `label` IS REQUIRED. The legacy exports a `SelectLabel` that nobody
15
+ * renders and passes no `aria-label` anywhere, so all 15 selects are
16
+ * unnamed comboboxes -- the same finding as Progress, Slider and
17
+ * ScrollArea. Pass `labelHidden` when the surrounding UI already says it.
18
+ *
19
+ * The trigger reuses `fieldSurfaceVariants`, shared with Input and
20
+ * Textarea. A select and a text field sit in the same form row and must
21
+ * be the same box; the legacy styled them separately and drifted --
22
+ * `h-10` here against Input's `h-9`, for no stated reason.
23
+ *
24
+ * The popover is `card`, not a `popover` role: `--popover` is
25
+ * byte-identical to `--card` in the legacy theme. Item hover is `muted`,
26
+ * not `accent`, for the same reason.
27
+ */
28
+ export declare const selectVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
29
+ export declare const selectTriggerVariants: (props?: ({
30
+ size?: "sm" | "md" | "lg" | null | undefined;
31
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
32
+ export declare const selectPopoverVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
33
+ export declare const selectItemVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
34
+ /**
35
+ * A single-choice picker.
36
+ *
37
+ * @example
38
+ * <Select label="Country" placeholder="Pick one"
39
+ * selectedKey={code} onSelectionChange={setCode}>
40
+ * <SelectItem id="gb">United Kingdom</SelectItem>
41
+ * <SelectItem id="us">United States</SelectItem>
42
+ * </Select>
43
+ */
44
+ interface SelectProps$1 extends Omit<SelectProps<object>, "className" | "style" | "children" | "items"> {
45
+ /** `SelectItem` children. */
46
+ children?: ReactNode;
47
+ /**
48
+ * What is being chosen, and the control's accessible name.
49
+ *
50
+ * **Required.** All 15 legacy selects are unnamed: the legacy exports a
51
+ * `SelectLabel` that nobody renders and passes no `aria-label`.
52
+ */
53
+ label: ReactNode;
54
+ /** Render the label for screen readers only. */
55
+ labelHidden?: boolean;
56
+ /** Shown while nothing is selected. */
57
+ placeholder?: string;
58
+ /**
59
+ * Why the selection is wrong. Rendered under the control, and it is what
60
+ * makes `isInvalid` reach a screen reader.
61
+ *
62
+ * React Aria marks the ROOT `data-invalid` and leaves the button untouched,
63
+ * and an `aria-invalid` handed to that button is dropped by
64
+ * `filterDOMProps`, whose allowlist stops at the labelling attributes -- the
65
+ * same silent loss as `aria-current` on a pagination button. So without a
66
+ * message, an invalid select is invalid only in colour, which is WCAG 1.4.1
67
+ * on its own. React Aria wires this into `aria-describedby` for us.
68
+ */
69
+ errorMessage?: ReactNode;
70
+ /**
71
+ * Control scale of the trigger. Height and inline padding come from the
72
+ * same `--density-control-*` tokens as Input and Button, so a Select, a
73
+ * field and a button on one rung are the same height.
74
+ *
75
+ * Type does not scale with it, matching Input.
76
+ *
77
+ * @default "md"
78
+ */
79
+ size?: "sm" | "md" | "lg";
80
+ /** Extra classes for the wrapper. Merged last. */
81
+ className?: string;
82
+ }
83
+ declare function Select$1({ children, label, labelHidden, placeholder, errorMessage, size, className, ...props }: SelectProps$1): import("react").JSX.Element;
84
+ /**
85
+ * One option. Its `id` is the value.
86
+ *
87
+ * @example
88
+ * <SelectItem id="gb">United Kingdom</SelectItem>
89
+ */
90
+ export interface SelectItemProps extends Omit<ListBoxItemProps, "className" | "style" | "children"> {
91
+ children?: ReactNode;
92
+ /**
93
+ * The option's text, for the trigger and for typeahead.
94
+ *
95
+ * Derived from `children` when they are a string. Pass it explicitly
96
+ * when the option renders anything richer than text.
97
+ */
98
+ textValue?: string;
99
+ /** Extra classes. Merged last, so they win conflicts with ours. */
100
+ className?: string;
101
+ }
102
+ export declare function SelectItem({ className, children, textValue, ...props }: SelectItemProps): import("react").JSX.Element;
103
+ //#endregion
104
+ export { Select$1 as Select, SelectProps$1 as SelectProps };
105
+ //# sourceMappingURL=select.d.ts.map
@@ -0,0 +1,149 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { fieldSurfaceVariants } from "../lib/field-surface.js";
4
+ import { cva } from "class-variance-authority";
5
+ import { Button, FieldError, Label, ListBox, ListBoxItem, Popover, Select, SelectValue } from "react-aria-components";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/select/select.tsx
8
+ /**
9
+ * Select, ported from the legacy shadcn/Radix component.
10
+ *
11
+ * FOUR PARTS COLLAPSE INTO ONE. The census reads `SelectItem` 35 and
12
+ * `Select`, `SelectTrigger`, `SelectValue`, `SelectContent` 15 each --
13
+ * those four always appear together in the same shape, exactly like
14
+ * Dialog's header trio, so they are one component and a prop.
15
+ * `SelectGroup`, `SelectLabel`, `SelectSeparator`, `SelectScrollUpButton`
16
+ * and `SelectScrollDownButton` have ZERO call sites and are not ported.
17
+ *
18
+ * `label` IS REQUIRED. The legacy exports a `SelectLabel` that nobody
19
+ * renders and passes no `aria-label` anywhere, so all 15 selects are
20
+ * unnamed comboboxes -- the same finding as Progress, Slider and
21
+ * ScrollArea. Pass `labelHidden` when the surrounding UI already says it.
22
+ *
23
+ * The trigger reuses `fieldSurfaceVariants`, shared with Input and
24
+ * Textarea. A select and a text field sit in the same form row and must
25
+ * be the same box; the legacy styled them separately and drifted --
26
+ * `h-10` here against Input's `h-9`, for no stated reason.
27
+ *
28
+ * The popover is `card`, not a `popover` role: `--popover` is
29
+ * byte-identical to `--card` in the legacy theme. Item hover is `muted`,
30
+ * not `accent`, for the same reason.
31
+ */
32
+ const selectVariants = cva(["group flex w-full flex-col gap-1.5 box-border font-sans"]);
33
+ const selectTriggerVariants = cva([
34
+ "group-data-[invalid]:border-destructive-strong",
35
+ fieldSurfaceVariants(),
36
+ "flex items-center justify-between gap-2 text-start",
37
+ "text-base leading-(--leading-normal) cursor-pointer",
38
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0",
39
+ "[&_svg:not([class*='size-'])]:size-(--icon-sm)"
40
+ ], {
41
+ variants: {
42
+ /**
43
+ * The same rungs and the same tokens as Input and Button. 14 of the
44
+ * product's 32 `SelectTrigger` call sites override the height by hand,
45
+ * and a Select sits in the same form row as a field -- giving Input the
46
+ * axis without giving it here would be exactly the drift
47
+ * `fieldSurfaceVariants` exists to prevent.
48
+ *
49
+ * Type stays `text-base` at every rung, matching Input. A row whose two
50
+ * controls disagree about type size reads as a mistake.
51
+ */
52
+ size: {
53
+ sm: "h-(--density-control-h-sm) px-(--density-control-pad-sm)",
54
+ md: "h-(--density-control-h-md) px-(--density-control-pad-md)",
55
+ lg: "h-(--density-control-h-lg) px-(--density-control-pad-lg)"
56
+ } },
57
+ defaultVariants: { size: "md" }
58
+ });
59
+ const selectPopoverVariants = cva([
60
+ "w-(--trigger-width) min-w-32 overflow-y-auto",
61
+ "rounded-md border-(length:--border-width) border-border p-1",
62
+ "bg-card text-card-foreground shadow-floating",
63
+ "box-border font-sans text-sm leading-(--leading-normal)"
64
+ ]);
65
+ const selectItemVariants = cva([
66
+ "relative flex cursor-pointer select-none items-center gap-2",
67
+ "rounded-2xs py-1.5 pe-2 ps-8",
68
+ "box-border text-sm leading-(--leading-normal) text-card-foreground",
69
+ "outline-none data-[focused]:bg-muted",
70
+ "data-[focus-visible]:outline-solid",
71
+ "data-[focus-visible]:outline-(length:--focus-ring-width)",
72
+ "data-[focus-visible]:outline-offset-(--focus-ring-offset)",
73
+ "data-[focus-visible]:outline-(--color-ring)",
74
+ "data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-disabled)"
75
+ ]);
76
+ function Select$1({ children, label, labelHidden, placeholder, errorMessage, size = "md", className, ...props }) {
77
+ return /* @__PURE__ */ jsxs(Select, {
78
+ ...props,
79
+ "data-slot": "select",
80
+ className: cn(selectVariants(), className),
81
+ children: [
82
+ /* @__PURE__ */ jsx(Label, {
83
+ "data-slot": "select-label",
84
+ className: cn("text-sm leading-(--leading-normal) text-foreground", labelHidden && "sr-only"),
85
+ children: label
86
+ }),
87
+ /* @__PURE__ */ jsxs(Button, {
88
+ "data-slot": "select-trigger",
89
+ className: selectTriggerVariants({ size }),
90
+ children: [/* @__PURE__ */ jsx(SelectValue, {
91
+ "data-slot": "select-value",
92
+ children: ({ isPlaceholder, selectedText }) => isPlaceholder ? /* @__PURE__ */ jsx("span", {
93
+ "data-slot": "select-placeholder",
94
+ className: "text-muted-foreground",
95
+ children: placeholder
96
+ }) : selectedText
97
+ }), /* @__PURE__ */ jsx("svg", {
98
+ "aria-hidden": "true",
99
+ viewBox: "0 0 16 16",
100
+ fill: "none",
101
+ stroke: "currentColor",
102
+ strokeWidth: "1.75",
103
+ strokeLinecap: "round",
104
+ strokeLinejoin: "round",
105
+ className: "text-muted-foreground",
106
+ children: /* @__PURE__ */ jsx("path", { d: "m4 6 4 4 4-4" })
107
+ })]
108
+ }),
109
+ errorMessage == null ? null : /* @__PURE__ */ jsx(FieldError, {
110
+ "data-slot": "select-error",
111
+ className: "text-sm leading-(--leading-normal) text-destructive-strong",
112
+ children: errorMessage
113
+ }),
114
+ /* @__PURE__ */ jsx(Popover, {
115
+ "data-slot": "select-popover",
116
+ className: selectPopoverVariants(),
117
+ children: /* @__PURE__ */ jsx(ListBox, {
118
+ "data-slot": "select-list",
119
+ className: "outline-none",
120
+ children
121
+ })
122
+ })
123
+ ]
124
+ });
125
+ }
126
+ function SelectItem({ className, children, textValue, ...props }) {
127
+ return /* @__PURE__ */ jsx(ListBoxItem, {
128
+ ...props,
129
+ textValue: textValue ?? (typeof children === "string" ? children : void 0),
130
+ "data-slot": "select-item",
131
+ className: cn(selectItemVariants(), className),
132
+ children: ({ isSelected }) => /* @__PURE__ */ jsxs(Fragment, { children: [isSelected ? /* @__PURE__ */ jsx("svg", {
133
+ "data-slot": "select-item-check",
134
+ "aria-hidden": "true",
135
+ viewBox: "0 0 16 16",
136
+ fill: "none",
137
+ stroke: "currentColor",
138
+ strokeWidth: "2",
139
+ strokeLinecap: "round",
140
+ strokeLinejoin: "round",
141
+ className: "absolute start-2 size-3.5",
142
+ children: /* @__PURE__ */ jsx("path", { d: "m3.5 8.5 3 3 6-6" })
143
+ }) : null, children] })
144
+ });
145
+ }
146
+ //#endregion
147
+ export { Select$1 as Select, SelectItem, selectItemVariants, selectPopoverVariants, selectTriggerVariants, selectVariants };
148
+
149
+ //# sourceMappingURL=select.js.map
@@ -0,0 +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 } 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={cn(\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,GACT,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"}
@@ -0,0 +1,2 @@
1
+ import { Separator, SeparatorProps, separatorVariants } from "./separator.js";
2
+ export { Separator, type SeparatorProps, separatorVariants };
@@ -0,0 +1,2 @@
1
+ import { Separator, separatorVariants } from "./separator.js";
2
+ export { Separator, separatorVariants };
@@ -0,0 +1,42 @@
1
+ import { SeparatorProps } from "react-aria-components";
2
+ //#region src/separator/separator.d.ts
3
+ /**
4
+ * Separator, ported from the legacy component onto React Aria's Separator.
5
+ *
6
+ * Census: 7 files, 6 uses, one of them `orientation="vertical"`. No other
7
+ * prop is ever passed.
8
+ *
9
+ * `decorative` is NOT ported -- zero call sites pass it, and React Aria has
10
+ * no equivalent. That does change the semantics of all six existing uses,
11
+ * which is worth saying out loud rather than burying: the legacy defaulted
12
+ * to `decorative: true` and therefore rendered `role="none"`, so every
13
+ * divider in the app is currently invisible to assistive technology. React
14
+ * Aria renders an `<hr>` for a horizontal separator, whose implicit role IS
15
+ * separator. A visible line between two groups of content is a separator;
16
+ * announcing it is the correct behaviour, and it is what the element the
17
+ * legacy chose (a `div`) was pretending not to be.
18
+ *
19
+ * Every string in the cva blocks below is a STATIC literal. Tailwind scans
20
+ * source text, so a class assembled at runtime is invisible to it and would
21
+ * simply never be emitted.
22
+ */
23
+ export declare const separatorVariants: (props?: ({
24
+ orientation?: "horizontal" | "vertical" | null | undefined;
25
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
26
+ /**
27
+ * A visual divider between two groups of content.
28
+ *
29
+ * @example
30
+ * <Separator />
31
+ * <Separator orientation="vertical" />
32
+ */
33
+ interface SeparatorProps$1 extends Omit<SeparatorProps, "className" | "style"> {
34
+ /** @default "horizontal" */
35
+ orientation?: "horizontal" | "vertical";
36
+ /** Extra classes. Merged last, so they win conflicts with ours. */
37
+ className?: string;
38
+ }
39
+ declare function Separator$1({ className, orientation, ...props }: SeparatorProps$1): import("react").JSX.Element;
40
+ //#endregion
41
+ export { Separator$1 as Separator, SeparatorProps$1 as SeparatorProps };
42
+ //# sourceMappingURL=separator.d.ts.map
@@ -0,0 +1,49 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { cva } from "class-variance-authority";
4
+ import { Separator } from "react-aria-components";
5
+ import { jsx } from "react/jsx-runtime";
6
+ //#region src/separator/separator.tsx
7
+ /**
8
+ * Separator, ported from the legacy component onto React Aria's Separator.
9
+ *
10
+ * Census: 7 files, 6 uses, one of them `orientation="vertical"`. No other
11
+ * prop is ever passed.
12
+ *
13
+ * `decorative` is NOT ported -- zero call sites pass it, and React Aria has
14
+ * no equivalent. That does change the semantics of all six existing uses,
15
+ * which is worth saying out loud rather than burying: the legacy defaulted
16
+ * to `decorative: true` and therefore rendered `role="none"`, so every
17
+ * divider in the app is currently invisible to assistive technology. React
18
+ * Aria renders an `<hr>` for a horizontal separator, whose implicit role IS
19
+ * separator. A visible line between two groups of content is a separator;
20
+ * announcing it is the correct behaviour, and it is what the element the
21
+ * legacy chose (a `div`) was pretending not to be.
22
+ *
23
+ * Every string in the cva blocks below is a STATIC literal. Tailwind scans
24
+ * source text, so a class assembled at runtime is invisible to it and would
25
+ * simply never be emitted.
26
+ */
27
+ const separatorVariants = cva([
28
+ "shrink-0 bg-border border-none",
29
+ "m-0",
30
+ "box-border"
31
+ ], {
32
+ variants: { orientation: {
33
+ horizontal: "h-px w-full",
34
+ vertical: "h-full w-px"
35
+ } },
36
+ defaultVariants: { orientation: "horizontal" }
37
+ });
38
+ function Separator$1({ className, orientation = "horizontal", ...props }) {
39
+ return /* @__PURE__ */ jsx(Separator, {
40
+ ...props,
41
+ orientation,
42
+ "data-slot": "separator",
43
+ className: cn(separatorVariants({ orientation }), className)
44
+ });
45
+ }
46
+ //#endregion
47
+ export { Separator$1 as Separator, separatorVariants };
48
+
49
+ //# sourceMappingURL=separator.js.map
@@ -0,0 +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"}