@kanzo-tech/ui 0.1.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.
@@ -1,17 +1,58 @@
1
- import { jsx as e, jsxs as v, Fragment as b } from "react/jsx-runtime";
1
+ import { jsx as e, jsxs as b, Fragment as v } from "react/jsx-runtime";
2
2
  import { ark as p } from "@ark-ui/react/factory";
3
3
  import { tv as f } from "tailwind-variants";
4
- import { cn as u } from "../lib/cn.js";
5
- import { Spinner as g } from "./spinner.js";
4
+ import { cn as g } from "../lib/cn.js";
5
+ import { Spinner as u } from "./spinner.js";
6
6
  const m = f({
7
7
  base: [
8
8
  "relative",
9
- // The height is HERE, on the base, and every size variant only moves `--size`. Keyed off the
10
- // part's own name instead — `[data-slot="button"][data-size="sm"]` — it was keyed off the one
11
- // attribute this library lets a caller rename, and a renamed part silently lost its height:
12
- // `ConversationScrollButton` measured 16×16 against a 24×24 floor. daisyUI's `.btn` does it
13
- // this way for the same reason, and `no-measurement-on-a-renameable-slot.test.ts` holds it.
9
+ // Nothing below may be keyed on `data-slot`. See the docblock: `--size` was moved here first,
10
+ // in `05f3a0a`, and the paint followed it once the same rename was measured against the colour.
14
11
  "h-(--size)",
12
+ // The only one of the three locals a variant may leave unset — `outline` is the sole variant
13
+ // that assigns `--btn-bd`, while all six assign the fill and the ink. A default for those two
14
+ // would be a line that is overwritten every single render.
15
+ "[--btn-bd:transparent]",
16
+ // Derived, never assigned by a variant. `--depth: 0` collapses the edge and the lift to
17
+ // nothing, which is what the library looks like today; `1` gives it relief. Same classes.
18
+ //
19
+ // daisyUI darkens a fill with `color-mix(in oklab, var(--btn-bg), #000 5%)`. Mixing toward
20
+ // `--foreground` instead is a divergence taken on a reason: `black` is only "darker" in a light
21
+ // theme, and on a dark one the hover of a pale button should move toward white. One formula
22
+ // reads as "push this fill away from the page and toward the ink" in both, and it follows a
23
+ // tenant's document instead of a constant — which is also why `--depth` can be a single number.
24
+ // Their half-pixel inset gloss needs a colour that is toward the light side in both modes and
25
+ // neither `--foreground` nor `--background` is that, so it waits for a reason to exist.
26
+ "[--btn-edge:color-mix(in_oklab,var(--btn-bd),var(--foreground)_calc(var(--depth)*8%))]",
27
+ "[--btn-lift:color-mix(in_oklab,var(--btn-bg)_calc(var(--depth)*30%),transparent)]",
28
+ "bg-(--btn-bg) text-(--btn-fg)",
29
+ // The grain, and the whole of the conditional that switches it off: at `--noise: 0` the layer
30
+ // is sized to zero and never painted, at `1` it tiles. No variant — which is the property that
31
+ // lets it be a value a tenant sets rather than a look somebody has to write. It defaults to `0`
32
+ // here rather than in every theme, because a theme that wants no texture is saying nothing.
33
+ "bg-(image:--fx-noise) bg-size-[calc(var(--noise,0)*100%)]",
34
+ // `rounded-[var(--radius-field)]` and not `rounded-field`, which is the same radius and the
35
+ // spelling every other recipe here uses: `tailwind-merge` knows nothing about a theme key it
36
+ // was never configured with, so `rounded-field` and a caller's `rounded-full` both survive the
37
+ // merge and the winner is whichever Tailwind happened to emit last. The `pill` variant is that
38
+ // caller. An arbitrary value is in a group the merge does know.
39
+ "border-[length:var(--stroke)] border-(--btn-edge) rounded-[var(--radius-field)]",
40
+ // As `shadow-*` rather than a raw `box-shadow`, so it composes with `focus-visible:ring-[3px]`
41
+ // through Tailwind's shadow chain instead of being replaced by it. At `--depth: 0` the lift is
42
+ // transparent and nothing is drawn either way, which is why that was invisible before.
43
+ "shadow-[0_3px_2px_-2px_var(--btn-lift),0_4px_3px_-2px_var(--btn-lift)]",
44
+ // One hover and one active for every variant, solid and transparent alike — and the transparent
45
+ // case is the tell that the formula is right. Mixing `transparent` with the foreground at 10%
46
+ // IS the foreground at 10% alpha, which is exactly the wash a ghost button wants.
47
+ //
48
+ // The CSS rule guarded these with `:not(:disabled, [aria-disabled="true"], [data-disabled])`.
49
+ // All three spellings already carry `pointer-events-none` on this same base and an element with
50
+ // `pointer-events: none` can match neither `:hover` nor `:active`, so the guard was
51
+ // belt-and-braces. Dropping it is not tidiness: with the plain modifier, `tailwind-merge` can
52
+ // see a variant's own `hover:bg-*` as the same utility and let it win — which is how `link`
53
+ // opts out in one class, instead of losing to a longer selector on specificity.
54
+ "hover:bg-[color-mix(in_oklab,var(--btn-bg),var(--foreground)_10%)]",
55
+ "active:bg-[color-mix(in_oklab,var(--btn-bg),var(--foreground)_16%)]",
15
56
  "inline-flex shrink-0 items-center justify-center gap-2",
16
57
  "whitespace-nowrap font-medium text-sm",
17
58
  "border-solid",
@@ -56,8 +97,9 @@ const m = f({
56
97
  "focus-visible:border-primary"
57
98
  ],
58
99
  // The one variant that opts OUT of the shared hover, because a link's hover is an underline
59
- // and not a wash. A utility beats the recipe base by cascade layer, which is what makes
60
- // opting out one class rather than an exception in the CSS.
100
+ // and not a wash. Same modifier and same utility group as the base's wash, so
101
+ // `tailwind-variants` merges the two and the variant wins — which is what makes opting out
102
+ // one class rather than an exception somewhere else.
61
103
  link: [
62
104
  "[--btn-bg:transparent]",
63
105
  "[--btn-fg:var(--primary)]",
@@ -67,9 +109,9 @@ const m = f({
67
109
  ]
68
110
  },
69
111
  /**
70
- * Padding, gaps and icon sizes. **The heights are not here** — they are `--size-field` in
71
- * `styles.css`, keyed off the `data-size` this recipe already writes, so a tenant can ask for
72
- * compact controls without asking for tighter text.
112
+ * Padding, gaps and icon sizes. A size variant only **moves `--size`** — the height that reads
113
+ * it is on the `base` — so a tenant can ask for compact controls, through `--size-field`,
114
+ * without asking for tighter text.
73
115
  *
74
116
  * The floor is still measured rather than chosen. `xs` and `icon-xs` were both `1.5rem`, and
75
117
  * every size is a `rem` against a root the density axis sets — 16px default, 14px compact, 18px
@@ -115,43 +157,43 @@ const m = f({
115
157
  clickEffect: !0,
116
158
  pill: !1
117
159
  }
118
- }), w = (t) => {
160
+ }), k = (s) => {
119
161
  const {
120
- variant: s = "default",
121
- size: r = "md",
162
+ variant: r = "default",
163
+ size: t = "md",
122
164
  clickEffect: n = !0,
123
165
  pill: o = !1,
124
- isLoading: i = !1,
166
+ isLoading: a = !1,
125
167
  className: l,
126
- children: a,
168
+ children: i,
127
169
  slot: d,
128
170
  ...c
129
- } = t;
171
+ } = s;
130
172
  return /* @__PURE__ */ e(
131
173
  p.button,
132
174
  {
133
- className: u(
134
- m({ variant: s, size: r, clickEffect: n, pill: o }),
175
+ className: g(
176
+ m({ variant: r, size: t, clickEffect: n, pill: o }),
135
177
  l
136
178
  ),
137
- "data-size": r,
138
- "data-state": i ? "loading" : "idle",
139
- "data-variant": s,
179
+ "data-size": t,
180
+ "data-state": a ? "loading" : "idle",
181
+ "data-variant": r,
140
182
  type: "button",
141
183
  ...c,
142
184
  "data-slot": d ?? "button",
143
- "aria-busy": i,
144
- "aria-disabled": i,
145
- children: i ? /* @__PURE__ */ v(b, { children: [
146
- /* @__PURE__ */ e("span", { "aria-hidden": !0, className: "invisible", children: a }),
147
- /* @__PURE__ */ e("span", { className: "sr-only", children: a }),
148
- /* @__PURE__ */ e("span", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ e(g, { "aria-hidden": !0 }) })
149
- ] }) : a
185
+ "aria-busy": a,
186
+ "aria-disabled": a,
187
+ children: a ? /* @__PURE__ */ b(v, { children: [
188
+ /* @__PURE__ */ e("span", { "aria-hidden": !0, className: "invisible", children: i }),
189
+ /* @__PURE__ */ e("span", { className: "sr-only", children: i }),
190
+ /* @__PURE__ */ e("span", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ e(u, { "aria-hidden": !0 }) })
191
+ ] }) : i
150
192
  }
151
193
  );
152
194
  };
153
195
  export {
154
- w as Button,
196
+ k as Button,
155
197
  m as buttonVariants
156
198
  };
157
199
  //# sourceMappingURL=button.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"button.js","sources":["../../src/simples/button.tsx"],"sourcesContent":["import { ark } from \"@ark-ui/react/factory\";\nimport type React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\nimport { cn } from \"../lib/cn\";\nimport { Spinner } from \"./spinner\";\n\n/**\n * Everything a class list can say. The rest — the fill, the ink, the edge, the lift, the hover, the\n * active and the height — is one block of real CSS in `styles.css`, written once in terms of three\n * locals, and this file assigns those three.\n *\n * **That is the whole shape of the change, and it is daisyUI's.** Their `.btn-primary` is two\n * custom-property assignments over a `.btn` that derives every state; ours was six variants of five\n * to six utilities each, naming eight different tokens between them, with the hover of each written\n * separately. A hover rule that lives in six places is a hover rule that drifts in six places, and\n * three of the comments this file used to carry were measurements taken to settle one of those\n * drifts.\n *\n * What a variant may set: `--btn-bg`, `--btn-fg`, `--btn-bd`. Nothing else here paints.\n */\nexport const buttonVariants = tv({\n base: [\n \"relative\",\n // The height is HERE, on the base, and every size variant only moves `--size`. Keyed off the\n // part's own name instead — `[data-slot=\"button\"][data-size=\"sm\"]` — it was keyed off the one\n // attribute this library lets a caller rename, and a renamed part silently lost its height:\n // `ConversationScrollButton` measured 16×16 against a 24×24 floor. daisyUI's `.btn` does it\n // this way for the same reason, and `no-measurement-on-a-renameable-slot.test.ts` holds it.\n \"h-(--size)\",\n \"inline-flex shrink-0 items-center justify-center gap-2\",\n \"whitespace-nowrap font-medium text-sm\",\n \"border-solid\",\n \"transition-all\",\n \"outline-none focus-visible:ring-[3px] focus-visible:ring-ring\",\n \"disabled:pointer-events-none disabled:opacity-64\",\n \"data-disabled:pointer-events-none data-disabled:opacity-64\",\n \"aria-disabled:pointer-events-none aria-disabled:opacity-64\",\n \"data-[state=loading]:pointer-events-none\",\n \"aria-invalid:border-destructive aria-invalid:ring-destructive/24\",\n \"[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"motion-reduce:transition-none!\",\n ],\n variants: {\n variant: {\n default: [\n \"[--btn-bg:var(--primary)]\",\n \"[--btn-fg:var(--primary-foreground)]\",\n \"focus-visible:border-background\",\n ],\n outline: [\n \"[--btn-bg:transparent]\",\n \"[--btn-fg:var(--foreground)]\",\n \"[--btn-bd:var(--input)]\",\n \"focus-visible:border-primary\",\n ],\n destructive: [\n \"[--btn-bg:var(--destructive)]\",\n // Was `text-white` at 3.81 on red-500. The fill moved to `-600` and the ink became a token,\n // because no choice of ink rescued red-500: white 3.81, near-black 4.15, AA needs 4.5.\n \"[--btn-fg:var(--destructive-content)]\",\n \"focus-visible:border-background focus-visible:ring-destructive-foreground/32\",\n ],\n secondary: [\n \"[--btn-bg:var(--secondary)]\",\n \"[--btn-fg:var(--secondary-foreground)]\",\n \"focus-visible:border-primary\",\n ],\n ghost: [\n \"[--btn-bg:transparent]\",\n \"[--btn-fg:var(--foreground)]\",\n \"focus-visible:border-primary\",\n ],\n // The one variant that opts OUT of the shared hover, because a link's hover is an underline\n // and not a wash. A utility beats the recipe base by cascade layer, which is what makes\n // opting out one class rather than an exception in the CSS.\n link: [\n \"[--btn-bg:transparent]\",\n \"[--btn-fg:var(--primary)]\",\n \"underline-offset-4\",\n \"hover:bg-transparent hover:underline active:bg-transparent\",\n \"focus-visible:border-primary\",\n ],\n },\n /**\n * Padding, gaps and icon sizes. **The heights are not here** — they are `--size-field` in\n * `styles.css`, keyed off the `data-size` this recipe already writes, so a tenant can ask for\n * compact controls without asking for tighter text.\n *\n * The floor is still measured rather than chosen. `xs` and `icon-xs` were both `1.5rem`, and\n * every size is a `rem` against a root the density axis sets — 16px default, 14px compact, 18px\n * comfortable — so they measured **21px in compact**, under the 24×24 WCAG 2.5.8 states in CSS\n * pixels. `sm` is the smallest size that clears the bar in all three densities. No call site was\n * failing: the spacing exception saves a clustered control, and every measured `xs` was in a\n * cluster. The variant went because its NAME promised a size it could not deliver at one\n * density, and nothing was published to break.\n */\n size: {\n sm: [\"[--size:calc(var(--size-field)*7)]\", \"px-2.5\", \"gap-1.5\", \"[&_svg:not([class*='size-'])]:size-3.5\"],\n md: [\"[--size:calc(var(--size-field)*8)]\", \"px-3\", \"py-2\"],\n lg: [\"[--size:calc(var(--size-field)*9)]\", \"px-3.5\"],\n xl: [\"[--size:calc(var(--size-field)*10)]\", \"text-base\", \"px-4\"],\n // Square, and the width is stated rather than left to `aspect-ratio`: a caller who writes\n // `w-fit` on top of an aspect ratio gets the icon's own width and a control twice as tall.\n \"icon-sm\": [\"[--size:calc(var(--size-field)*7)]\", \"w-(--size)\", \"px-0\"],\n \"icon-md\": [\"[--size:calc(var(--size-field)*8)]\", \"w-(--size)\", \"px-0\"],\n \"icon-lg\": [\"[--size:calc(var(--size-field)*9)]\", \"w-(--size)\", \"px-0\"],\n \"icon-xl\": [\n \"[--size:calc(var(--size-field)*10)]\",\n \"w-(--size)\",\n \"px-0\",\n \"[&_svg:not([class*='size-'])]:size-5\",\n ],\n },\n clickEffect: {\n true: \"active:not-aria-[haspopup]:scale-[0.98]\",\n },\n pill: {\n true: [\n \"rounded-full\",\n \"has-[>svg]:data-[size=sm]:pe-3.5\",\n \"has-[>svg]:data-[size=md]:pe-4\",\n \"has-[>svg]:data-[size=lg]:pe-4.5\",\n \"has-[>svg]:data-[size=xl]:pe-5\",\n ],\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"md\",\n clickEffect: true,\n pill: false,\n },\n});\n\nexport interface ButtonProps\n extends React.ComponentProps<typeof ark.button>,\n VariantProps<typeof buttonVariants> {\n /**\n * Apply a click effect to the button\n *\n * @default true\n */\n clickEffect?: boolean;\n /**\n * Show a loading indicator\n *\n * @default false\n */\n isLoading?: boolean;\n}\n\nexport const Button = (props: ButtonProps) => {\n const {\n variant = \"default\",\n size = \"md\",\n clickEffect = true,\n pill = false,\n isLoading = false,\n className,\n children,\n slot,\n ...rest\n } = props;\n\n return (\n <ark.button\n className={cn(\n buttonVariants({ variant, size, clickEffect, pill }),\n className\n )}\n data-size={size}\n data-state={isLoading ? \"loading\" : \"idle\"}\n data-variant={variant}\n type=\"button\"\n {...rest}\n data-slot={slot ?? \"button\"}\n aria-busy={isLoading}\n aria-disabled={isLoading}\n >\n {isLoading ? (\n <>\n <span aria-hidden className=\"invisible\">\n {children}\n </span>\n\n <span className=\"sr-only\">{children}</span>\n\n <span className=\"absolute inset-0 flex items-center justify-center\">\n <Spinner aria-hidden />\n </span>\n </>\n ) : (\n children\n )}\n </ark.button>\n );\n};\n"],"names":["buttonVariants","tv","Button","props","variant","size","clickEffect","pill","isLoading","className","children","slot","rest","jsx","ark","cn","jsxs","Fragment","Spinner"],"mappings":"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"}
1
+ {"version":3,"file":"button.js","sources":["../../src/simples/button.tsx"],"sourcesContent":["import { ark } from \"@ark-ui/react/factory\";\nimport type React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\nimport { cn } from \"../lib/cn\";\nimport { Spinner } from \"./spinner\";\n\n/**\n * The whole control: the fill, the ink, the edge, the lift, both washes and the height, all on the\n * `base` and all written once in terms of three locals a variant assigns.\n *\n * **The shape is daisyUI's.** Their `.btn-primary` is two custom-property assignments over a `.btn`\n * that derives every state; ours was six variants of five to six utilities each, naming eight\n * different tokens between them, with the hover of each written separately. A hover rule that lives\n * in six places is a hover rule that drifts in six places.\n *\n * **The placement is daisyUI's too, and that half arrived late.** The derivations sat in\n * `styles.css` under `[data-slot=\"button\"]`, which is the one attribute this library invites a\n * caller to RENAME — every part takes `slot?: string`. So every component of ours that renames a\n * `Button` got the variant's three assignments and no rule that read them: `AlertDialogAction` and\n * `AlertDialogCancel` reported their `data-variant` correctly and `background-color:\n * rgba(0, 0, 0, 0)` in a live document, and twenty-nine more call sites sat on the same fault.\n * `05f3a0a` had already moved the *height* here for exactly this reason and left the colour behind,\n * on the argument that losing a colour on a rename is a caller asking for a different look — which\n * is true of a caller and was never true of us. `no-paint-on-a-renameable-slot.test.ts` holds it.\n *\n * What a variant may set: `--btn-bg`, `--btn-fg`, `--btn-bd`. Nothing else here paints.\n */\nexport const buttonVariants = tv({\n base: [\n \"relative\",\n // Nothing below may be keyed on `data-slot`. See the docblock: `--size` was moved here first,\n // in `05f3a0a`, and the paint followed it once the same rename was measured against the colour.\n \"h-(--size)\",\n // The only one of the three locals a variant may leave unset — `outline` is the sole variant\n // that assigns `--btn-bd`, while all six assign the fill and the ink. A default for those two\n // would be a line that is overwritten every single render.\n \"[--btn-bd:transparent]\",\n // Derived, never assigned by a variant. `--depth: 0` collapses the edge and the lift to\n // nothing, which is what the library looks like today; `1` gives it relief. Same classes.\n //\n // daisyUI darkens a fill with `color-mix(in oklab, var(--btn-bg), #000 5%)`. Mixing toward\n // `--foreground` instead is a divergence taken on a reason: `black` is only \"darker\" in a light\n // theme, and on a dark one the hover of a pale button should move toward white. One formula\n // reads as \"push this fill away from the page and toward the ink\" in both, and it follows a\n // tenant's document instead of a constant — which is also why `--depth` can be a single number.\n // Their half-pixel inset gloss needs a colour that is toward the light side in both modes and\n // neither `--foreground` nor `--background` is that, so it waits for a reason to exist.\n \"[--btn-edge:color-mix(in_oklab,var(--btn-bd),var(--foreground)_calc(var(--depth)*8%))]\",\n \"[--btn-lift:color-mix(in_oklab,var(--btn-bg)_calc(var(--depth)*30%),transparent)]\",\n \"bg-(--btn-bg) text-(--btn-fg)\",\n // The grain, and the whole of the conditional that switches it off: at `--noise: 0` the layer\n // is sized to zero and never painted, at `1` it tiles. No variant — which is the property that\n // lets it be a value a tenant sets rather than a look somebody has to write. It defaults to `0`\n // here rather than in every theme, because a theme that wants no texture is saying nothing.\n \"bg-(image:--fx-noise) bg-size-[calc(var(--noise,0)*100%)]\",\n // `rounded-[var(--radius-field)]` and not `rounded-field`, which is the same radius and the\n // spelling every other recipe here uses: `tailwind-merge` knows nothing about a theme key it\n // was never configured with, so `rounded-field` and a caller's `rounded-full` both survive the\n // merge and the winner is whichever Tailwind happened to emit last. The `pill` variant is that\n // caller. An arbitrary value is in a group the merge does know.\n \"border-[length:var(--stroke)] border-(--btn-edge) rounded-[var(--radius-field)]\",\n // As `shadow-*` rather than a raw `box-shadow`, so it composes with `focus-visible:ring-[3px]`\n // through Tailwind's shadow chain instead of being replaced by it. At `--depth: 0` the lift is\n // transparent and nothing is drawn either way, which is why that was invisible before.\n \"shadow-[0_3px_2px_-2px_var(--btn-lift),0_4px_3px_-2px_var(--btn-lift)]\",\n // One hover and one active for every variant, solid and transparent alike — and the transparent\n // case is the tell that the formula is right. Mixing `transparent` with the foreground at 10%\n // IS the foreground at 10% alpha, which is exactly the wash a ghost button wants.\n //\n // The CSS rule guarded these with `:not(:disabled, [aria-disabled=\"true\"], [data-disabled])`.\n // All three spellings already carry `pointer-events-none` on this same base and an element with\n // `pointer-events: none` can match neither `:hover` nor `:active`, so the guard was\n // belt-and-braces. Dropping it is not tidiness: with the plain modifier, `tailwind-merge` can\n // see a variant's own `hover:bg-*` as the same utility and let it win — which is how `link`\n // opts out in one class, instead of losing to a longer selector on specificity.\n \"hover:bg-[color-mix(in_oklab,var(--btn-bg),var(--foreground)_10%)]\",\n \"active:bg-[color-mix(in_oklab,var(--btn-bg),var(--foreground)_16%)]\",\n \"inline-flex shrink-0 items-center justify-center gap-2\",\n \"whitespace-nowrap font-medium text-sm\",\n \"border-solid\",\n \"transition-all\",\n \"outline-none focus-visible:ring-[3px] focus-visible:ring-ring\",\n \"disabled:pointer-events-none disabled:opacity-64\",\n \"data-disabled:pointer-events-none data-disabled:opacity-64\",\n \"aria-disabled:pointer-events-none aria-disabled:opacity-64\",\n \"data-[state=loading]:pointer-events-none\",\n \"aria-invalid:border-destructive aria-invalid:ring-destructive/24\",\n \"[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"motion-reduce:transition-none!\",\n ],\n variants: {\n variant: {\n default: [\n \"[--btn-bg:var(--primary)]\",\n \"[--btn-fg:var(--primary-foreground)]\",\n \"focus-visible:border-background\",\n ],\n outline: [\n \"[--btn-bg:transparent]\",\n \"[--btn-fg:var(--foreground)]\",\n \"[--btn-bd:var(--input)]\",\n \"focus-visible:border-primary\",\n ],\n destructive: [\n \"[--btn-bg:var(--destructive)]\",\n // Was `text-white` at 3.81 on red-500. The fill moved to `-600` and the ink became a token,\n // because no choice of ink rescued red-500: white 3.81, near-black 4.15, AA needs 4.5.\n \"[--btn-fg:var(--destructive-content)]\",\n \"focus-visible:border-background focus-visible:ring-destructive-foreground/32\",\n ],\n secondary: [\n \"[--btn-bg:var(--secondary)]\",\n \"[--btn-fg:var(--secondary-foreground)]\",\n \"focus-visible:border-primary\",\n ],\n ghost: [\n \"[--btn-bg:transparent]\",\n \"[--btn-fg:var(--foreground)]\",\n \"focus-visible:border-primary\",\n ],\n // The one variant that opts OUT of the shared hover, because a link's hover is an underline\n // and not a wash. Same modifier and same utility group as the base's wash, so\n // `tailwind-variants` merges the two and the variant wins — which is what makes opting out\n // one class rather than an exception somewhere else.\n link: [\n \"[--btn-bg:transparent]\",\n \"[--btn-fg:var(--primary)]\",\n \"underline-offset-4\",\n \"hover:bg-transparent hover:underline active:bg-transparent\",\n \"focus-visible:border-primary\",\n ],\n },\n /**\n * Padding, gaps and icon sizes. A size variant only **moves `--size`** — the height that reads\n * it is on the `base` — so a tenant can ask for compact controls, through `--size-field`,\n * without asking for tighter text.\n *\n * The floor is still measured rather than chosen. `xs` and `icon-xs` were both `1.5rem`, and\n * every size is a `rem` against a root the density axis sets — 16px default, 14px compact, 18px\n * comfortable — so they measured **21px in compact**, under the 24×24 WCAG 2.5.8 states in CSS\n * pixels. `sm` is the smallest size that clears the bar in all three densities. No call site was\n * failing: the spacing exception saves a clustered control, and every measured `xs` was in a\n * cluster. The variant went because its NAME promised a size it could not deliver at one\n * density, and nothing was published to break.\n */\n size: {\n sm: [\"[--size:calc(var(--size-field)*7)]\", \"px-2.5\", \"gap-1.5\", \"[&_svg:not([class*='size-'])]:size-3.5\"],\n md: [\"[--size:calc(var(--size-field)*8)]\", \"px-3\", \"py-2\"],\n lg: [\"[--size:calc(var(--size-field)*9)]\", \"px-3.5\"],\n xl: [\"[--size:calc(var(--size-field)*10)]\", \"text-base\", \"px-4\"],\n // Square, and the width is stated rather than left to `aspect-ratio`: a caller who writes\n // `w-fit` on top of an aspect ratio gets the icon's own width and a control twice as tall.\n \"icon-sm\": [\"[--size:calc(var(--size-field)*7)]\", \"w-(--size)\", \"px-0\"],\n \"icon-md\": [\"[--size:calc(var(--size-field)*8)]\", \"w-(--size)\", \"px-0\"],\n \"icon-lg\": [\"[--size:calc(var(--size-field)*9)]\", \"w-(--size)\", \"px-0\"],\n \"icon-xl\": [\n \"[--size:calc(var(--size-field)*10)]\",\n \"w-(--size)\",\n \"px-0\",\n \"[&_svg:not([class*='size-'])]:size-5\",\n ],\n },\n clickEffect: {\n true: \"active:not-aria-[haspopup]:scale-[0.98]\",\n },\n pill: {\n true: [\n \"rounded-full\",\n \"has-[>svg]:data-[size=sm]:pe-3.5\",\n \"has-[>svg]:data-[size=md]:pe-4\",\n \"has-[>svg]:data-[size=lg]:pe-4.5\",\n \"has-[>svg]:data-[size=xl]:pe-5\",\n ],\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"md\",\n clickEffect: true,\n pill: false,\n },\n});\n\nexport interface ButtonProps\n extends React.ComponentProps<typeof ark.button>,\n VariantProps<typeof buttonVariants> {\n /**\n * Apply a click effect to the button\n *\n * @default true\n */\n clickEffect?: boolean;\n /**\n * Show a loading indicator\n *\n * @default false\n */\n isLoading?: boolean;\n}\n\nexport const Button = (props: ButtonProps) => {\n const {\n variant = \"default\",\n size = \"md\",\n clickEffect = true,\n pill = false,\n isLoading = false,\n className,\n children,\n slot,\n ...rest\n } = props;\n\n return (\n <ark.button\n className={cn(\n buttonVariants({ variant, size, clickEffect, pill }),\n className\n )}\n data-size={size}\n data-state={isLoading ? \"loading\" : \"idle\"}\n data-variant={variant}\n type=\"button\"\n {...rest}\n data-slot={slot ?? \"button\"}\n aria-busy={isLoading}\n aria-disabled={isLoading}\n >\n {isLoading ? (\n <>\n <span aria-hidden className=\"invisible\">\n {children}\n </span>\n\n <span className=\"sr-only\">{children}</span>\n\n <span className=\"absolute inset-0 flex items-center justify-center\">\n <Spinner aria-hidden />\n </span>\n </>\n ) : (\n children\n )}\n </ark.button>\n );\n};\n"],"names":["buttonVariants","tv","Button","props","variant","size","clickEffect","pill","isLoading","className","children","slot","rest","jsx","ark","cn","jsxs","Fragment","Spinner"],"mappings":"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"}