@fabio.caffarello/react-design-system 4.0.0 → 4.2.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.
@@ -1,43 +1,43 @@
1
1
  "use client";
2
- var G = Object.defineProperty, H = Object.defineProperties;
3
- var J = Object.getOwnPropertyDescriptors;
4
- var y = Object.getOwnPropertySymbols;
5
- var A = Object.prototype.hasOwnProperty, F = Object.prototype.propertyIsEnumerable;
6
- var $ = (r, n, o) => n in r ? G(r, n, { enumerable: !0, configurable: !0, writable: !0, value: o }) : r[n] = o, u = (r, n) => {
2
+ var T = Object.defineProperty, W = Object.defineProperties;
3
+ var _ = Object.getOwnPropertyDescriptors;
4
+ var u = Object.getOwnPropertySymbols;
5
+ var k = Object.prototype.hasOwnProperty, j = Object.prototype.propertyIsEnumerable;
6
+ var B = (r, n, o) => n in r ? T(r, n, { enumerable: !0, configurable: !0, writable: !0, value: o }) : r[n] = o, m = (r, n) => {
7
7
  for (var o in n || (n = {}))
8
- A.call(n, o) && $(r, o, n[o]);
9
- if (y)
10
- for (var o of y(n))
11
- F.call(n, o) && $(r, o, n[o]);
8
+ k.call(n, o) && B(r, o, n[o]);
9
+ if (u)
10
+ for (var o of u(n))
11
+ j.call(n, o) && B(r, o, n[o]);
12
12
  return r;
13
- }, v = (r, n) => H(r, J(n));
14
- var M = (r, n) => {
13
+ }, g = (r, n) => W(r, _(n));
14
+ var C = (r, n) => {
15
15
  var o = {};
16
- for (var s in r)
17
- A.call(r, s) && n.indexOf(s) < 0 && (o[s] = r[s]);
18
- if (r != null && y)
19
- for (var s of y(r))
20
- n.indexOf(s) < 0 && F.call(r, s) && (o[s] = r[s]);
16
+ for (var t in r)
17
+ k.call(r, t) && n.indexOf(t) < 0 && (o[t] = r[t]);
18
+ if (r != null && u)
19
+ for (var t of u(r))
20
+ n.indexOf(t) < 0 && j.call(r, t) && (o[t] = r[t]);
21
21
  return o;
22
22
  };
23
- import { jsx as i, jsxs as h, Fragment as P } from "react/jsx-runtime";
24
- import { memo as K, forwardRef as Q, useMemo as m } from "react";
25
- import { Slot as U, Slottable as W } from "@radix-ui/react-slot";
26
- import { getRadiusClass as X } from "../../tokens/radius.js";
23
+ import { jsxs as b, jsx as i, Fragment as D } from "react/jsx-runtime";
24
+ import { memo as q, forwardRef as G } from "react";
25
+ import { Slot as H, Slottable as J } from "@radix-ui/react-slot";
26
+ import { getRadiusClass as K } from "../../tokens/radius.js";
27
27
  import { getSpacingClass as e } from "../../tokens/spacing.js";
28
- import { getTypographySize as x, getTypographyClasses as Y } from "../../tokens/typography.js";
29
- import Z from "../Spinner/Spinner.js";
30
- import { cn as t } from "../../utils/cn.js";
31
- import { cva as L } from "../../utils/cva.js";
32
- const I = L(
28
+ import { getTypographySize as v, getTypographyClasses as M } from "../../tokens/typography.js";
29
+ import Q from "../Spinner/Spinner.js";
30
+ import { cn as s } from "../../utils/cn.js";
31
+ import { cva as U } from "../../utils/cva.js";
32
+ const X = U(
33
33
  // Base classes
34
- t(
34
+ s(
35
35
  "inline-flex",
36
36
  "items-center",
37
37
  "justify-center",
38
- Y("button").split(" ")[2] || "font-medium",
38
+ M("button").split(" ")[2] || "font-medium",
39
39
  // Extract font-medium
40
- X("md"),
40
+ K("md"),
41
41
  "transition-colors",
42
42
  "focus:outline-none",
43
43
  "focus:ring-2",
@@ -48,25 +48,25 @@ const I = L(
48
48
  {
49
49
  variants: {
50
50
  variant: {
51
- primary: t(
51
+ primary: s(
52
52
  "bg-surface-brand-strong",
53
53
  "text-fg-inverse",
54
54
  "hover:opacity-90",
55
55
  "focus:ring-line-brand"
56
56
  ),
57
- secondary: t(
57
+ secondary: s(
58
58
  "bg-surface-secondary",
59
59
  "text-fg-inverse",
60
60
  "hover:opacity-90",
61
61
  "focus:ring-line-secondary"
62
62
  ),
63
- error: t(
63
+ error: s(
64
64
  "bg-error",
65
65
  "text-fg-inverse",
66
66
  "hover:opacity-90",
67
67
  "focus:ring-error"
68
68
  ),
69
- outline: t(
69
+ outline: s(
70
70
  "border-2",
71
71
  "border-line-default",
72
72
  "bg-transparent",
@@ -74,13 +74,13 @@ const I = L(
74
74
  "hover:bg-surface-hover",
75
75
  "focus:ring-line-focus"
76
76
  ),
77
- ghost: t(
77
+ ghost: s(
78
78
  "bg-transparent",
79
79
  "text-fg-primary",
80
80
  "hover:bg-surface-hover",
81
81
  "focus:ring-line-focus"
82
82
  ),
83
- iconOnly: t(
83
+ iconOnly: s(
84
84
  "bg-transparent",
85
85
  "text-fg-primary",
86
86
  "hover:bg-surface-hover",
@@ -114,7 +114,7 @@ const I = L(
114
114
  // a disabled link should still receive the same visual
115
115
  // treatment as a disabled chrome variant — the opacity and
116
116
  // cursor signal the disabled state.
117
- link: t(
117
+ link: s(
118
118
  "bg-transparent",
119
119
  "text-fg-brand",
120
120
  "underline-offset-4",
@@ -123,22 +123,22 @@ const I = L(
123
123
  )
124
124
  },
125
125
  size: {
126
- sm: t(
126
+ sm: s(
127
127
  e("md", "px"),
128
128
  e("1.5", "py"),
129
- x("bodySmall"),
129
+ v("bodySmall"),
130
130
  e("1.5", "gap")
131
131
  ),
132
- md: t(
132
+ md: s(
133
133
  e("base", "px"),
134
134
  e("sm", "py"),
135
- x("body"),
135
+ v("body"),
136
136
  e("sm", "gap")
137
137
  ),
138
- lg: t(
138
+ lg: s(
139
139
  e("lg", "px"),
140
140
  e("md", "py"),
141
- x("bodyLarge"),
141
+ v("bodyLarge"),
142
142
  e("2.5", "gap")
143
143
  )
144
144
  }
@@ -148,17 +148,17 @@ const I = L(
148
148
  {
149
149
  variant: "iconOnly",
150
150
  size: "sm",
151
- class: t("h-8", "w-8", e("none", "p"))
151
+ class: s("h-8", "w-8", e("none", "p"))
152
152
  },
153
153
  {
154
154
  variant: "iconOnly",
155
155
  size: "md",
156
- class: t("h-10", "w-10", e("none", "p"))
156
+ class: s("h-10", "w-10", e("none", "p"))
157
157
  },
158
158
  {
159
159
  variant: "iconOnly",
160
160
  size: "lg",
161
- class: t("h-12", "w-12", e("none", "p"))
161
+ class: s("h-12", "w-12", e("none", "p"))
162
162
  },
163
163
  // Link variant zeroes the size's padding via compoundVariants so
164
164
  // the override runs AFTER the size block's `px-N`/`py-N` and
@@ -169,17 +169,17 @@ const I = L(
169
169
  {
170
170
  variant: "link",
171
171
  size: "sm",
172
- class: t(e("none", "px"), e("none", "py"))
172
+ class: s(e("none", "px"), e("none", "py"))
173
173
  },
174
174
  {
175
175
  variant: "link",
176
176
  size: "md",
177
- class: t(e("none", "px"), e("none", "py"))
177
+ class: s(e("none", "px"), e("none", "py"))
178
178
  },
179
179
  {
180
180
  variant: "link",
181
181
  size: "lg",
182
- class: t(e("none", "px"), e("none", "py"))
182
+ class: s(e("none", "px"), e("none", "py"))
183
183
  }
184
184
  ],
185
185
  defaultVariants: {
@@ -200,24 +200,24 @@ function d({
200
200
  }
201
201
  ) : null;
202
202
  }
203
- const nn = K(
204
- Q(function(en, B) {
205
- var V = en, {
203
+ const Y = q(
204
+ G(function(Z, z) {
205
+ var O = Z, {
206
206
  variant: n = "primary",
207
207
  size: o = "md",
208
- isLoading: s = !1,
209
- loadingText: g,
210
- loadingIcon: w,
211
- leftIcon: l,
208
+ isLoading: t = !1,
209
+ loadingText: y,
210
+ loadingIcon: E,
211
+ leftIcon: p,
212
212
  rightIcon: c,
213
- fullWidth: z = !1,
213
+ fullWidth: R = !1,
214
214
  asChild: f = !1,
215
- as: p,
216
- className: O = "",
217
- disabled: S = !1,
218
- children: a,
219
- "aria-label": b
220
- } = V, N = M(V, [
215
+ as: a,
216
+ className: $ = "",
217
+ disabled: h = !1,
218
+ children: l,
219
+ "aria-label": x
220
+ } = O, w = C(O, [
221
221
  "variant",
222
222
  "size",
223
223
  "isLoading",
@@ -233,56 +233,38 @@ const nn = K(
233
233
  "children",
234
234
  "aria-label"
235
235
  ]);
236
- typeof process != "undefined" && process.env.NODE_ENV !== "production" && f && p !== void 0 && p !== "button" && console.warn(
236
+ typeof process != "undefined" && process.env.NODE_ENV !== "production" && f && a !== void 0 && a !== "button" && console.warn(
237
237
  "[Button] `as` is ignored when `asChild` is true; the child element is used as the root. Drop one of the two props to silence this warning."
238
238
  );
239
- const k = f ? U : p != null ? p : "button", T = m(
240
- () => t(
241
- I({
242
- variant: n,
243
- size: o
244
- }),
245
- z && "w-full",
246
- O
247
- ),
248
- [n, o, z, O]
249
- ), j = m(
250
- () => n === "iconOnly" || !a && (l || c),
251
- [n, a, l, c]
252
- ), _ = m(
253
- () => j && !b && !a ? "Button" : b,
254
- [j, b, a]
255
- ), C = m(() => n === "error" ? "primary" : n === "primary" || n === "secondary" ? "neutral" : "primary", [n]), D = m(
256
- () => o === "sm" ? "sm" : o === "lg" ? "lg" : "md",
257
- [o]
258
- ), q = m(
259
- () => w || /* @__PURE__ */ i(Z, { size: D, variant: C }),
260
- [w, D, C]
261
- ), E = !f && (p === void 0 || p === "button") && !N.type ? "button" : void 0, R = u(u({
262
- className: T,
263
- disabled: S || s,
264
- "aria-busy": s,
265
- "aria-label": _,
266
- "aria-disabled": S || s
267
- }, E ? { type: E } : {}), N);
268
- return f ? /* @__PURE__ */ h(k, v(u({ ref: B }, R), { children: [
269
- l && /* @__PURE__ */ i(d, { position: "left", children: l }),
270
- /* @__PURE__ */ i(W, { children: a }),
239
+ const S = f ? H : a != null ? a : "button", A = s(
240
+ X({ variant: n, size: o }),
241
+ R && "w-full",
242
+ $
243
+ ), F = (n === "iconOnly" || !l && (p || c)) && !x && !l ? "Button" : x, P = E || /* @__PURE__ */ i(Q, { size: o === "sm" ? "sm" : o === "lg" ? "lg" : "md", variant: n === "error" ? "primary" : n === "primary" || n === "secondary" ? "neutral" : "primary" }), N = !f && (a === void 0 || a === "button") && !w.type ? "button" : void 0, V = m(m({
244
+ className: A,
245
+ disabled: h || t,
246
+ "aria-busy": t,
247
+ "aria-label": F,
248
+ "aria-disabled": h || t
249
+ }, N ? { type: N } : {}), w);
250
+ return f ? /* @__PURE__ */ b(S, g(m({ ref: z }, V), { children: [
251
+ p && /* @__PURE__ */ i(d, { position: "left", children: p }),
252
+ /* @__PURE__ */ i(J, { children: l }),
271
253
  c && /* @__PURE__ */ i(d, { position: "right", children: c })
272
- ] })) : /* @__PURE__ */ i(k, v(u({ ref: B }, R), { children: s ? /* @__PURE__ */ h(P, { children: [
273
- q,
274
- g && /* @__PURE__ */ i("span", { className: e("sm", "ml"), children: g }),
275
- !g && a && /* @__PURE__ */ i("span", { className: `${e("sm", "ml")} opacity-0`, children: a })
276
- ] }) : /* @__PURE__ */ h(P, { children: [
277
- l && /* @__PURE__ */ i(d, { position: "left", children: l }),
278
- a,
254
+ ] })) : /* @__PURE__ */ i(S, g(m({ ref: z }, V), { children: t ? /* @__PURE__ */ b(D, { children: [
255
+ P,
256
+ y && /* @__PURE__ */ i("span", { className: e("sm", "ml"), children: y }),
257
+ !y && l && /* @__PURE__ */ i("span", { className: `${e("sm", "ml")} opacity-0`, children: l })
258
+ ] }) : /* @__PURE__ */ b(D, { children: [
259
+ p && /* @__PURE__ */ i(d, { position: "left", children: p }),
260
+ l,
279
261
  c && /* @__PURE__ */ i(d, { position: "right", children: c })
280
262
  ] }) }));
281
263
  })
282
264
  );
283
- nn.displayName = "Button";
265
+ Y.displayName = "Button";
284
266
  export {
285
- nn as Button,
286
- nn as default
267
+ Y as Button,
268
+ Y as default
287
269
  };
288
270
  //# sourceMappingURL=Button.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Button.js","sources":["../../../../../src/ui/primitives/Button/Button.tsx"],"sourcesContent":["import { forwardRef, memo, useMemo } from \"react\";\nimport type { ButtonHTMLAttributes, ReactNode, ElementType } from \"react\";\nimport { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport { getRadiusClass } from \"../../tokens/radius\";\n\n// Ambient declaration so the dev-only warn below typechecks without\n// pulling @types/node into the app tsconfig. At runtime the consumer's\n// bundler (webpack/turbopack/etc.) replaces `process.env.NODE_ENV` with\n// a literal; the `typeof process` guard keeps the branch safe in\n// browser/edge runtimes where `process` doesn't exist.\ndeclare const process: { env: { NODE_ENV?: string } };\nimport { getSpacingClass } from \"../../tokens/spacing\";\nimport {\n getTypographyClasses,\n getTypographySize,\n} from \"../../tokens/typography\";\nimport { cn, cva } from \"../../utils\";\nimport Spinner from \"../Spinner/Spinner\";\n\nexport type ButtonVariant =\n | \"primary\"\n | \"secondary\"\n | \"error\"\n | \"outline\"\n | \"ghost\"\n | \"iconOnly\"\n | \"link\";\nexport type ButtonSize = \"sm\" | \"md\" | \"lg\";\n\nexport interface ButtonProps extends Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n \"as\"\n> {\n variant?: ButtonVariant;\n size?: ButtonSize;\n isLoading?: boolean;\n loadingText?: string;\n loadingIcon?: ReactNode;\n leftIcon?: ReactNode;\n rightIcon?: ReactNode;\n fullWidth?: boolean;\n as?: ElementType;\n href?: string;\n target?: string;\n /**\n * When true, render via Radix `Slot`: classes, ARIA, ref and remaining\n * props are projected onto the single child element instead of an\n * intrinsic `<button>`. The child keeps its own element type and props\n * intact — including framework-native props like `<Link href prefetch>`.\n *\n * `asChild` takes precedence over `as`: if both are supplied, `as` is\n * ignored and a dev-only warning is logged.\n *\n * @example\n * ```tsx\n * <Button asChild variant=\"primary\">\n * <Link href=\"/profile\" prefetch>Open profile</Link>\n * </Button>\n * // → <a class=\"…button classes\" href=\"/profile\" data-prefetch>Open profile</a>\n * ```\n */\n asChild?: boolean;\n}\n\n/**\n * Button Variants using CVA\n * Type-safe variant system for Button component\n */\nconst buttonVariants = cva(\n // Base classes\n cn(\n \"inline-flex\",\n \"items-center\",\n \"justify-center\",\n getTypographyClasses(\"button\").split(\" \")[2] || \"font-medium\", // Extract font-medium\n getRadiusClass(\"md\"),\n \"transition-colors\",\n \"focus:outline-none\",\n \"focus:ring-2\",\n \"focus:ring-offset-2\",\n \"disabled:opacity-50\",\n \"disabled:cursor-not-allowed\",\n ),\n {\n variants: {\n variant: {\n primary: cn(\n \"bg-surface-brand-strong\",\n \"text-fg-inverse\",\n \"hover:opacity-90\",\n \"focus:ring-line-brand\",\n ),\n secondary: cn(\n \"bg-surface-secondary\",\n \"text-fg-inverse\",\n \"hover:opacity-90\",\n \"focus:ring-line-secondary\",\n ),\n error: cn(\n \"bg-error\",\n \"text-fg-inverse\",\n \"hover:opacity-90\",\n \"focus:ring-error\",\n ),\n outline: cn(\n \"border-2\",\n \"border-line-default\",\n \"bg-transparent\",\n \"text-fg-primary\",\n \"hover:bg-surface-hover\",\n \"focus:ring-line-focus\",\n ),\n ghost: cn(\n \"bg-transparent\",\n \"text-fg-primary\",\n \"hover:bg-surface-hover\",\n \"focus:ring-line-focus\",\n ),\n iconOnly: cn(\n \"bg-transparent\",\n \"text-fg-primary\",\n \"hover:bg-surface-hover\",\n \"focus:ring-line-focus\",\n getSpacingClass(\"none\", \"p\"),\n ),\n // Textual call-to-action — brand-coloured text, underline on\n // hover, no chrome (no surface, no border). Padding is zeroed\n // out per size in compoundVariants so the bounding box hugs\n // the text (height intrinsic). Issue #156.\n //\n // Focus ring: `focus:ring-line-focus` keeps the same focus\n // family the other no-chrome variants use (ghost, outline,\n // iconOnly). The base's `focus:ring-2` + `focus:ring-offset-2`\n // produce a 2px ring 2px away from the text bounding box; the\n // 2px offset is rendered in surface-base so the ring contrasts\n // against surface (≥3:1 for WCAG 2.4.11, verified) rather than\n // colliding with the brand-coloured text. The element keeps\n // `getRadiusClass(\"md\")` from the base, which is invisible\n // without chrome but rounds the focus ring corners.\n //\n // hover:underline pairs with `underline-offset-4` so the\n // underline that appears on hover sits clear of the descenders\n // (WCAG-aligned with link best practice). At rest the link\n // carries no underline — the brand colour alone signals\n // affordance, matching the shadcn convention this variant is\n // modelled on (see issue body).\n //\n // disabled: inherits opacity-50 + cursor-not-allowed from the\n // base. We do NOT special-case `disabled:no-underline` because\n // a disabled link should still receive the same visual\n // treatment as a disabled chrome variant — the opacity and\n // cursor signal the disabled state.\n link: cn(\n \"bg-transparent\",\n \"text-fg-brand\",\n \"underline-offset-4\",\n \"hover:underline\",\n \"focus:ring-line-focus\",\n ),\n },\n size: {\n sm: cn(\n getSpacingClass(\"md\", \"px\"),\n getSpacingClass(\"1.5\", \"py\"),\n getTypographySize(\"bodySmall\"),\n getSpacingClass(\"1.5\", \"gap\"),\n ),\n md: cn(\n getSpacingClass(\"base\", \"px\"),\n getSpacingClass(\"sm\", \"py\"),\n getTypographySize(\"body\"),\n getSpacingClass(\"sm\", \"gap\"),\n ),\n lg: cn(\n getSpacingClass(\"lg\", \"px\"),\n getSpacingClass(\"md\", \"py\"),\n getTypographySize(\"bodyLarge\"),\n getSpacingClass(\"2.5\", \"gap\"),\n ),\n },\n },\n compoundVariants: [\n // IconOnly variant has different sizing\n {\n variant: \"iconOnly\",\n size: \"sm\",\n class: cn(\"h-8\", \"w-8\", getSpacingClass(\"none\", \"p\")),\n },\n {\n variant: \"iconOnly\",\n size: \"md\",\n class: cn(\"h-10\", \"w-10\", getSpacingClass(\"none\", \"p\")),\n },\n {\n variant: \"iconOnly\",\n size: \"lg\",\n class: cn(\"h-12\", \"w-12\", getSpacingClass(\"none\", \"p\")),\n },\n // Link variant zeroes the size's padding via compoundVariants so\n // the override runs AFTER the size block's `px-N`/`py-N` and\n // twMerge picks the zero value (`cn` joins variant before\n // compound). The size's typography and gap survive — the link's\n // text scales with size and icons still get gap-N when present.\n // Issue #156.\n {\n variant: \"link\",\n size: \"sm\",\n class: cn(getSpacingClass(\"none\", \"px\"), getSpacingClass(\"none\", \"py\")),\n },\n {\n variant: \"link\",\n size: \"md\",\n class: cn(getSpacingClass(\"none\", \"px\"), getSpacingClass(\"none\", \"py\")),\n },\n {\n variant: \"link\",\n size: \"lg\",\n class: cn(getSpacingClass(\"none\", \"px\"), getSpacingClass(\"none\", \"py\")),\n },\n ],\n defaultVariants: {\n variant: \"primary\",\n size: \"md\",\n },\n },\n);\n\n/**\n * Icon Wrapper Component\n * Handles icon spacing and alignment consistently\n */\nfunction IconWrapper({\n children,\n position,\n}: {\n children: ReactNode;\n position: \"left\" | \"right\";\n}) {\n if (!children) return null;\n\n return (\n <span\n className={`inline-flex items-center ${position === \"left\" ? getSpacingClass(\"none\", \"mr\") : getSpacingClass(\"none\", \"ml\")}`}\n >\n {children}\n </span>\n );\n}\n\n/**\n * Button Component\n *\n * A styled button with variants, sizes, and loading states.\n *\n * Polymorphism — two APIs:\n * - `as` (legacy): swap the root element type. `<Button as={Link} href=\"…\">`.\n * - `asChild` (recommended): project Button's styling onto the single\n * child element. Idiomatic Radix Slot pattern, preserves the child's\n * own props and TS type (e.g. `<Link href prefetch>`). When `asChild`\n * is true, `as` is ignored.\n *\n * @example\n * ```tsx\n * // Basic usage\n * <Button variant=\"primary\" size=\"md\" onClick={handleClick}>Click me</Button>\n *\n * // With icons\n * <Button leftIcon={<Icon />} rightIcon={<Icon />}>Action</Button>\n *\n * // Loading state\n * <Button isLoading loadingText=\"Saving...\">Save</Button>\n *\n * // Polymorphic via asChild (preserves Next Link's TS type and native props)\n * <Button asChild variant=\"primary\">\n * <Link href=\"/page\" prefetch>Open</Link>\n * </Button>\n *\n * // Polymorphic via legacy `as`\n * <Button as=\"a\" href=\"/page\">Navigate</Button>\n * ```\n */\nconst Button = memo(\n forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n {\n variant = \"primary\",\n size = \"md\",\n isLoading = false,\n loadingText,\n loadingIcon,\n leftIcon,\n rightIcon,\n fullWidth = false,\n asChild = false,\n as,\n className = \"\",\n disabled = false,\n children,\n \"aria-label\": ariaLabel,\n ...props\n },\n ref,\n ) {\n // asChild wins over `as`. Warn in dev so the precedence is observable\n // instead of being a silent override. Production builds strip this branch.\n if (\n typeof process !== \"undefined\" &&\n process.env.NODE_ENV !== \"production\" &&\n asChild &&\n as !== undefined &&\n as !== \"button\"\n ) {\n console.warn(\n \"[Button] `as` is ignored when `asChild` is true; the child element is used as the root. Drop one of the two props to silence this warning.\",\n );\n }\n\n const Component: ElementType = asChild ? Slot : (as ?? \"button\");\n\n // Memoize classes computation\n const classes = useMemo(\n () =>\n cn(\n buttonVariants({\n variant,\n size,\n }),\n fullWidth && \"w-full\",\n className,\n ),\n [variant, size, fullWidth, className],\n );\n\n // Memoize icon-only check\n const isIconOnly = useMemo(\n () => variant === \"iconOnly\" || (!children && (leftIcon || rightIcon)),\n [variant, children, leftIcon, rightIcon],\n );\n\n // Memoize aria label\n const finalAriaLabel = useMemo(\n () =>\n isIconOnly && !ariaLabel && !children\n ? \"Button\" // Fallback, but should be provided\n : ariaLabel,\n [isIconOnly, ariaLabel, children],\n );\n\n // Memoize spinner variant computation\n const spinnerVariant = useMemo((): \"primary\" | \"secondary\" | \"neutral\" => {\n if (variant === \"error\") return \"primary\"; // Red buttons use primary spinner (white)\n if (variant === \"primary\" || variant === \"secondary\") return \"neutral\"; // Colored buttons use neutral spinner\n return \"primary\"; // Default\n }, [variant]);\n\n // Memoize spinner size\n const spinnerSize = useMemo(\n () => (size === \"sm\" ? \"sm\" : size === \"lg\" ? \"lg\" : \"md\"),\n [size],\n );\n\n // Memoize loading icon\n const displayLoadingIcon = useMemo(\n () =>\n loadingIcon || <Spinner size={spinnerSize} variant={spinnerVariant} />,\n [loadingIcon, spinnerSize, spinnerVariant],\n );\n\n // Build button props (spread props at the end to allow overrides).\n // `type=\"button\"` default applies only when rendering an intrinsic\n // <button>. asChild and `as` (custom) projections leave it off so we\n // don't paint a meaningless `type` attribute onto <a> / <Link>.\n const defaultType =\n !asChild && (as === undefined || as === \"button\") && !props.type\n ? \"button\"\n : undefined;\n const buttonProps = {\n className: classes,\n disabled: disabled || isLoading,\n \"aria-busy\": isLoading,\n \"aria-label\": finalAriaLabel,\n \"aria-disabled\": disabled || isLoading,\n ...(defaultType ? { type: defaultType } : {}),\n ...props,\n };\n\n // asChild path: render Slot with the icons and Slottable as direct\n // sibling children (NOT wrapped in a React.Fragment). Slot's\n // children-processing inspects each direct child for Slottable; if\n // the children are wrapped in a Fragment, Slot's SlotClone merges\n // the projected props into the Fragment itself (a silent no-op for\n // className / aria / ref), and nothing reaches the consumer's\n // element. Hence the two distinct branches below: same content\n // shape, different host element, different child layout.\n if (asChild) {\n return (\n <Component ref={ref} {...buttonProps}>\n {leftIcon && <IconWrapper position=\"left\">{leftIcon}</IconWrapper>}\n <Slottable>{children}</Slottable>\n {rightIcon && <IconWrapper position=\"right\">{rightIcon}</IconWrapper>}\n </Component>\n );\n }\n\n return (\n <Component ref={ref} {...buttonProps}>\n {isLoading ? (\n <>\n {displayLoadingIcon}\n {loadingText && (\n <span className={getSpacingClass(\"sm\", \"ml\")}>{loadingText}</span>\n )}\n {!loadingText && children && (\n <span className={`${getSpacingClass(\"sm\", \"ml\")} opacity-0`}>\n {children}\n </span>\n )}\n </>\n ) : (\n <>\n {leftIcon && <IconWrapper position=\"left\">{leftIcon}</IconWrapper>}\n {children}\n {rightIcon && (\n <IconWrapper position=\"right\">{rightIcon}</IconWrapper>\n )}\n </>\n )}\n </Component>\n );\n }),\n);\n\nButton.displayName = \"Button\";\n\nexport default Button;\nexport { Button };\n"],"names":["buttonVariants","cva","cn","getTypographyClasses","getRadiusClass","getSpacingClass","getTypographySize","IconWrapper","children","position","jsx","Button","memo","forwardRef","_a","ref","_b","variant","size","isLoading","loadingText","loadingIcon","leftIcon","rightIcon","fullWidth","asChild","as","className","disabled","ariaLabel","props","__objRest","Component","Slot","classes","useMemo","isIconOnly","finalAriaLabel","spinnerVariant","spinnerSize","displayLoadingIcon","Spinner","defaultType","buttonProps","__spreadValues","jsxs","__spreadProps","Slottable","Fragment"],"mappings":"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"}
1
+ {"version":3,"file":"Button.js","sources":["../../../../../src/ui/primitives/Button/Button.tsx"],"sourcesContent":["import { forwardRef, memo } from \"react\";\nimport type { ButtonHTMLAttributes, ReactNode, ElementType } from \"react\";\nimport { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport { getRadiusClass } from \"../../tokens/radius\";\n\n// Ambient declaration so the dev-only warn below typechecks without\n// pulling @types/node into the app tsconfig. At runtime the consumer's\n// bundler (webpack/turbopack/etc.) replaces `process.env.NODE_ENV` with\n// a literal; the `typeof process` guard keeps the branch safe in\n// browser/edge runtimes where `process` doesn't exist.\ndeclare const process: { env: { NODE_ENV?: string } };\nimport { getSpacingClass } from \"../../tokens/spacing\";\nimport {\n getTypographyClasses,\n getTypographySize,\n} from \"../../tokens/typography\";\nimport { cn, cva } from \"../../utils\";\nimport Spinner from \"../Spinner/Spinner\";\n\nexport type ButtonVariant =\n | \"primary\"\n | \"secondary\"\n | \"error\"\n | \"outline\"\n | \"ghost\"\n | \"iconOnly\"\n | \"link\";\nexport type ButtonSize = \"sm\" | \"md\" | \"lg\";\n\nexport interface ButtonProps extends Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n \"as\"\n> {\n variant?: ButtonVariant;\n size?: ButtonSize;\n isLoading?: boolean;\n loadingText?: string;\n loadingIcon?: ReactNode;\n leftIcon?: ReactNode;\n rightIcon?: ReactNode;\n fullWidth?: boolean;\n as?: ElementType;\n href?: string;\n target?: string;\n /**\n * When true, render via Radix `Slot`: classes, ARIA, ref and remaining\n * props are projected onto the single child element instead of an\n * intrinsic `<button>`. The child keeps its own element type and props\n * intact — including framework-native props like `<Link href prefetch>`.\n *\n * `asChild` takes precedence over `as`: if both are supplied, `as` is\n * ignored and a dev-only warning is logged.\n *\n * @example\n * ```tsx\n * <Button asChild variant=\"primary\">\n * <Link href=\"/profile\" prefetch>Open profile</Link>\n * </Button>\n * // → <a class=\"…button classes\" href=\"/profile\" data-prefetch>Open profile</a>\n * ```\n */\n asChild?: boolean;\n}\n\n/**\n * Button Variants using CVA\n * Type-safe variant system for Button component\n */\nconst buttonVariants = cva(\n // Base classes\n cn(\n \"inline-flex\",\n \"items-center\",\n \"justify-center\",\n getTypographyClasses(\"button\").split(\" \")[2] || \"font-medium\", // Extract font-medium\n getRadiusClass(\"md\"),\n \"transition-colors\",\n \"focus:outline-none\",\n \"focus:ring-2\",\n \"focus:ring-offset-2\",\n \"disabled:opacity-50\",\n \"disabled:cursor-not-allowed\",\n ),\n {\n variants: {\n variant: {\n primary: cn(\n \"bg-surface-brand-strong\",\n \"text-fg-inverse\",\n \"hover:opacity-90\",\n \"focus:ring-line-brand\",\n ),\n secondary: cn(\n \"bg-surface-secondary\",\n \"text-fg-inverse\",\n \"hover:opacity-90\",\n \"focus:ring-line-secondary\",\n ),\n error: cn(\n \"bg-error\",\n \"text-fg-inverse\",\n \"hover:opacity-90\",\n \"focus:ring-error\",\n ),\n outline: cn(\n \"border-2\",\n \"border-line-default\",\n \"bg-transparent\",\n \"text-fg-primary\",\n \"hover:bg-surface-hover\",\n \"focus:ring-line-focus\",\n ),\n ghost: cn(\n \"bg-transparent\",\n \"text-fg-primary\",\n \"hover:bg-surface-hover\",\n \"focus:ring-line-focus\",\n ),\n iconOnly: cn(\n \"bg-transparent\",\n \"text-fg-primary\",\n \"hover:bg-surface-hover\",\n \"focus:ring-line-focus\",\n getSpacingClass(\"none\", \"p\"),\n ),\n // Textual call-to-action — brand-coloured text, underline on\n // hover, no chrome (no surface, no border). Padding is zeroed\n // out per size in compoundVariants so the bounding box hugs\n // the text (height intrinsic). Issue #156.\n //\n // Focus ring: `focus:ring-line-focus` keeps the same focus\n // family the other no-chrome variants use (ghost, outline,\n // iconOnly). The base's `focus:ring-2` + `focus:ring-offset-2`\n // produce a 2px ring 2px away from the text bounding box; the\n // 2px offset is rendered in surface-base so the ring contrasts\n // against surface (≥3:1 for WCAG 2.4.11, verified) rather than\n // colliding with the brand-coloured text. The element keeps\n // `getRadiusClass(\"md\")` from the base, which is invisible\n // without chrome but rounds the focus ring corners.\n //\n // hover:underline pairs with `underline-offset-4` so the\n // underline that appears on hover sits clear of the descenders\n // (WCAG-aligned with link best practice). At rest the link\n // carries no underline — the brand colour alone signals\n // affordance, matching the shadcn convention this variant is\n // modelled on (see issue body).\n //\n // disabled: inherits opacity-50 + cursor-not-allowed from the\n // base. We do NOT special-case `disabled:no-underline` because\n // a disabled link should still receive the same visual\n // treatment as a disabled chrome variant — the opacity and\n // cursor signal the disabled state.\n link: cn(\n \"bg-transparent\",\n \"text-fg-brand\",\n \"underline-offset-4\",\n \"hover:underline\",\n \"focus:ring-line-focus\",\n ),\n },\n size: {\n sm: cn(\n getSpacingClass(\"md\", \"px\"),\n getSpacingClass(\"1.5\", \"py\"),\n getTypographySize(\"bodySmall\"),\n getSpacingClass(\"1.5\", \"gap\"),\n ),\n md: cn(\n getSpacingClass(\"base\", \"px\"),\n getSpacingClass(\"sm\", \"py\"),\n getTypographySize(\"body\"),\n getSpacingClass(\"sm\", \"gap\"),\n ),\n lg: cn(\n getSpacingClass(\"lg\", \"px\"),\n getSpacingClass(\"md\", \"py\"),\n getTypographySize(\"bodyLarge\"),\n getSpacingClass(\"2.5\", \"gap\"),\n ),\n },\n },\n compoundVariants: [\n // IconOnly variant has different sizing\n {\n variant: \"iconOnly\",\n size: \"sm\",\n class: cn(\"h-8\", \"w-8\", getSpacingClass(\"none\", \"p\")),\n },\n {\n variant: \"iconOnly\",\n size: \"md\",\n class: cn(\"h-10\", \"w-10\", getSpacingClass(\"none\", \"p\")),\n },\n {\n variant: \"iconOnly\",\n size: \"lg\",\n class: cn(\"h-12\", \"w-12\", getSpacingClass(\"none\", \"p\")),\n },\n // Link variant zeroes the size's padding via compoundVariants so\n // the override runs AFTER the size block's `px-N`/`py-N` and\n // twMerge picks the zero value (`cn` joins variant before\n // compound). The size's typography and gap survive — the link's\n // text scales with size and icons still get gap-N when present.\n // Issue #156.\n {\n variant: \"link\",\n size: \"sm\",\n class: cn(getSpacingClass(\"none\", \"px\"), getSpacingClass(\"none\", \"py\")),\n },\n {\n variant: \"link\",\n size: \"md\",\n class: cn(getSpacingClass(\"none\", \"px\"), getSpacingClass(\"none\", \"py\")),\n },\n {\n variant: \"link\",\n size: \"lg\",\n class: cn(getSpacingClass(\"none\", \"px\"), getSpacingClass(\"none\", \"py\")),\n },\n ],\n defaultVariants: {\n variant: \"primary\",\n size: \"md\",\n },\n },\n);\n\n/**\n * Icon Wrapper Component\n * Handles icon spacing and alignment consistently\n */\nfunction IconWrapper({\n children,\n position,\n}: {\n children: ReactNode;\n position: \"left\" | \"right\";\n}) {\n if (!children) return null;\n\n return (\n <span\n className={`inline-flex items-center ${position === \"left\" ? getSpacingClass(\"none\", \"mr\") : getSpacingClass(\"none\", \"ml\")}`}\n >\n {children}\n </span>\n );\n}\n\n/**\n * Button Component\n *\n * A styled button with variants, sizes, and loading states.\n *\n * Polymorphism — two APIs:\n * - `as` (legacy): swap the root element type. `<Button as={Link} href=\"…\">`.\n * - `asChild` (recommended): project Button's styling onto the single\n * child element. Idiomatic Radix Slot pattern, preserves the child's\n * own props and TS type (e.g. `<Link href prefetch>`). When `asChild`\n * is true, `as` is ignored.\n *\n * @example\n * ```tsx\n * // Basic usage\n * <Button variant=\"primary\" size=\"md\" onClick={handleClick}>Click me</Button>\n *\n * // With icons\n * <Button leftIcon={<Icon />} rightIcon={<Icon />}>Action</Button>\n *\n * // Loading state\n * <Button isLoading loadingText=\"Saving...\">Save</Button>\n *\n * // Polymorphic via asChild (preserves Next Link's TS type and native props)\n * <Button asChild variant=\"primary\">\n * <Link href=\"/page\" prefetch>Open</Link>\n * </Button>\n *\n * // Polymorphic via legacy `as`\n * <Button as=\"a\" href=\"/page\">Navigate</Button>\n * ```\n */\nconst Button = memo(\n forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n {\n variant = \"primary\",\n size = \"md\",\n isLoading = false,\n loadingText,\n loadingIcon,\n leftIcon,\n rightIcon,\n fullWidth = false,\n asChild = false,\n as,\n className = \"\",\n disabled = false,\n children,\n \"aria-label\": ariaLabel,\n ...props\n },\n ref,\n ) {\n // asChild wins over `as`. Warn in dev so the precedence is observable\n // instead of being a silent override. Production builds strip this branch.\n if (\n typeof process !== \"undefined\" &&\n process.env.NODE_ENV !== \"production\" &&\n asChild &&\n as !== undefined &&\n as !== \"button\"\n ) {\n console.warn(\n \"[Button] `as` is ignored when `asChild` is true; the child element is used as the root. Drop one of the two props to silence this warning.\",\n );\n }\n\n const Component: ElementType = asChild ? Slot : (as ?? \"button\");\n\n // These were `useMemo` originally — all decorative (class-string\n // concat, small boolean/string/JSX picks), none gating render-driving\n // state. Inlined so Button carries NO React client API and qualifies\n // for the `./server` entry (issue #224), the same de-memoization\n // precedent as Badge/Label/Card in #155. `React.memo` on the\n // component is preserved and still gates re-renders at the\n // consumer-prop boundary; the inlined work is nanoseconds.\n const classes = cn(\n buttonVariants({ variant, size }),\n fullWidth && \"w-full\",\n className,\n );\n\n const isIconOnly =\n variant === \"iconOnly\" || (!children && (leftIcon || rightIcon));\n\n const finalAriaLabel =\n isIconOnly && !ariaLabel && !children\n ? \"Button\" // Fallback, but should be provided\n : ariaLabel;\n\n const spinnerVariant: \"primary\" | \"secondary\" | \"neutral\" =\n variant === \"error\"\n ? \"primary\" // Red buttons use primary spinner (white)\n : variant === \"primary\" || variant === \"secondary\"\n ? \"neutral\" // Colored buttons use neutral spinner\n : \"primary\";\n\n const spinnerSize = size === \"sm\" ? \"sm\" : size === \"lg\" ? \"lg\" : \"md\";\n\n const displayLoadingIcon = loadingIcon || (\n <Spinner size={spinnerSize} variant={spinnerVariant} />\n );\n\n // Build button props (spread props at the end to allow overrides).\n // `type=\"button\"` default applies only when rendering an intrinsic\n // <button>. asChild and `as` (custom) projections leave it off so we\n // don't paint a meaningless `type` attribute onto <a> / <Link>.\n const defaultType =\n !asChild && (as === undefined || as === \"button\") && !props.type\n ? \"button\"\n : undefined;\n const buttonProps = {\n className: classes,\n disabled: disabled || isLoading,\n \"aria-busy\": isLoading,\n \"aria-label\": finalAriaLabel,\n \"aria-disabled\": disabled || isLoading,\n ...(defaultType ? { type: defaultType } : {}),\n ...props,\n };\n\n // asChild path: render Slot with the icons and Slottable as direct\n // sibling children (NOT wrapped in a React.Fragment). Slot's\n // children-processing inspects each direct child for Slottable; if\n // the children are wrapped in a Fragment, Slot's SlotClone merges\n // the projected props into the Fragment itself (a silent no-op for\n // className / aria / ref), and nothing reaches the consumer's\n // element. Hence the two distinct branches below: same content\n // shape, different host element, different child layout.\n if (asChild) {\n return (\n <Component ref={ref} {...buttonProps}>\n {leftIcon && <IconWrapper position=\"left\">{leftIcon}</IconWrapper>}\n <Slottable>{children}</Slottable>\n {rightIcon && <IconWrapper position=\"right\">{rightIcon}</IconWrapper>}\n </Component>\n );\n }\n\n return (\n <Component ref={ref} {...buttonProps}>\n {isLoading ? (\n <>\n {displayLoadingIcon}\n {loadingText && (\n <span className={getSpacingClass(\"sm\", \"ml\")}>{loadingText}</span>\n )}\n {!loadingText && children && (\n <span className={`${getSpacingClass(\"sm\", \"ml\")} opacity-0`}>\n {children}\n </span>\n )}\n </>\n ) : (\n <>\n {leftIcon && <IconWrapper position=\"left\">{leftIcon}</IconWrapper>}\n {children}\n {rightIcon && (\n <IconWrapper position=\"right\">{rightIcon}</IconWrapper>\n )}\n </>\n )}\n </Component>\n );\n }),\n);\n\nButton.displayName = \"Button\";\n\nexport default Button;\nexport { Button };\n"],"names":["buttonVariants","cva","cn","getTypographyClasses","getRadiusClass","getSpacingClass","getTypographySize","IconWrapper","children","position","jsx","Button","memo","forwardRef","_a","ref","_b","variant","size","isLoading","loadingText","loadingIcon","leftIcon","rightIcon","fullWidth","asChild","as","className","disabled","ariaLabel","props","__objRest","Component","Slot","classes","finalAriaLabel","displayLoadingIcon","Spinner","defaultType","buttonProps","__spreadValues","jsxs","__spreadProps","Slottable","Fragment"],"mappings":"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"}