@seekalfred/primitives 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/README.md +950 -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 +64 -0
  60. package/dist/input/input.js +94 -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 +93 -0
  99. package/dist/select/select.js +132 -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,2 @@
1
+ import { Avatar, avatarVariants } from "./avatar.js";
2
+ export { Avatar, avatarVariants };
@@ -0,0 +1,47 @@
1
+ import { VariantProps } from "class-variance-authority";
2
+ import { ComponentPropsWithoutRef } from "react";
3
+ //#region src/badge/badge.d.ts
4
+ /**
5
+ * Badge -- the status pill, in every shape the product asks for.
6
+ *
7
+ * Presentational, so there is no React Aria base. RAC's `Tag` looks similar
8
+ * and is not equivalent: it is focusable, selectable and removable, and it is
9
+ * illegal outside a `TagGroup` collection. A status pill is none of those.
10
+ *
11
+ * THIS ABSORBED THE PRODUCT'S `Pill`. The app shipped two components for one
12
+ * job -- `badge.tsx` with a `variant` axis and `pill.tsx` with a `tone` axis --
13
+ * across 22 and 22 consumer files. They disagreed about everything: shape
14
+ * (`rounded-md` against `rounded-full`), size (one fixed, one `sm`/`md`/`lg`)
15
+ * and naming, so `pill` accepted `blue` AND `info`, `green` AND `success`, for
16
+ * the same colour. Converging them is why `variant` is gone.
17
+ *
18
+ * The axes are the two questions a call site actually answers -- what does
19
+ * this MEAN (`tone`) and how loud should it be (`appearance`) -- rather than
20
+ * one axis whose values are the cross product. Six tones by three appearances
21
+ * is eighteen `variant` values otherwise, and shipping eighteen is how a
22
+ * design system ends up with `softPrimary` next to `primaryOutline`.
23
+ *
24
+ * Every string below is a STATIC literal, including the compound ones.
25
+ * Tailwind scans source text, so a class assembled at runtime is invisible to
26
+ * it and would never be emitted.
27
+ *
28
+ * Three things in the legacy are deliberately not ported:
29
+ *
30
+ * - `asChild` and its `[a&]:hover:` rules. Zero call sites; the hover styles
31
+ * existed only to colour the anchor asChild produced.
32
+ * - `focus-visible:ring-*`. A span is not focusable, so the ring could never
33
+ * appear. A consumer who adds `tabIndex` owns the indicator, and a ring is
34
+ * the wrong one anyway -- box-shadows vanish under forced-colors.
35
+ * - `aria-invalid:*`. A badge is never invalid; that was boilerplate copied
36
+ * across every shadcn control.
37
+ */
38
+ export declare const badgeVariants: (props?: ({
39
+ tone?: "neutral" | "success" | "warning" | "danger" | "info" | "primary" | null | undefined;
40
+ appearance?: "outline" | "solid" | "soft" | null | undefined;
41
+ size?: "sm" | "md" | "lg" | null | undefined;
42
+ shape?: "rounded" | "pill" | null | undefined;
43
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
44
+ export interface BadgeProps extends ComponentPropsWithoutRef<"span">, VariantProps<typeof badgeVariants> {}
45
+ export declare function Badge({ className, tone, appearance, size, shape, ...props }: BadgeProps): import("react").JSX.Element;
46
+ //#endregion
47
+ //# sourceMappingURL=badge.d.ts.map
@@ -0,0 +1,189 @@
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
+ //#region src/badge/badge.tsx
6
+ /**
7
+ * Badge -- the status pill, in every shape the product asks for.
8
+ *
9
+ * Presentational, so there is no React Aria base. RAC's `Tag` looks similar
10
+ * and is not equivalent: it is focusable, selectable and removable, and it is
11
+ * illegal outside a `TagGroup` collection. A status pill is none of those.
12
+ *
13
+ * THIS ABSORBED THE PRODUCT'S `Pill`. The app shipped two components for one
14
+ * job -- `badge.tsx` with a `variant` axis and `pill.tsx` with a `tone` axis --
15
+ * across 22 and 22 consumer files. They disagreed about everything: shape
16
+ * (`rounded-md` against `rounded-full`), size (one fixed, one `sm`/`md`/`lg`)
17
+ * and naming, so `pill` accepted `blue` AND `info`, `green` AND `success`, for
18
+ * the same colour. Converging them is why `variant` is gone.
19
+ *
20
+ * The axes are the two questions a call site actually answers -- what does
21
+ * this MEAN (`tone`) and how loud should it be (`appearance`) -- rather than
22
+ * one axis whose values are the cross product. Six tones by three appearances
23
+ * is eighteen `variant` values otherwise, and shipping eighteen is how a
24
+ * design system ends up with `softPrimary` next to `primaryOutline`.
25
+ *
26
+ * Every string below is a STATIC literal, including the compound ones.
27
+ * Tailwind scans source text, so a class assembled at runtime is invisible to
28
+ * it and would never be emitted.
29
+ *
30
+ * Three things in the legacy are deliberately not ported:
31
+ *
32
+ * - `asChild` and its `[a&]:hover:` rules. Zero call sites; the hover styles
33
+ * existed only to colour the anchor asChild produced.
34
+ * - `focus-visible:ring-*`. A span is not focusable, so the ring could never
35
+ * appear. A consumer who adds `tabIndex` owns the indicator, and a ring is
36
+ * the wrong one anyway -- box-shadows vanish under forced-colors.
37
+ * - `aria-invalid:*`. A badge is never invalid; that was boilerplate copied
38
+ * across every shadcn control.
39
+ */
40
+ const badgeVariants = cva([
41
+ "inline-flex w-fit shrink-0 items-center justify-center gap-1",
42
+ "border-(length:--border-width) border-transparent",
43
+ "font-sans font-medium",
44
+ "whitespace-nowrap",
45
+ "box-border",
46
+ "[&>svg]:pointer-events-none [&>svg]:size-3"
47
+ ], {
48
+ variants: {
49
+ /** What the badge MEANS. Colour follows from it; it is never picked directly. */
50
+ tone: {
51
+ neutral: "",
52
+ primary: "",
53
+ success: "",
54
+ warning: "",
55
+ danger: "",
56
+ info: ""
57
+ },
58
+ /** How loud it is. */
59
+ appearance: {
60
+ solid: "",
61
+ soft: "",
62
+ outline: "bg-transparent"
63
+ },
64
+ size: {
65
+ sm: "px-2 py-0.5 text-xs leading-(--leading-snug)",
66
+ md: "px-2.5 py-0.5 text-xs leading-(--leading-snug)",
67
+ lg: "px-3 py-1 text-sm leading-(--leading-snug)"
68
+ },
69
+ shape: {
70
+ rounded: "rounded-md",
71
+ pill: "rounded-full"
72
+ }
73
+ },
74
+ compoundVariants: [
75
+ {
76
+ tone: "neutral",
77
+ appearance: "solid",
78
+ class: "bg-muted-foreground text-background"
79
+ },
80
+ {
81
+ tone: "primary",
82
+ appearance: "solid",
83
+ class: "bg-primary text-primary-foreground"
84
+ },
85
+ {
86
+ tone: "success",
87
+ appearance: "solid",
88
+ class: "bg-success-strong text-success-strong-foreground"
89
+ },
90
+ {
91
+ tone: "warning",
92
+ appearance: "solid",
93
+ class: "bg-warning-strong text-warning-strong-foreground"
94
+ },
95
+ {
96
+ tone: "danger",
97
+ appearance: "solid",
98
+ class: "bg-destructive text-destructive-foreground"
99
+ },
100
+ {
101
+ tone: "info",
102
+ appearance: "solid",
103
+ class: "bg-info-strong text-info-strong-foreground"
104
+ },
105
+ {
106
+ tone: "neutral",
107
+ appearance: "soft",
108
+ class: "bg-muted text-muted-foreground"
109
+ },
110
+ {
111
+ tone: "primary",
112
+ appearance: "soft",
113
+ class: "bg-primary-subtle text-foreground"
114
+ },
115
+ {
116
+ tone: "success",
117
+ appearance: "soft",
118
+ class: "bg-success-subtle text-success-foreground"
119
+ },
120
+ {
121
+ tone: "warning",
122
+ appearance: "soft",
123
+ class: "bg-warning-subtle text-warning-foreground"
124
+ },
125
+ {
126
+ tone: "danger",
127
+ appearance: "soft",
128
+ class: "bg-destructive-subtle text-destructive-subtle-foreground"
129
+ },
130
+ {
131
+ tone: "info",
132
+ appearance: "soft",
133
+ class: "bg-info-subtle text-info-foreground"
134
+ },
135
+ {
136
+ tone: "neutral",
137
+ appearance: "outline",
138
+ class: "border-border text-foreground"
139
+ },
140
+ {
141
+ tone: "primary",
142
+ appearance: "outline",
143
+ class: "border-primary-strong text-foreground"
144
+ },
145
+ {
146
+ tone: "success",
147
+ appearance: "outline",
148
+ class: "border-success-strong text-success-strong"
149
+ },
150
+ {
151
+ tone: "warning",
152
+ appearance: "outline",
153
+ class: "border-warning-strong text-warning-strong"
154
+ },
155
+ {
156
+ tone: "danger",
157
+ appearance: "outline",
158
+ class: "border-destructive-strong text-destructive-strong"
159
+ },
160
+ {
161
+ tone: "info",
162
+ appearance: "outline",
163
+ class: "border-info-strong text-info-strong"
164
+ }
165
+ ],
166
+ defaultVariants: {
167
+ tone: "neutral",
168
+ appearance: "soft",
169
+ size: "md",
170
+ shape: "rounded"
171
+ }
172
+ });
173
+ function Badge({ className, tone, appearance, size, shape, ...props }) {
174
+ return /* @__PURE__ */ jsx("span", {
175
+ ...props,
176
+ "data-slot": "badge",
177
+ "data-tone": tone ?? "neutral",
178
+ className: cn(badgeVariants({
179
+ tone,
180
+ appearance,
181
+ size,
182
+ shape
183
+ }), className)
184
+ });
185
+ }
186
+ //#endregion
187
+ export { Badge, badgeVariants };
188
+
189
+ //# sourceMappingURL=badge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"badge.js","names":[],"sources":["../../src/badge/badge.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentPropsWithoutRef } from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Badge -- the status pill, in every shape the product asks for.\n *\n * Presentational, so there is no React Aria base. RAC's `Tag` looks similar\n * and is not equivalent: it is focusable, selectable and removable, and it is\n * illegal outside a `TagGroup` collection. A status pill is none of those.\n *\n * THIS ABSORBED THE PRODUCT'S `Pill`. The app shipped two components for one\n * job -- `badge.tsx` with a `variant` axis and `pill.tsx` with a `tone` axis --\n * across 22 and 22 consumer files. They disagreed about everything: shape\n * (`rounded-md` against `rounded-full`), size (one fixed, one `sm`/`md`/`lg`)\n * and naming, so `pill` accepted `blue` AND `info`, `green` AND `success`, for\n * the same colour. Converging them is why `variant` is gone.\n *\n * The axes are the two questions a call site actually answers -- what does\n * this MEAN (`tone`) and how loud should it be (`appearance`) -- rather than\n * one axis whose values are the cross product. Six tones by three appearances\n * is eighteen `variant` values otherwise, and shipping eighteen is how a\n * design system ends up with `softPrimary` next to `primaryOutline`.\n *\n * Every string below is a STATIC literal, including the compound ones.\n * Tailwind scans source text, so a class assembled at runtime is invisible to\n * it and would never be emitted.\n *\n * Three things in the legacy are deliberately not ported:\n *\n * - `asChild` and its `[a&]:hover:` rules. Zero call sites; the hover styles\n * existed only to colour the anchor asChild produced.\n * - `focus-visible:ring-*`. A span is not focusable, so the ring could never\n * appear. A consumer who adds `tabIndex` owns the indicator, and a ring is\n * the wrong one anyway -- box-shadows vanish under forced-colors.\n * - `aria-invalid:*`. A badge is never invalid; that was boilerplate copied\n * across every shadcn control.\n */\nexport const badgeVariants = cva(\n [\n \"inline-flex w-fit shrink-0 items-center justify-center gap-1\",\n \"border-(length:--border-width) border-transparent\",\n // NO leading-* here. It lives on each `size` instead, AFTER that size's\n // text-* class: tailwind-merge treats a later text-* as owning\n // line-height, so a leading-* in this base string is silently stripped the\n // moment a size variant adds one. The NoUaLeak story caught exactly that.\n \"font-sans font-medium\",\n \"whitespace-nowrap\",\n // box-border and font-sans are not boilerplate. The reset in\n // @seekalfred/styles is OPT-IN, so a consumer may load none: an unstyled\n // span inherits the host's serif, and content-box would add the padding\n // to any width a consumer sets.\n \"box-border\",\n \"[&>svg]:pointer-events-none [&>svg]:size-3\",\n ],\n {\n variants: {\n /** What the badge MEANS. Colour follows from it; it is never picked directly. */\n tone: {\n neutral: \"\",\n primary: \"\",\n success: \"\",\n warning: \"\",\n danger: \"\",\n info: \"\",\n },\n /** How loud it is. */\n appearance: {\n solid: \"\",\n soft: \"\",\n outline: \"bg-transparent\",\n },\n size: {\n sm: \"px-2 py-0.5 text-xs leading-(--leading-snug)\",\n md: \"px-2.5 py-0.5 text-xs leading-(--leading-snug)\",\n lg: \"px-3 py-1 text-sm leading-(--leading-snug)\",\n },\n /*\n * The product's two components disagreed on this and nothing else:\n * `badge.tsx` was rounded-md, `pill.tsx` was rounded-full. Both survive\n * as one axis rather than one winning, because a count-bearing badge and\n * a verdict pill really do read differently.\n */\n shape: { rounded: \"rounded-md\", pill: \"rounded-full\" },\n },\n compoundVariants: [\n // SOLID -- the fill carries the meaning, so the text must clear 4.5:1 on it.\n /*\n * muted-foreground, NOT `secondary`. --color-secondary resolves to\n * periwinkle.400, so a \"neutral\" badge rendered lavender -- caught in a\n * browser, not by any assertion, because the story compared it against\n * the same wrong token it was painted with. A tone called neutral has to\n * be neutral; the periwinkle fill the product uses for its `secondary`\n * badge is the brand's SECOND colour, not an absence of one.\n */\n { tone: \"neutral\", appearance: \"solid\", class: \"bg-muted-foreground text-background\" },\n { tone: \"primary\", appearance: \"solid\", class: \"bg-primary text-primary-foreground\" },\n { tone: \"success\", appearance: \"solid\", class: \"bg-success-strong text-success-strong-foreground\" },\n { tone: \"warning\", appearance: \"solid\", class: \"bg-warning-strong text-warning-strong-foreground\" },\n { tone: \"danger\", appearance: \"solid\", class: \"bg-destructive text-destructive-foreground\" },\n { tone: \"info\", appearance: \"solid\", class: \"bg-info-strong text-info-strong-foreground\" },\n\n /*\n * SOFT -- a tint, and the default, because this is what a status pill is.\n * The ink is one step darker than the obvious pairing for success,\n * warning and info: the product measured 4.13, 3.48 and 3.34 on its own\n * tints and had to darken all three. Our `-foreground` tokens already\n * encode that correction, which is why they are used rather than\n * `-strong`.\n */\n { tone: \"neutral\", appearance: \"soft\", class: \"bg-muted text-muted-foreground\" },\n // No primary-subtle-foreground token: plain `foreground` on this tint\n // measures 18.70 light / 9.78 dark, already graded for the Table's\n // highlighted row.\n { tone: \"primary\", appearance: \"soft\", class: \"bg-primary-subtle text-foreground\" },\n { tone: \"success\", appearance: \"soft\", class: \"bg-success-subtle text-success-foreground\" },\n { tone: \"warning\", appearance: \"soft\", class: \"bg-warning-subtle text-warning-foreground\" },\n {\n tone: \"danger\",\n appearance: \"soft\",\n class: \"bg-destructive-subtle text-destructive-subtle-foreground\",\n },\n { tone: \"info\", appearance: \"soft\", class: \"bg-info-subtle text-info-foreground\" },\n\n /*\n * OUTLINE -- no fill, so the border is the only thing separating the\n * badge from the page and is held to the 3:1 non-text floor of WCAG\n * 1.4.11. Text AND border are `-strong`, not `-foreground`: with no tint\n * behind it the label sits on `background`, and `-strong on background`\n * is the pair already graded at 4.5. `-foreground` is graded only\n * against its own `-subtle`, so using it here would have been a ratio\n * nobody had measured.\n */\n { tone: \"neutral\", appearance: \"outline\", class: \"border-border text-foreground\" },\n { tone: \"primary\", appearance: \"outline\", class: \"border-primary-strong text-foreground\" },\n {\n tone: \"success\",\n appearance: \"outline\",\n class: \"border-success-strong text-success-strong\",\n },\n {\n tone: \"warning\",\n appearance: \"outline\",\n class: \"border-warning-strong text-warning-strong\",\n },\n {\n tone: \"danger\",\n appearance: \"outline\",\n class: \"border-destructive-strong text-destructive-strong\",\n },\n { tone: \"info\", appearance: \"outline\", class: \"border-info-strong text-info-strong\" },\n ],\n defaultVariants: { tone: \"neutral\", appearance: \"soft\", size: \"md\", shape: \"rounded\" },\n },\n);\n\nexport interface BadgeProps\n extends ComponentPropsWithoutRef<\"span\">,\n VariantProps<typeof badgeVariants> {}\n\nexport function Badge({ className, tone, appearance, size, shape, ...props }: BadgeProps) {\n return (\n <span\n {...props}\n data-slot=\"badge\"\n data-tone={tone ?? \"neutral\"}\n className={cn(badgeVariants({ tone, appearance, size, shape }), className)}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,MAAa,gBAAgB,IAC3B;CACE;CACA;CAKA;CACA;CAKA;CACA;AACF,GACA;CACE,UAAU;;EAER,MAAM;GACJ,SAAS;GACT,SAAS;GACT,SAAS;GACT,SAAS;GACT,QAAQ;GACR,MAAM;EACR;;EAEA,YAAY;GACV,OAAO;GACP,MAAM;GACN,SAAS;EACX;EACA,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;EACN;EAOA,OAAO;GAAE,SAAS;GAAc,MAAM;EAAe;CACvD;CACA,kBAAkB;EAUhB;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAsC;EACrF;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAqC;EACpF;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAmD;EAClG;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAmD;EAClG;GAAE,MAAM;GAAU,YAAY;GAAS,OAAO;EAA6C;EAC3F;GAAE,MAAM;GAAQ,YAAY;GAAS,OAAO;EAA6C;EAUzF;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAAiC;EAI/E;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAAoC;EAClF;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAA4C;EAC1F;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAA4C;EAC1F;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GAAE,MAAM;GAAQ,YAAY;GAAQ,OAAO;EAAsC;EAWjF;GAAE,MAAM;GAAW,YAAY;GAAW,OAAO;EAAgC;EACjF;GAAE,MAAM;GAAW,YAAY;GAAW,OAAO;EAAwC;EACzF;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GAAE,MAAM;GAAQ,YAAY;GAAW,OAAO;EAAsC;CACtF;CACA,iBAAiB;EAAE,MAAM;EAAW,YAAY;EAAQ,MAAM;EAAM,OAAO;CAAU;AACvF,CACF;AAMA,SAAgB,MAAM,EAAE,WAAW,MAAM,YAAY,MAAM,OAAO,GAAG,SAAqB;CACxF,OACE,oBAAC,QAAD;EACE,GAAI;EACJ,aAAU;EACV,aAAW,QAAQ;EACnB,WAAW,GAAG,cAAc;GAAE;GAAM;GAAY;GAAM;EAAM,CAAC,GAAG,SAAS;CAC1E,CAAA;AAEL"}
@@ -0,0 +1,2 @@
1
+ import { Badge, BadgeProps, badgeVariants } from "./badge.js";
2
+ export { Badge, type BadgeProps, badgeVariants };
@@ -0,0 +1,2 @@
1
+ import { Badge, badgeVariants } from "./badge.js";
2
+ export { Badge, badgeVariants };
@@ -0,0 +1,98 @@
1
+ import { BreadcrumbProps, BreadcrumbsProps } from "react-aria-components";
2
+ import { ReactNode } from "react";
3
+ //#region src/breadcrumb/breadcrumb.d.ts
4
+ /**
5
+ * Breadcrumbs, ported from the legacy shadcn/Radix component onto React
6
+ * Aria's Breadcrumbs collection.
7
+ *
8
+ * SIX LEGACY PARTS COLLAPSE TO TWO, and not for tidiness -- React Aria's
9
+ * `Breadcrumbs` is a real collection, so it knows which item is last:
10
+ *
11
+ * | legacy | uses | fate |
12
+ * | ------------------- | ---- | ------------------------------------------- |
13
+ * | Breadcrumb | 2 | -> `Breadcrumbs` (nav + ol) |
14
+ * | BreadcrumbList | 2 | dropped -- `Breadcrumbs` IS the `<ol>` |
15
+ * | BreadcrumbItem | 6 | -> `BreadcrumbItem` |
16
+ * | BreadcrumbLink | 4 | merged into `BreadcrumbItem` as `href` |
17
+ * | BreadcrumbPage | 2 | dropped -- the last crumb is auto-current |
18
+ * | BreadcrumbSeparator | 4 | dropped -- every non-last crumb draws one |
19
+ * | BreadcrumbEllipsis | 0 | dropped -- zero call sites |
20
+ *
21
+ * The collection is also why the separator CANNOT stay a component: RAC
22
+ * builds its item list from the children, so a `<BreadcrumbSeparator/>`
23
+ * sibling would be a non-item node inside a collection and there is nowhere
24
+ * legal to put it.
25
+ *
26
+ * Every string in the cva blocks below is a STATIC literal. Tailwind scans
27
+ * source text, so a class assembled at runtime is invisible to it and would
28
+ * simply never be emitted -- the component would render unstyled with no
29
+ * error anywhere.
30
+ */
31
+ export declare const breadcrumbsVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
32
+ export declare const breadcrumbItemVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
33
+ export declare const breadcrumbLinkVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
34
+ /**
35
+ * A navigation trail.
36
+ *
37
+ * The last child is the current page: give it no `href`, and React Aria
38
+ * marks it `aria-current="page"` and takes it out of the tab order.
39
+ *
40
+ * @example
41
+ * <Breadcrumbs>
42
+ * <BreadcrumbItem href="/">Home</BreadcrumbItem>
43
+ * <BreadcrumbItem href="/design-thinking">Design Thinking</BreadcrumbItem>
44
+ * <BreadcrumbItem>User Personas</BreadcrumbItem>
45
+ * </Breadcrumbs>
46
+ */
47
+ interface BreadcrumbsProps$1 extends Omit<BreadcrumbsProps<object>, "className" | "style" | "children" | "items" | "dependencies"> {
48
+ /**
49
+ * `BreadcrumbItem` elements. Static children only: `items` and the render
50
+ * function are not exposed, because no call site builds a trail from data
51
+ * and the generic they require leaks into every consumer's types.
52
+ */
53
+ children?: ReactNode;
54
+ /**
55
+ * Extra classes, merged last so they win conflicts with ours.
56
+ *
57
+ * They land on the `<ol>`, where our own classes are, so `tailwind-merge`
58
+ * can actually resolve a conflict. The `<nav>` is an unstyled landmark
59
+ * wrapper.
60
+ */
61
+ className?: string;
62
+ /**
63
+ * Accessible name for the navigation landmark. Only worth changing when a
64
+ * page has more than one trail.
65
+ *
66
+ * @default "Breadcrumb"
67
+ */
68
+ "aria-label"?: string;
69
+ }
70
+ declare function Breadcrumbs$1({ className, children, "aria-label": ariaLabel, ...props }: BreadcrumbsProps$1): import("react").JSX.Element;
71
+ /**
72
+ * One crumb.
73
+ *
74
+ * @example
75
+ * <BreadcrumbItem href="/reports">Reports</BreadcrumbItem>
76
+ */
77
+ export interface BreadcrumbItemProps extends Omit<BreadcrumbProps, "className" | "style" | "children"> {
78
+ /**
79
+ * The crumb's label.
80
+ *
81
+ * An icon-only crumb has no accessible name, which is an axe `link-name`
82
+ * violation -- both legacy call sites ship one. Pass `aria-label` when the
83
+ * only child is a glyph.
84
+ */
85
+ children?: ReactNode;
86
+ /** Extra classes for the `<li>`, merged last. */
87
+ className?: string;
88
+ /** Destination. Omit it on the last crumb: that one is the current page. */
89
+ href?: string;
90
+ /** Anchor target, forwarded when `href` is set. */
91
+ target?: string;
92
+ /** Anchor rel, forwarded when `href` is set. */
93
+ rel?: string;
94
+ }
95
+ export declare function BreadcrumbItem({ className, children, href, target, rel, "aria-label": ariaLabel, ...props }: BreadcrumbItemProps): import("react").JSX.Element;
96
+ //#endregion
97
+ export { Breadcrumbs$1 as Breadcrumbs, BreadcrumbsProps$1 as BreadcrumbsProps };
98
+ //# sourceMappingURL=breadcrumb.d.ts.map
@@ -0,0 +1,93 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { cva } from "class-variance-authority";
4
+ import { Breadcrumb, Breadcrumbs, Link } from "react-aria-components";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/breadcrumb/breadcrumb.tsx
7
+ /**
8
+ * Breadcrumbs, ported from the legacy shadcn/Radix component onto React
9
+ * Aria's Breadcrumbs collection.
10
+ *
11
+ * SIX LEGACY PARTS COLLAPSE TO TWO, and not for tidiness -- React Aria's
12
+ * `Breadcrumbs` is a real collection, so it knows which item is last:
13
+ *
14
+ * | legacy | uses | fate |
15
+ * | ------------------- | ---- | ------------------------------------------- |
16
+ * | Breadcrumb | 2 | -> `Breadcrumbs` (nav + ol) |
17
+ * | BreadcrumbList | 2 | dropped -- `Breadcrumbs` IS the `<ol>` |
18
+ * | BreadcrumbItem | 6 | -> `BreadcrumbItem` |
19
+ * | BreadcrumbLink | 4 | merged into `BreadcrumbItem` as `href` |
20
+ * | BreadcrumbPage | 2 | dropped -- the last crumb is auto-current |
21
+ * | BreadcrumbSeparator | 4 | dropped -- every non-last crumb draws one |
22
+ * | BreadcrumbEllipsis | 0 | dropped -- zero call sites |
23
+ *
24
+ * The collection is also why the separator CANNOT stay a component: RAC
25
+ * builds its item list from the children, so a `<BreadcrumbSeparator/>`
26
+ * sibling would be a non-item node inside a collection and there is nowhere
27
+ * legal to put it.
28
+ *
29
+ * Every string in the cva blocks below is a STATIC literal. Tailwind scans
30
+ * source text, so a class assembled at runtime is invisible to it and would
31
+ * simply never be emitted -- the component would render unstyled with no
32
+ * error anywhere.
33
+ */
34
+ const breadcrumbsVariants = cva([
35
+ "flex flex-wrap items-center gap-2",
36
+ "m-0 list-none p-0",
37
+ "font-sans text-sm leading-(--leading-normal) text-muted-foreground",
38
+ "box-border break-words"
39
+ ]);
40
+ const breadcrumbItemVariants = cva(["group inline-flex items-center gap-2"]);
41
+ const breadcrumbLinkVariants = cva([
42
+ "inline-flex items-center gap-1.5",
43
+ "font-sans text-muted-foreground no-underline",
44
+ "rounded-sm cursor-pointer",
45
+ "motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard",
46
+ "data-[hovered]:text-foreground data-[hovered]:underline",
47
+ "outline-none",
48
+ "data-[focus-visible]:outline-solid",
49
+ "data-[focus-visible]:outline-(length:--focus-ring-width)",
50
+ "data-[focus-visible]:outline-offset-(--focus-ring-offset)",
51
+ "data-[focus-visible]:outline-(--color-ring)",
52
+ "data-[current]:text-foreground data-[current]:font-normal data-[current]:cursor-default",
53
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0",
54
+ "[&_svg:not([class*='size-'])]:size-(--icon-sm)"
55
+ ]);
56
+ function Breadcrumbs$1({ className, children, "aria-label": ariaLabel = "Breadcrumb", ...props }) {
57
+ return /* @__PURE__ */ jsx("nav", {
58
+ "aria-label": ariaLabel,
59
+ "data-slot": "breadcrumb",
60
+ children: /* @__PURE__ */ jsx(Breadcrumbs, {
61
+ ...props,
62
+ "aria-label": ariaLabel,
63
+ "data-slot": "breadcrumb-list",
64
+ className: cn(breadcrumbsVariants(), className),
65
+ children
66
+ })
67
+ });
68
+ }
69
+ function BreadcrumbItem({ className, children, href, target, rel, "aria-label": ariaLabel, ...props }) {
70
+ return /* @__PURE__ */ jsxs(Breadcrumb, {
71
+ ...props,
72
+ "data-slot": "breadcrumb-item",
73
+ className: cn(breadcrumbItemVariants(), className),
74
+ children: [/* @__PURE__ */ jsx(Link, {
75
+ href,
76
+ target,
77
+ rel,
78
+ "aria-label": ariaLabel,
79
+ "data-slot": "breadcrumb-link",
80
+ className: breadcrumbLinkVariants(),
81
+ children
82
+ }), /* @__PURE__ */ jsx("span", {
83
+ "aria-hidden": "true",
84
+ "data-slot": "breadcrumb-separator",
85
+ className: "shrink-0 select-none group-data-[current]:hidden",
86
+ children: "/"
87
+ })]
88
+ });
89
+ }
90
+ //#endregion
91
+ export { BreadcrumbItem, Breadcrumbs$1 as Breadcrumbs, breadcrumbItemVariants, breadcrumbLinkVariants, breadcrumbsVariants };
92
+
93
+ //# sourceMappingURL=breadcrumb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"breadcrumb.js","names":["Breadcrumbs","AriaBreadcrumbs","AriaBreadcrumb","AriaLink"],"sources":["../../src/breadcrumb/breadcrumb.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport type { ReactNode } from \"react\";\nimport {\n Breadcrumb as AriaBreadcrumb,\n Breadcrumbs as AriaBreadcrumbs,\n Link as AriaLink,\n type BreadcrumbProps as AriaBreadcrumbProps,\n type BreadcrumbsProps as AriaBreadcrumbsProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Breadcrumbs, ported from the legacy shadcn/Radix component onto React\n * Aria's Breadcrumbs collection.\n *\n * SIX LEGACY PARTS COLLAPSE TO TWO, and not for tidiness -- React Aria's\n * `Breadcrumbs` is a real collection, so it knows which item is last:\n *\n * | legacy | uses | fate |\n * | ------------------- | ---- | ------------------------------------------- |\n * | Breadcrumb | 2 | -> `Breadcrumbs` (nav + ol) |\n * | BreadcrumbList | 2 | dropped -- `Breadcrumbs` IS the `<ol>` |\n * | BreadcrumbItem | 6 | -> `BreadcrumbItem` |\n * | BreadcrumbLink | 4 | merged into `BreadcrumbItem` as `href` |\n * | BreadcrumbPage | 2 | dropped -- the last crumb is auto-current |\n * | BreadcrumbSeparator | 4 | dropped -- every non-last crumb draws one |\n * | BreadcrumbEllipsis | 0 | dropped -- zero call sites |\n *\n * The collection is also why the separator CANNOT stay a component: RAC\n * builds its item list from the children, so a `<BreadcrumbSeparator/>`\n * sibling would be a non-item node inside a collection and there is nowhere\n * legal to put it.\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 -- the component would render unstyled with no\n * error anywhere.\n */\nexport const breadcrumbsVariants = cva([\n \"flex flex-wrap items-center gap-2\",\n\n // The user-agent `<ol>`: decimal markers, a 40px inline padding and a 1em\n // block margin. The reset in @seekalfred/styles is OPT-IN, so a consumer\n // may load none -- unreset, the trail renders as a numbered, indented\n // list. The Accordion shipped its panel in the browser's serif for exactly\n // this family of reason.\n \"m-0 list-none p-0\",\n\n // leading-* AFTER text-*: tailwind-merge treats a later text-* as owning\n // line-height and silently strips a leading-* that precedes it.\n \"font-sans text-sm leading-(--leading-normal) text-muted-foreground\",\n\n // box-border: content-box would add any padding a consumer sets on top of\n // a constrained width. The Alert shipped overflowing by 46px that way.\n \"box-border break-words\",\n]);\n\nexport const breadcrumbItemVariants = cva([\n // `group` is load-bearing: the separator hides itself from the current\n // crumb via group-data-[current], and RAC puts data-current on the <li>,\n // which CSS cannot look upward to from the span.\n \"group inline-flex items-center gap-2\",\n]);\n\nexport const breadcrumbLinkVariants = cva([\n \"inline-flex items-center gap-1.5\",\n\n // The user-agent `<a>`: -webkit-link blue and a permanent underline. Left\n // alone, every crumb is blue, and the hover underline below stops being a\n // state cue at all because it is already on.\n \"font-sans text-muted-foreground no-underline\",\n \"rounded-sm cursor-pointer\",\n \"motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard\",\n\n // Hover is NOT colour-only. The legacy component moved muted-foreground to\n // foreground and nothing else; under forced-colors the user's palette\n // overrides both and the cue vanishes entirely (WCAG 1.4.1, and\n // ARCHITECTURE section 5 rule 6). The underline survives any colour mode.\n \"data-[hovered]:text-foreground data-[hovered]:underline\",\n\n // outline-solid is load-bearing, not decoration. `outline-none` sets BOTH\n // outline-style:none and --tw-outline-style:none, and the width utility\n // resolves its style from that same variable -- so a focus variant can\n // apply a width and a colour while faithfully re-applying style:none and\n // painting nothing. Button shipped exactly that: keyboard-focusable with\n // no visible ring, and every gate stayed green because axe has no\n // focus-indicator rule.\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\n // The current crumb. RAC forces isDisabled on it, so React Aria renders a\n // <span role=\"link\" aria-disabled=\"true\" aria-current=\"page\"> with no\n // tabIndex -- byte for byte the legacy BreadcrumbPage, minus the chance of\n // hand-writing it onto the wrong item.\n \"data-[current]:text-foreground data-[current]:font-normal data-[current]:cursor-default\",\n\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"[&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n]);\n\n/**\n * A navigation trail.\n *\n * The last child is the current page: give it no `href`, and React Aria\n * marks it `aria-current=\"page\"` and takes it out of the tab order.\n *\n * @example\n * <Breadcrumbs>\n * <BreadcrumbItem href=\"/\">Home</BreadcrumbItem>\n * <BreadcrumbItem href=\"/design-thinking\">Design Thinking</BreadcrumbItem>\n * <BreadcrumbItem>User Personas</BreadcrumbItem>\n * </Breadcrumbs>\n */\nexport interface BreadcrumbsProps\n extends Omit<\n AriaBreadcrumbsProps<object>,\n \"className\" | \"style\" | \"children\" | \"items\" | \"dependencies\"\n > {\n /**\n * `BreadcrumbItem` elements. Static children only: `items` and the render\n * function are not exposed, because no call site builds a trail from data\n * and the generic they require leaks into every consumer's types.\n */\n children?: ReactNode;\n /**\n * Extra classes, merged last so they win conflicts with ours.\n *\n * They land on the `<ol>`, where our own classes are, so `tailwind-merge`\n * can actually resolve a conflict. The `<nav>` is an unstyled landmark\n * wrapper.\n */\n className?: string;\n /**\n * Accessible name for the navigation landmark. Only worth changing when a\n * page has more than one trail.\n *\n * @default \"Breadcrumb\"\n */\n \"aria-label\"?: string;\n}\n\nexport function Breadcrumbs({\n className,\n children,\n \"aria-label\": ariaLabel = \"Breadcrumb\",\n ...props\n}: BreadcrumbsProps) {\n return (\n // RAC renders the <ol> and puts its aria-label there; it does NOT render\n // a landmark. Dropping the <nav> would silently remove the thing screen\n // reader users actually navigate by, so it is added back here. Both\n // carry the same name deliberately -- RAC falls back to a localized\n // \"Breadcrumbs\" when aria-label is absent, so the <ol> is named either\n // way and letting the two names diverge would be the worse outcome.\n <nav aria-label={ariaLabel} data-slot=\"breadcrumb\">\n <AriaBreadcrumbs\n {...props}\n aria-label={ariaLabel}\n data-slot=\"breadcrumb-list\"\n className={cn(breadcrumbsVariants(), className)}\n >\n {children}\n </AriaBreadcrumbs>\n </nav>\n );\n}\n\n/**\n * One crumb.\n *\n * @example\n * <BreadcrumbItem href=\"/reports\">Reports</BreadcrumbItem>\n */\nexport interface BreadcrumbItemProps\n extends Omit<AriaBreadcrumbProps, \"className\" | \"style\" | \"children\"> {\n /**\n * The crumb's label.\n *\n * An icon-only crumb has no accessible name, which is an axe `link-name`\n * violation -- both legacy call sites ship one. Pass `aria-label` when the\n * only child is a glyph.\n */\n children?: ReactNode;\n /** Extra classes for the `<li>`, merged last. */\n className?: string;\n /** Destination. Omit it on the last crumb: that one is the current page. */\n href?: string;\n /** Anchor target, forwarded when `href` is set. */\n target?: string;\n /** Anchor rel, forwarded when `href` is set. */\n rel?: string;\n}\n\nexport function BreadcrumbItem({\n className,\n children,\n href,\n target,\n rel,\n \"aria-label\": ariaLabel,\n ...props\n}: BreadcrumbItemProps) {\n return (\n <AriaBreadcrumb\n {...props}\n data-slot=\"breadcrumb-item\"\n className={cn(breadcrumbItemVariants(), className)}\n >\n <AriaLink\n href={href}\n target={target}\n rel={rel}\n aria-label={ariaLabel}\n data-slot=\"breadcrumb-link\"\n className={breadcrumbLinkVariants()}\n >\n {children}\n </AriaLink>\n {/*\n A real element, not a ::after. Generated content is announced by some\n screen readers, and it cannot be read back from the DOM at all -- a\n separator that stopped rendering would be invisible to the suite.\n\n \"/\" rather than the legacy lucide ChevronRight: this library ships no\n icon dependency, all four legacy separators are the default, and a\n chevron glyph does not mirror in RTL while a slash needs no mirroring\n at all.\n */}\n <span\n aria-hidden=\"true\"\n data-slot=\"breadcrumb-separator\"\n className=\"shrink-0 select-none group-data-[current]:hidden\"\n >\n /\n </span>\n </AriaBreadcrumb>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,MAAa,sBAAsB,IAAI;CACrC;CAOA;CAIA;CAIA;AACF,CAAC;AAED,MAAa,yBAAyB,IAAI,CAIxC,sCACF,CAAC;AAED,MAAa,yBAAyB,IAAI;CACxC;CAKA;CACA;CACA;CAMA;CASA;CACA;CACA;CACA;CACA;CAMA;CAEA;CACA;AACF,CAAC;AA2CD,SAAgBA,cAAY,EAC1B,WACA,UACA,cAAc,YAAY,cAC1B,GAAG,SACgB;CACnB,OAOE,oBAAC,OAAD;EAAK,cAAY;EAAW,aAAU;EACpC,UAAA,oBAACC,aAAD;GACE,GAAI;GACJ,cAAY;GACZ,aAAU;GACV,WAAW,GAAG,oBAAoB,GAAG,SAAS;GAE7C;EACc,CAAA;CACd,CAAA;AAET;AA4BA,SAAgB,eAAe,EAC7B,WACA,UACA,MACA,QACA,KACA,cAAc,WACd,GAAG,SACmB;CACtB,OACE,qBAACC,YAAD;EACE,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,uBAAuB,GAAG,SAAS;EAHnD,UAAA,CAKE,oBAACC,MAAD;GACQ;GACE;GACH;GACL,cAAY;GACZ,aAAU;GACV,WAAW,uBAAuB;GAEjC;EACO,CAAA,GAWV,oBAAC,QAAD;GACE,eAAY;GACZ,aAAU;GACV,WAAU;GACX,UAAA;EAEK,CAAA,CACQ;;AAEpB"}
@@ -0,0 +1,2 @@
1
+ import { BreadcrumbItem, BreadcrumbItemProps, Breadcrumbs, BreadcrumbsProps, breadcrumbItemVariants, breadcrumbLinkVariants, breadcrumbsVariants } from "./breadcrumb.js";
2
+ export { BreadcrumbItem, type BreadcrumbItemProps, Breadcrumbs, type BreadcrumbsProps, breadcrumbItemVariants, breadcrumbLinkVariants, breadcrumbsVariants };
@@ -0,0 +1,2 @@
1
+ import { BreadcrumbItem, Breadcrumbs, breadcrumbItemVariants, breadcrumbLinkVariants, breadcrumbsVariants } from "./breadcrumb.js";
2
+ export { BreadcrumbItem, Breadcrumbs, breadcrumbItemVariants, breadcrumbLinkVariants, breadcrumbsVariants };
@@ -21,7 +21,7 @@ import { ReactNode } from "react";
21
21
  * touch and keyboard uniformly. The original had no pressed state at all.
22
22
  */
23
23
  export declare const buttonVariants: (props?: ({
24
- variant?: "primary" | "secondary" | "outline" | "ghost" | "destructive" | "link" | null | undefined;
24
+ variant?: "primary" | "secondary" | "outline" | "outlineNeutral" | "ghost" | "destructive" | "link" | null | undefined;
25
25
  size?: "sm" | "md" | "lg" | "icon" | null | undefined;
26
26
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
27
27
  /**
@@ -30,6 +30,7 @@ const buttonVariants = cva([
30
30
  "[&_svg]:pointer-events-none [&_svg]:shrink-0",
31
31
  "[&_svg:not([class*='size-'])]:size-(--icon-sm)",
32
32
  "outline-none",
33
+ "data-[focus-visible]:outline-solid",
33
34
  "data-[focus-visible]:outline-(length:--focus-ring-width)",
34
35
  "data-[focus-visible]:outline-offset-(--focus-ring-offset)",
35
36
  "data-[focus-visible]:outline-(--color-ring)",
@@ -56,6 +57,12 @@ const buttonVariants = cva([
56
57
  "data-[hovered]:bg-muted",
57
58
  "data-[pressed]:bg-muted data-[pressed]:border-foreground"
58
59
  ],
60
+ outlineNeutral: [
61
+ "bg-background text-foreground border-(length:--border-width) border-border shadow-surface",
62
+ "motion-safe:transition-colors",
63
+ "data-[hovered]:bg-muted",
64
+ "data-[pressed]:bg-muted data-[pressed]:border-foreground"
65
+ ],
59
66
  ghost: [
60
67
  "bg-transparent text-primary-strong",
61
68
  "motion-safe:transition-colors",
@@ -1 +1 @@
1
- {"version":3,"file":"button.js","names":["Button","AriaButton"],"sources":["../../src/button/button.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ReactNode } from \"react\";\nimport { Button as AriaButton, type ButtonProps as AriaButtonProps } from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Variant classes, exported so a consumer can reuse the exact classes we ship\n * rather than guessing at them. This is also the supported answer for the\n * link-shaped cases React Aria's Button cannot cover: it never renders an\n * anchor, so `<a className={buttonVariants({ variant: \"outline\" })}>` is how a\n * real navigation target gets button styling until the Link primitive lands.\n *\n * Ported from the legacy shadcn Button. The variant and size vocabulary is\n * deliberately identical so existing call sites migrate by rename, with six\n * corrections that the Alfred contracts require -- each marked below.\n *\n * Every string here is a STATIC literal. Tailwind scans source text, so a class\n * assembled at runtime is invisible to it and would simply never be emitted.\n *\n * State comes from React Aria data attributes rather than pseudo-classes:\n * data-[focus-visible] is keyboard focus only, and data-[pressed] covers mouse,\n * touch and keyboard uniformly. The original had no pressed state at all.\n */\nexport const buttonVariants = cva(\n [\n \"inline-flex items-center justify-center gap-(--density-control-gap) shrink-0 whitespace-nowrap\",\n // No leading-* here on purpose. tailwind-merge treats a later text-* class\n // as owning line-height (a Tailwind v3 assumption, where text-base carried\n // one), so a leading-* in the base string is silently STRIPPED by cn() once\n // a size variant adds text-base. It is not needed either: the control has a\n // fixed height and centres with flex, so line-height cannot overflow it.\n // A component with auto height must put leading-* AFTER its text-* class.\n \"rounded-md font-sans font-medium\",\n \"cursor-pointer select-none\",\n\n // font-sans above is part of the same problem: a <button> does not inherit\n // the page font, so without a reset it renders in the user-agent face\n // (Arial in Chromium) rather than the design system's. The same applies to\n // bg-transparent: an unstyled <button> paints the user-agent ButtonFace\n // grey -- which is what surfaces the moment a fill class fails to resolve,\n // and is why the paint stories compare against tokens rather than checking\n // for transparency.\n //\n // The reset in @seekalfred/styles is OPT-IN, so a consumer may never load\n // one. A bare <button> then keeps its user-agent border (2px, black, in\n // Chromium) and inline padding, which is a black box around every variant\n // that does not set a border of its own. The original inherited a zeroed\n // border from Tailwind Preflight in the app; a library component cannot\n // assume the consumer supplied one.\n \"border-(length:--border-width) border-transparent bg-transparent\",\n\n // Icon handling, ported verbatim: an svg child is sized unless the caller\n // already said otherwise, and never swallows the press.\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"[&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n\n // CORRECTION 1 -- the original focused with `ring-[3px]`, which is a\n // box-shadow. Box-shadows are discarded under forced-colors, taking the\n // only focus indication with them, and axe has no rule that would catch it\n // (ARCHITECTURE section 5, rule 6). A real outline survives.\n \"outline-none\",\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\n // CORRECTION 2 -- `disabled:pointer-events-none` also kills the cursor, so\n // the control reads as inert rather than as unavailable. React Aria already\n // blocks activation; this only has to say so.\n \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-disabled)\",\n\n // React Aria's own pending state. The original had no equivalent.\n \"data-[pending]:cursor-progress\",\n ],\n {\n variants: {\n variant: {\n // CORRECTION 3 -- the legacy button filled this variant with the brand\n // gradient. It is flat here, because the brand system caps\n // --gradient-brand at one intentional element per view and keeps that\n // budget for the logo and progress bars: \"a gradient on every button\n // spends that budget everywhere. Ours is the flat primary.\"\n //\n // CORRECTION 4 -- the label is ink, not white. Brand orange #FF8431\n // carries white at 2.44:1; ink clears it at 8.35:1, which is the brand\n // system's own --text-on-orange rule. Its Button ships white only as a\n // dated, individually signed-off exception, and this library's contrast\n // contract is build-failing with no exemption mechanism to inherit it.\n //\n // CORRECTION 7 -- hover and pressed are their own TOKENS, not opacity\n // modifiers on the fill. `bg-primary/90` lightened the button toward the\n // page, which reads as receding rather than interactive and was nearly\n // invisible; it also made the label pair unmeasurable, because an alpha\n // composite depends on whatever happens to sit behind it. The brand system\n // steps DOWN its ramp instead -- 500 rest, 600 hover, 700 pressed -- and\n // every one of those fills is now in the contrast contract.\n primary: [\n \"bg-primary text-primary-foreground shadow-surface\",\n \"motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard\",\n \"data-[hovered]:bg-primary-hover\",\n \"data-[pressed]:bg-primary-pressed\",\n ],\n secondary: [\n \"bg-secondary text-secondary-foreground shadow-surface\",\n \"motion-safe:transition-colors\",\n \"data-[hovered]:bg-secondary-hover\",\n \"data-[pressed]:bg-secondary-pressed\",\n ],\n // primary-strong, never primary: the brand orange is 2.44:1 on the page\n // and cannot legally be a label OR a border. That is the whole reason\n // the token exists.\n outline: [\n \"bg-background text-primary-strong border-(length:--border-width) border-primary-strong shadow-surface\",\n \"motion-safe:transition-colors\",\n \"data-[hovered]:bg-muted\",\n \"data-[pressed]:bg-muted data-[pressed]:border-foreground\",\n ],\n ghost: [\n \"bg-transparent text-primary-strong\",\n \"motion-safe:transition-colors\",\n \"data-[hovered]:bg-muted\",\n // CORRECTION 5 -- pressed must not be background-only. A ghost button\n // has no border at rest, so the pressed border is the non-colour cue\n // that survives forced-colors (rule 6 again).\n \"data-[pressed]:bg-muted data-[pressed]:border-primary-strong\",\n ],\n destructive: [\n \"bg-destructive text-destructive-foreground shadow-surface\",\n \"motion-safe:transition-colors\",\n \"data-[hovered]:bg-destructive-hover\",\n \"data-[pressed]:bg-destructive-pressed\",\n ],\n link: [\n \"bg-transparent text-primary-strong underline-offset-(--underline-offset)\",\n \"data-[hovered]:underline\",\n \"data-[pressed]:underline\",\n ],\n },\n size: {\n // Height, padding and label size all come from the control scale, so\n // data-density moves them together. The icon-only variant is square at\n // the md height. The legacy shadcn rule that tightened padding when a\n // child was an icon is gone: it needed three more magic numbers, the\n // brand system has no equivalent, and the gap token now owns icon\n // spacing.\n sm: \"h-(--density-control-h-sm) px-(--density-control-pad-sm) text-sm\",\n md: \"h-(--density-control-h-md) px-(--density-control-pad-md) text-base\",\n lg: \"h-(--density-control-h-lg) px-(--density-control-pad-lg) text-lg\",\n // text-base even though there is no visible label: without it the\n // user-agent default (13.33px in Chromium) leaks in and sizes any\n // em-based child against the wrong root.\n icon: \"size-(--density-control-h-md) px-0 text-base\",\n },\n },\n defaultVariants: {\n variant: \"primary\",\n size: \"md\",\n },\n },\n);\n\n/**\n * Props.\n *\n * CORRECTION 6 -- the legacy Button was a plain `<button>`, so it took\n * `onClick`, `disabled` and Radix's `asChild`. React Aria's Button has none of\n * those: `ButtonProps` explicitly omits `onClick` and `href`. The replacements\n * are `onPress`, `isDisabled`, and `buttonVariants()` on the consumer's own\n * element. See the migration table in the package README.\n *\n * Deliberately NOT intersected with ComponentPropsWithoutRef<\"button\">: that\n * would advertise disabled, tabIndex and role, which React Aria does not\n * implement and silently ignores. React Aria already forwards the native event\n * surface, so nothing is lost.\n */\nexport interface ButtonProps\n extends Omit<AriaButtonProps, \"className\" | \"style\">, VariantProps<typeof buttonVariants> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n /**\n * Icon rendered before the label.\n *\n * Mark the glyph `aria-hidden` -- the label is the accessible name, and a\n * second name source only makes the button announce twice. It is sized by the\n * base `[&_svg]` rule at `--icon-sm` unless it carries a `size-*` class of\n * its own, and spaced by `--density-control-gap`.\n *\n * Two named slots rather than one `icon` plus a position prop: a button can\n * legitimately carry a leading glyph AND a trailing affordance, which one\n * icon and a position cannot express.\n *\n * NOT the route for `size=\"icon\"`. An icon-only button passes its glyph as\n * `children`, which is what gives it its single centred child.\n */\n startIcon?: ReactNode;\n /** Icon rendered after the label. Same rules as {@link ButtonProps.startIcon}. */\n endIcon?: ReactNode;\n}\n\n/**\n * A button.\n *\n * `size=\"icon\"` renders a square button with no visible text, so it **must**\n * carry an `aria-label` -- otherwise it has no accessible name and fails axe's\n * `button-name` rule. The IconOnly story is the guard on that.\n *\n * @example\n * <Button onPress={save}>Save changes</Button>\n * <Button startIcon={<SaveIcon aria-hidden />}>Save changes</Button>\n * <Button endIcon={<ChevronIcon aria-hidden />}>Continue</Button>\n * <Button variant=\"outline\" size=\"icon\" aria-label=\"Close\"><XIcon /></Button>\n */\nexport function Button({\n className,\n variant,\n size,\n startIcon,\n endIcon,\n children,\n ...props\n}: ButtonProps) {\n return (\n <AriaButton\n {...props}\n data-slot=\"button\"\n className={cn(buttonVariants({ variant, size }), className)}\n >\n {/*\n * children passes through UNTOUCHED when neither slot is used, so a\n * button with no icon renders byte-identical DOM to before this prop\n * existed. That is what keeps this an additive change rather than a\n * structural one -- and the childOrder assertions in the stories are what\n * hold it true, since they read the button's direct children.\n *\n * The slot branch has to be a render function because React Aria's\n * children are ChildrenOrFunction<ButtonRenderProps>: a consumer may pass\n * a function to read isPressed or isPending, and wrapping the node\n * directly would silently render that function as nothing. The fragment\n * adds no element of its own.\n */}\n {startIcon === undefined && endIcon === undefined\n ? children\n : (renderProps) => (\n <>\n {startIcon}\n {typeof children === \"function\" ? children(renderProps) : children}\n {endIcon}\n </>\n )}\n </AriaButton>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,iBAAiB,IAC5B;CACE;CAOA;CACA;CAgBA;CAIA;CACA;CAMA;CACA;CACA;CACA;CAKA;CAGA;AACF,GACA;CACE,UAAU;EACR,SAAS;GAoBP,SAAS;IACP;IACA;IACA;IACA;GACF;GACA,WAAW;IACT;IACA;IACA;IACA;GACF;GAIA,SAAS;IACP;IACA;IACA;IACA;GACF;GACA,OAAO;IACL;IACA;IACA;IAIA;GACF;GACA,aAAa;IACX;IACA;IACA;IACA;GACF;GACA,MAAM;IACJ;IACA;IACA;GACF;EACF;EACA,MAAM;GAOJ,IAAI;GACJ,IAAI;GACJ,IAAI;GAIJ,MAAM;EACR;CACF;CACA,iBAAiB;EACf,SAAS;EACT,MAAM;CACR;AACF,CACF;;;;;;;;;;;;;;AAqDA,SAAgBA,SAAO,EACrB,WACA,SACA,MACA,WACA,SACA,UACA,GAAG,SACW;CACd,OACE,oBAACC,QAAD;EACE,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,eAAe;GAAE;GAAS;EAAK,CAAC,GAAG,SAAS;EAezD,UAAA,cAAc,KAAA,KAAa,YAAY,KAAA,IACpC,YACC,gBACC,qBAAA,UAAA,EAAA,UAAA;GACG;GACA,OAAO,aAAa,aAAa,SAAS,WAAW,IAAI;GACzD;EACD,EAAA,CAAA;CAEE,CAAA;AAEhB"}
1
+ {"version":3,"file":"button.js","names":["Button","AriaButton"],"sources":["../../src/button/button.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ReactNode } from \"react\";\nimport { Button as AriaButton, type ButtonProps as AriaButtonProps } from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Variant classes, exported so a consumer can reuse the exact classes we ship\n * rather than guessing at them. This is also the supported answer for the\n * link-shaped cases React Aria's Button cannot cover: it never renders an\n * anchor, so `<a className={buttonVariants({ variant: \"outline\" })}>` is how a\n * real navigation target gets button styling until the Link primitive lands.\n *\n * Ported from the legacy shadcn Button. The variant and size vocabulary is\n * deliberately identical so existing call sites migrate by rename, with six\n * corrections that the Alfred contracts require -- each marked below.\n *\n * Every string here is a STATIC literal. Tailwind scans source text, so a class\n * assembled at runtime is invisible to it and would simply never be emitted.\n *\n * State comes from React Aria data attributes rather than pseudo-classes:\n * data-[focus-visible] is keyboard focus only, and data-[pressed] covers mouse,\n * touch and keyboard uniformly. The original had no pressed state at all.\n */\nexport const buttonVariants = cva(\n [\n \"inline-flex items-center justify-center gap-(--density-control-gap) shrink-0 whitespace-nowrap\",\n // No leading-* here on purpose. tailwind-merge treats a later text-* class\n // as owning line-height (a Tailwind v3 assumption, where text-base carried\n // one), so a leading-* in the base string is silently STRIPPED by cn() once\n // a size variant adds text-base. It is not needed either: the control has a\n // fixed height and centres with flex, so line-height cannot overflow it.\n // A component with auto height must put leading-* AFTER its text-* class.\n \"rounded-md font-sans font-medium\",\n \"cursor-pointer select-none\",\n\n // font-sans above is part of the same problem: a <button> does not inherit\n // the page font, so without a reset it renders in the user-agent face\n // (Arial in Chromium) rather than the design system's. The same applies to\n // bg-transparent: an unstyled <button> paints the user-agent ButtonFace\n // grey -- which is what surfaces the moment a fill class fails to resolve,\n // and is why the paint stories compare against tokens rather than checking\n // for transparency.\n //\n // The reset in @seekalfred/styles is OPT-IN, so a consumer may never load\n // one. A bare <button> then keeps its user-agent border (2px, black, in\n // Chromium) and inline padding, which is a black box around every variant\n // that does not set a border of its own. The original inherited a zeroed\n // border from Tailwind Preflight in the app; a library component cannot\n // assume the consumer supplied one.\n \"border-(length:--border-width) border-transparent bg-transparent\",\n\n // Icon handling, ported verbatim: an svg child is sized unless the caller\n // already said otherwise, and never swallows the press.\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"[&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n\n // CORRECTION 1 -- the original focused with `ring-[3px]`, which is a\n // box-shadow. Box-shadows are discarded under forced-colors, taking the\n // only focus indication with them, and axe has no rule that would catch it\n // (ARCHITECTURE section 5, rule 6). A real outline survives.\n //\n // outline-solid on the focus variant is load-bearing, not decoration.\n // `outline-none` sets BOTH outline-style:none and --tw-outline-style:none,\n // and Tailwind's outline-width utility resolves its style from that same\n // variable -- so the focus variant applied a width and a colour while\n // faithfully re-applying style:none, and painted nothing. The button was\n // keyboard-focusable with no visible ring. axe has no focus-indicator\n // rule, so every automated gate stayed green; the KeyboardFocus story now\n // asserts outlineStyle directly.\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\n // CORRECTION 2 -- `disabled:pointer-events-none` also kills the cursor, so\n // the control reads as inert rather than as unavailable. React Aria already\n // blocks activation; this only has to say so.\n \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-disabled)\",\n\n // React Aria's own pending state. The original had no equivalent.\n \"data-[pending]:cursor-progress\",\n ],\n {\n variants: {\n variant: {\n // CORRECTION 3 -- the legacy button filled this variant with the brand\n // gradient. It is flat here, because the brand system caps\n // --gradient-brand at one intentional element per view and keeps that\n // budget for the logo and progress bars: \"a gradient on every button\n // spends that budget everywhere. Ours is the flat primary.\"\n //\n // CORRECTION 4 -- the label is ink, not white. Brand orange #FF8431\n // carries white at 2.44:1; ink clears it at 8.35:1, which is the brand\n // system's own --text-on-orange rule. Its Button ships white only as a\n // dated, individually signed-off exception, and this library's contrast\n // contract is build-failing with no exemption mechanism to inherit it.\n //\n // CORRECTION 7 -- hover and pressed are their own TOKENS, not opacity\n // modifiers on the fill. `bg-primary/90` lightened the button toward the\n // page, which reads as receding rather than interactive and was nearly\n // invisible; it also made the label pair unmeasurable, because an alpha\n // composite depends on whatever happens to sit behind it. The brand system\n // steps DOWN its ramp instead -- 500 rest, 600 hover, 700 pressed -- and\n // every one of those fills is now in the contrast contract.\n primary: [\n \"bg-primary text-primary-foreground shadow-surface\",\n \"motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard\",\n \"data-[hovered]:bg-primary-hover\",\n \"data-[pressed]:bg-primary-pressed\",\n ],\n secondary: [\n \"bg-secondary text-secondary-foreground shadow-surface\",\n \"motion-safe:transition-colors\",\n \"data-[hovered]:bg-secondary-hover\",\n \"data-[pressed]:bg-secondary-pressed\",\n ],\n // primary-strong, never primary: the brand orange is 2.44:1 on the page\n // and cannot legally be a label OR a border. That is the whole reason\n // the token exists.\n outline: [\n \"bg-background text-primary-strong border-(length:--border-width) border-primary-strong shadow-surface\",\n \"motion-safe:transition-colors\",\n \"data-[hovered]:bg-muted\",\n \"data-[pressed]:bg-muted data-[pressed]:border-foreground\",\n ],\n /*\n * The NEUTRAL outline, and the product's single most-used button:\n * 99 call sites against 32 for the brand-coloured one.\n *\n * It exists because `outline` above is brand-toned -- orange border,\n * orange label -- which is what the product calls `secondaryOutline`.\n * Its plain `outline` is this: a grey border and ordinary body text,\n * for a button that must not compete with the primary action next to\n * it. One name could not cover both, and renaming `outline` would have\n * silently repainted every existing call site of ours instead.\n *\n * `border` rather than `primary-strong`: --color-border is the token\n * already held to the 3:1 non-text floor of WCAG 1.4.11, which is what\n * a border separating a control from the page has to clear.\n */\n outlineNeutral: [\n \"bg-background text-foreground border-(length:--border-width) border-border shadow-surface\",\n \"motion-safe:transition-colors\",\n \"data-[hovered]:bg-muted\",\n \"data-[pressed]:bg-muted data-[pressed]:border-foreground\",\n ],\n ghost: [\n \"bg-transparent text-primary-strong\",\n \"motion-safe:transition-colors\",\n \"data-[hovered]:bg-muted\",\n // CORRECTION 5 -- pressed must not be background-only. A ghost button\n // has no border at rest, so the pressed border is the non-colour cue\n // that survives forced-colors (rule 6 again).\n \"data-[pressed]:bg-muted data-[pressed]:border-primary-strong\",\n ],\n destructive: [\n \"bg-destructive text-destructive-foreground shadow-surface\",\n \"motion-safe:transition-colors\",\n \"data-[hovered]:bg-destructive-hover\",\n \"data-[pressed]:bg-destructive-pressed\",\n ],\n link: [\n \"bg-transparent text-primary-strong underline-offset-(--underline-offset)\",\n \"data-[hovered]:underline\",\n \"data-[pressed]:underline\",\n ],\n },\n size: {\n // Height, padding and label size all come from the control scale, so\n // data-density moves them together. The icon-only variant is square at\n // the md height. The legacy shadcn rule that tightened padding when a\n // child was an icon is gone: it needed three more magic numbers, the\n // brand system has no equivalent, and the gap token now owns icon\n // spacing.\n sm: \"h-(--density-control-h-sm) px-(--density-control-pad-sm) text-sm\",\n md: \"h-(--density-control-h-md) px-(--density-control-pad-md) text-base\",\n lg: \"h-(--density-control-h-lg) px-(--density-control-pad-lg) text-lg\",\n // text-base even though there is no visible label: without it the\n // user-agent default (13.33px in Chromium) leaks in and sizes any\n // em-based child against the wrong root.\n icon: \"size-(--density-control-h-md) px-0 text-base\",\n },\n },\n defaultVariants: {\n variant: \"primary\",\n size: \"md\",\n },\n },\n);\n\n/**\n * Props.\n *\n * CORRECTION 6 -- the legacy Button was a plain `<button>`, so it took\n * `onClick`, `disabled` and Radix's `asChild`. React Aria's Button has none of\n * those: `ButtonProps` explicitly omits `onClick` and `href`. The replacements\n * are `onPress`, `isDisabled`, and `buttonVariants()` on the consumer's own\n * element. See the migration table in the package README.\n *\n * Deliberately NOT intersected with ComponentPropsWithoutRef<\"button\">: that\n * would advertise disabled, tabIndex and role, which React Aria does not\n * implement and silently ignores. React Aria already forwards the native event\n * surface, so nothing is lost.\n */\nexport interface ButtonProps\n extends Omit<AriaButtonProps, \"className\" | \"style\">, VariantProps<typeof buttonVariants> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n /**\n * Icon rendered before the label.\n *\n * Mark the glyph `aria-hidden` -- the label is the accessible name, and a\n * second name source only makes the button announce twice. It is sized by the\n * base `[&_svg]` rule at `--icon-sm` unless it carries a `size-*` class of\n * its own, and spaced by `--density-control-gap`.\n *\n * Two named slots rather than one `icon` plus a position prop: a button can\n * legitimately carry a leading glyph AND a trailing affordance, which one\n * icon and a position cannot express.\n *\n * NOT the route for `size=\"icon\"`. An icon-only button passes its glyph as\n * `children`, which is what gives it its single centred child.\n */\n startIcon?: ReactNode;\n /** Icon rendered after the label. Same rules as {@link ButtonProps.startIcon}. */\n endIcon?: ReactNode;\n}\n\n/**\n * A button.\n *\n * `size=\"icon\"` renders a square button with no visible text, so it **must**\n * carry an `aria-label` -- otherwise it has no accessible name and fails axe's\n * `button-name` rule. The IconOnly story is the guard on that.\n *\n * @example\n * <Button onPress={save}>Save changes</Button>\n * <Button startIcon={<SaveIcon aria-hidden />}>Save changes</Button>\n * <Button endIcon={<ChevronIcon aria-hidden />}>Continue</Button>\n * <Button variant=\"outline\" size=\"icon\" aria-label=\"Close\"><XIcon /></Button>\n */\nexport function Button({\n className,\n variant,\n size,\n startIcon,\n endIcon,\n children,\n ...props\n}: ButtonProps) {\n return (\n <AriaButton\n {...props}\n data-slot=\"button\"\n className={cn(buttonVariants({ variant, size }), className)}\n >\n {/*\n * children passes through UNTOUCHED when neither slot is used, so a\n * button with no icon renders byte-identical DOM to before this prop\n * existed. That is what keeps this an additive change rather than a\n * structural one -- and the childOrder assertions in the stories are what\n * hold it true, since they read the button's direct children.\n *\n * The slot branch has to be a render function because React Aria's\n * children are ChildrenOrFunction<ButtonRenderProps>: a consumer may pass\n * a function to read isPressed or isPending, and wrapping the node\n * directly would silently render that function as nothing. The fragment\n * adds no element of its own.\n */}\n {startIcon === undefined && endIcon === undefined\n ? children\n : (renderProps) => (\n <>\n {startIcon}\n {typeof children === \"function\" ? children(renderProps) : children}\n {endIcon}\n </>\n )}\n </AriaButton>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,iBAAiB,IAC5B;CACE;CAOA;CACA;CAgBA;CAIA;CACA;CAeA;CACA;CACA;CACA;CACA;CAKA;CAGA;AACF,GACA;CACE,UAAU;EACR,SAAS;GAoBP,SAAS;IACP;IACA;IACA;IACA;GACF;GACA,WAAW;IACT;IACA;IACA;IACA;GACF;GAIA,SAAS;IACP;IACA;IACA;IACA;GACF;GAgBA,gBAAgB;IACd;IACA;IACA;IACA;GACF;GACA,OAAO;IACL;IACA;IACA;IAIA;GACF;GACA,aAAa;IACX;IACA;IACA;IACA;GACF;GACA,MAAM;IACJ;IACA;IACA;GACF;EACF;EACA,MAAM;GAOJ,IAAI;GACJ,IAAI;GACJ,IAAI;GAIJ,MAAM;EACR;CACF;CACA,iBAAiB;EACf,SAAS;EACT,MAAM;CACR;AACF,CACF;;;;;;;;;;;;;;AAqDA,SAAgBA,SAAO,EACrB,WACA,SACA,MACA,WACA,SACA,UACA,GAAG,SACW;CACd,OACE,oBAACC,QAAD;EACE,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,eAAe;GAAE;GAAS;EAAK,CAAC,GAAG,SAAS;EAezD,UAAA,cAAc,KAAA,KAAa,YAAY,KAAA,IACpC,YACC,gBACC,qBAAA,UAAA,EAAA,UAAA;GACG;GACA,OAAO,aAAa,aAAa,SAAS,WAAW,IAAI;GACzD;EACD,EAAA,CAAA;CAEE,CAAA;AAEhB"}