@krizaka/ui 1.3.0 → 2.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +137 -4
  2. package/dist/alert.d.ts +95 -0
  3. package/dist/alert.js +38 -0
  4. package/dist/alert.js.map +1 -0
  5. package/dist/avatar.d.ts +98 -0
  6. package/dist/avatar.js +48 -0
  7. package/dist/avatar.js.map +1 -0
  8. package/dist/badge.d.ts +64 -0
  9. package/dist/badge.js +31 -0
  10. package/dist/badge.js.map +1 -0
  11. package/dist/button.d.ts +76 -0
  12. package/dist/button.js +46 -0
  13. package/dist/button.js.map +1 -0
  14. package/dist/card.d.ts +227 -0
  15. package/dist/card.js +108 -0
  16. package/dist/card.js.map +1 -0
  17. package/dist/chunk-CPMZS4XA.js +11 -0
  18. package/dist/chunk-CPMZS4XA.js.map +1 -0
  19. package/dist/chunk-J3NC6ER4.js +9 -0
  20. package/dist/chunk-J3NC6ER4.js.map +1 -0
  21. package/dist/chunk-QRG5RYEI.js +49 -0
  22. package/dist/chunk-QRG5RYEI.js.map +1 -0
  23. package/dist/chunk-RLOXWF2G.js +13 -0
  24. package/dist/chunk-RLOXWF2G.js.map +1 -0
  25. package/dist/cn.d.ts +10 -0
  26. package/dist/cn.js +7 -0
  27. package/dist/cn.js.map +1 -0
  28. package/dist/countdown.d.ts +46 -0
  29. package/dist/countdown.js +74 -0
  30. package/dist/countdown.js.map +1 -0
  31. package/dist/dialog.d.ts +155 -0
  32. package/dist/dialog.js +157 -0
  33. package/dist/dialog.js.map +1 -0
  34. package/dist/dropdown-menu.d.ts +71 -0
  35. package/dist/dropdown-menu.js +66 -0
  36. package/dist/dropdown-menu.js.map +1 -0
  37. package/dist/empty-state.d.ts +63 -0
  38. package/dist/empty-state.js +26 -0
  39. package/dist/empty-state.js.map +1 -0
  40. package/dist/field.d.ts +80 -0
  41. package/dist/field.js +47 -0
  42. package/dist/field.js.map +1 -0
  43. package/dist/index.d.ts +6 -6
  44. package/dist/kbd.d.ts +24 -0
  45. package/dist/kbd.js +16 -0
  46. package/dist/kbd.js.map +1 -0
  47. package/dist/page-header.d.ts +77 -0
  48. package/dist/page-header.js +32 -0
  49. package/dist/page-header.js.map +1 -0
  50. package/dist/popover.d.ts +18 -0
  51. package/dist/popover.js +34 -0
  52. package/dist/popover.js.map +1 -0
  53. package/dist/separator.d.ts +24 -0
  54. package/dist/separator.js +17 -0
  55. package/dist/separator.js.map +1 -0
  56. package/dist/skeleton.d.ts +28 -0
  57. package/dist/skeleton.js +18 -0
  58. package/dist/skeleton.js.map +1 -0
  59. package/dist/slot.d.ts +11 -0
  60. package/dist/slot.js +9 -0
  61. package/dist/slot.js.map +1 -0
  62. package/dist/spinner.d.ts +31 -0
  63. package/dist/spinner.js +31 -0
  64. package/dist/spinner.js.map +1 -0
  65. package/dist/stat.d.ts +75 -0
  66. package/dist/stat.js +42 -0
  67. package/dist/stat.js.map +1 -0
  68. package/dist/theme.d.ts +95 -0
  69. package/dist/theme.js +119 -0
  70. package/dist/theme.js.map +1 -0
  71. package/dist/toast.d.ts +32 -0
  72. package/dist/toast.js +49 -0
  73. package/dist/toast.js.map +1 -0
  74. package/dist/tooltip.d.ts +21 -0
  75. package/dist/tooltip.js +44 -0
  76. package/dist/tooltip.js.map +1 -0
  77. package/package.json +333 -7
package/README.md CHANGED
@@ -4,9 +4,10 @@
4
4
 
5
5
  # @krizaka/ui
6
6
 
7
- **The Krizaka brand layer, shared by every product.**
8
- The animated marks of Krizaka, Orazaka and Orochia, and the Krizaka motion signature — one easing, one way to enter,
9
- reveal, roll, shine and open.
7
+ **The Krizaka front-end platform's components, shared by every product.**
8
+ The primitives (button, badge, avatar, field, theme, countdown…) on the semantic `--kz-*` tokens, the animated marks
9
+ of Krizaka, Orazaka and Orochia, and the Krizaka motion signature — one easing, one way to enter, reveal, roll, shine
10
+ and open.
10
11
 
11
12
  [![npm](https://img.shields.io/npm/v/@krizaka/ui?color=3b82f6&label=npm)](https://www.npmjs.com/package/@krizaka/ui)
12
13
  [![CI](https://github.com/krizaka/krizaka-ui/actions/workflows/ci.yml/badge.svg)](https://github.com/krizaka/krizaka-ui/actions/workflows/ci.yml)
@@ -20,9 +21,141 @@ reveal, roll, shine and open.
20
21
  npm install @krizaka/ui
21
22
  ```
22
23
 
23
- React 18 or 19. No framework, no CSS library required: the marks carry their own styles, the motion is one CSS file.
24
+ The marks and the motion: React 18 or 19, no framework, no CSS library — the marks carry their own styles, the motion
25
+ is one CSS file. The primitives: **React 19** and Tailwind CSS v4 with the Krizaka preset (below).
24
26
 
25
27
 
28
+ ## Primitives
29
+
30
+ > 2.0 beta: the atoms, then the structure of a page and what opens over it (cards, dialogs, sheets, toasts, popovers,
31
+ > menus, tooltips). Tabs, chips, switches and the command palette follow in the next betas.
32
+
33
+ ### Install
34
+
35
+ ```bash
36
+ npm install @krizaka/ui@beta @krizaka/tailwind@beta @krizaka/tokens@beta tailwindcss
37
+ ```
38
+
39
+ ```css
40
+ /* app/globals.css — the preset (tokens as utilities), then the classes the primitives use */
41
+ @import "tailwindcss";
42
+ @import "@krizaka/tailwind";
43
+ @import "@krizaka/ui/tailwind.css";
44
+ ```
45
+
46
+ ```tsx
47
+ import { Button, IconButton } from "@krizaka/ui/button";
48
+ import { Badge } from "@krizaka/ui/badge";
49
+ import { Field, Input } from "@krizaka/ui/field";
50
+ import { ThemeProvider, ThemeScript, ThemeToggle } from "@krizaka/ui/theme";
51
+ import { Card } from "@krizaka/ui/card";
52
+ import { AlertDialog, Dialog } from "@krizaka/ui/dialog";
53
+ import { toast, Toaster } from "@krizaka/ui/toast";
54
+
55
+ <Button variant="primary" loading={saving}>{t("save")}</Button>
56
+ <Button asChild variant="outline"><Link href="/docs">{t("docs")}</Link></Button>
57
+ <Badge tone="danger" dot pulse>{t("endsSoon")}</Badge>
58
+ <Field.Root>
59
+ <Field.Label htmlFor="email">{t("email")}</Field.Label>
60
+ <Input id="email" invalid={!!error} aria-describedby="email-error" />
61
+ {error && <Field.Error id="email-error">{error}</Field.Error>}
62
+ </Field.Root>
63
+
64
+ <Card.Root asChild interactive reveal={index}>
65
+ <Link href={href}>
66
+ <Card.Media><Card.Image src={cover} fallback={<PlayIcon />} /></Card.Media>
67
+ <Card.Body><Card.Title>{title}</Card.Title></Card.Body>
68
+ </Link>
69
+ </Card.Root>
70
+
71
+ <Dialog.Root>
72
+ <Dialog.Trigger asChild><Button>{t("edit")}</Button></Dialog.Trigger>
73
+ <Dialog.Content closeLabel={t("close")} placement="bottom">
74
+ <Dialog.Header><Dialog.Title>{t("editTitle")}</Dialog.Title></Dialog.Header>
75
+ <Dialog.Body>…</Dialog.Body>
76
+ </Dialog.Content>
77
+ </Dialog.Root>
78
+
79
+ <AlertDialog tone="danger" trigger={<Button variant="danger">{t("delete")}</Button>} title={t("deleteTitle")}
80
+ confirmLabel={t("delete")} cancelLabel={t("cancel")} onConfirm={() => deleteVideo(id)} />
81
+
82
+ // once, in the root layout — then toast.success(t("saved")) anywhere on the client
83
+ <Toaster label={t("notifications")} closeLabel={t("dismiss")} />
84
+ ```
85
+
86
+ **React 19 is required by the primitives** (`ref` is a prop, no `forwardRef`). The package keeps `react >= 18` as its
87
+ peer range, because the marks and the motion still run on React 18: one `package.json` cannot hold both ranges.
88
+ `tailwindcss` and `@krizaka/tailwind` are optional peers — a native app or a marks-only site never installs them.
89
+
90
+ ### The component contract (12 points)
91
+
92
+ 1. **Composed when there is structure** (`Card.*`, `Dialog.*`, `Field.*`, `Avatar.Group`), **flat for an atom**
93
+ (`Button`, `Badge`, `Input`, `Stat`).
94
+ 2. **Variants exported next to the component** (`buttonVariants`, `badgeVariants`): a `<Link>` or a Server Component
95
+ is styled without importing the component.
96
+ 3. **`asChild`** (Radix `Slot`) rather than `as`: props, `className` and `ref` are merged into the child.
97
+ 4. **`className` is always merged last** (tailwind-merge, through `cn`): the product's override wins.
98
+ 5. **Words are props** (`label`, `closeLabel`, `units`, `title`) — there is no `t()` in the kit. Each app re-exports the
99
+ primitives with its words from its own `components/ui/`.
100
+ 6. **Controlled and uncontrolled**: `value`/`defaultValue` + `onValueChange`, `open`/`defaultOpen` + `onOpenChange`.
101
+ 7. **State is exposed as `data-*`** (`data-tone`, `data-loading`, `data-invalid`, `data-urgent`, `data-mode`): a
102
+ product styles a state without a new variant.
103
+ 8. **Server by default**: `"use client"` only on the entries with a hook or a portal (`avatar`, `theme`, `countdown`,
104
+ `dialog`, `toast`, `popover`, `dropdown-menu`, `tooltip`).
105
+ One build entry per component; the directive is kept per entry.
106
+ 9. **React 19 minimum for the primitives**; the marks stay `react >= 18`.
107
+ 10. **Accessibility is delegated**: non-trivial interactions on Radix UI (`radix-ui`), the command palette on cmdk,
108
+ toasts on sonner — no hand-written focus trap.
109
+ 11. **Every primitive ships** types, a story (every variant, dark and light, screenshot-tested), an RTL + axe test and
110
+ an entry here.
111
+ 12. **Motion**: `prefers-reduced-motion` is honoured (`motion-safe:`); no component animates a layout property.
112
+
113
+ Colours are roles of the preset (`bg-surface-2`, `text-fg-secondary`, `border-border-default`, `text-accent`,
114
+ `text-danger`), never a palette step nor `light:`: a theme — dark, light or a product's — is a set of tokens.
115
+
116
+ ### Entries
117
+
118
+ | Entry | Exports | Runs |
119
+ | :-- | :-- | :-- |
120
+ | `@krizaka/ui/cn` | `cn(...classes)` — clsx + tailwind-merge (the engine `tailwind-variants` ships) | server |
121
+ | `@krizaka/ui/slot` | `Slot`, `VisuallyHidden` (Radix) | server |
122
+ | `@krizaka/ui/button` | `Button` (`variant` primary·secondary·outline·ghost·danger, `size` sm·md·lg·icon, `shape` rounded·pill, `loading`, `asChild`), `IconButton` (`label` required), `buttonVariants` | server |
123
+ | `@krizaka/ui/badge` | `Badge` (`tone` neutral·accent·success·warning·danger·scrim, `size` sm·md, `dot`, `pulse`), `badgeVariants` | server |
124
+ | `@krizaka/ui/avatar` | `Avatar` (`src`, `alt`, `fallback`, `size` xs·sm·md·lg·xl), `Avatar.Group` (`max`) | client |
125
+ | `@krizaka/ui/field` | `Field.Root/Label/Hint/Error`, `Input`, `Textarea`, `Select` (native, styled) — `invalid` | server |
126
+ | `@krizaka/ui/theme` | `ThemeScript` (in `<head>`, no flash), `ThemeProvider`, `useTheme()` → `{ mode, setMode, theme, setTheme }`, `ThemeToggle` (`label`) | client |
127
+ | `@krizaka/ui/skeleton` | `Skeleton` (`shape` text·circle·rect) | server |
128
+ | `@krizaka/ui/empty-state` | `EmptyState` (`icon`, `title`, `description`, `action`) | server |
129
+ | `@krizaka/ui/spinner` | `Spinner` (`label`, `size`) | server |
130
+ | `@krizaka/ui/countdown` | `Countdown` (`target`, `label`, `units`, `size`, `urgentBelowMs`, `skewMs`), `useCountdown`, `splitDuration` | client |
131
+ | `@krizaka/ui/card` | `Card.Root` (`asChild`, `reveal`, `radius` md·lg·xl, `tone` default·elevated·glass, `interactive`), `Card.Media` (`aspect` video·square·portrait·auto), `Card.Image` (`fallback`), `Card.Overlay` (`corner`), `Card.Body` (`padding`), `Card.Title` (`as`), `Card.Description`, `Card.Stat` (`label`), `Card.Footer`; `card` (the slots) | server |
132
+ | `@krizaka/ui/dialog` | `Dialog.Root/Trigger/Content/Header/Title/Description/Body/Footer/Close` (`placement` center·bottom·right, `size` sm·md·lg, `closeLabel` required), `Sheet` (= `placement="bottom"`), `AlertDialog` (`title`, `description`, `confirmLabel`, `cancelLabel`, `tone` danger·primary, async `onConfirm`, `trigger`) | client |
133
+ | `@krizaka/ui/toast` | `Toaster` (sonner styled by roles: `label`, `closeLabel`, `position`, `closeButton`; `richColors` off), `toast` (`.success/.warning/.error/.info/.custom`…) | client |
134
+ | `@krizaka/ui/popover` | `Popover.Root/Trigger/Anchor/Content/Close` | client |
135
+ | `@krizaka/ui/dropdown-menu` | `DropdownMenu.Root/Trigger/Content/Item/CheckboxItem/Label/Separator/Group` (`Item` `tone` default·danger), `menu` (the slots) | client |
136
+ | `@krizaka/ui/tooltip` | `Tooltip` (`content`, `delayDuration` 300, `side`, `open`/`defaultOpen`), `TooltipProvider` | client |
137
+ | `@krizaka/ui/stat` | `Stat` (`label`, `value`, `hint`, `trend` up·down·flat, `trendLabel`) | server |
138
+ | `@krizaka/ui/page-header` | `PageHeader` (`title`, `description`, `actions`, `breadcrumb`, `as` h1·h2) | server |
139
+ | `@krizaka/ui/alert` | `Alert` (`tone` info·success·warning·danger, `icon`, `title`, `action`; `role="alert"` for danger and warning, `status` otherwise), `alertVariants` | server |
140
+ | `@krizaka/ui/separator` | `Separator` (`orientation`, `decorative`, Radix), `separatorVariants` | server |
141
+ | `@krizaka/ui/kbd` | `Kbd` (`size` sm·md), `kbdVariants` | server |
142
+ | `@krizaka/ui` | the marks, `MotionObserver`, `RotatingWord`, `cx` (unchanged) | client |
143
+ | `@krizaka/ui/native` | the marks for React Native (unchanged) | native |
144
+ | `@krizaka/ui/motion.css`, `@krizaka/ui/tailwind.css` | the motion signature; the `@source` of the primitives' classes | CSS |
145
+
146
+ The theme is one mechanism for every product: dark by default, `html.light` for light, `html.theme-<name>` for a named
147
+ theme, persisted as `kz-theme` (and `kz-theme-name`).
148
+
149
+ ```tsx
150
+ // app/layout.tsx
151
+ <html lang={locale} suppressHydrationWarning>
152
+ <head><ThemeScript /></head>
153
+ <body><ThemeProvider>{children}</ThemeProvider></body>
154
+ </html>
155
+
156
+ <ThemeToggle label={(mode) => t(`theme.${mode}`)} />
157
+ ```
158
+
26
159
  ## React Native
27
160
 
28
161
  The marks have one geometry; `@krizaka/ui/native` draws it with `react-native-svg` for the apps
@@ -0,0 +1,95 @@
1
+ import * as tailwind_variants from 'tailwind-variants';
2
+ import { VariantProps } from 'tailwind-variants';
3
+ import * as React from 'react';
4
+
5
+ declare const alertVariants: tailwind_variants.TVReturnType<{
6
+ tone: {
7
+ info: {
8
+ root: string;
9
+ icon: string;
10
+ };
11
+ success: {
12
+ root: string;
13
+ icon: string;
14
+ };
15
+ warning: {
16
+ root: string;
17
+ icon: string;
18
+ };
19
+ danger: {
20
+ root: string;
21
+ icon: string;
22
+ };
23
+ };
24
+ }, {
25
+ root: string;
26
+ icon: string;
27
+ content: string;
28
+ title: string;
29
+ description: string;
30
+ action: string;
31
+ }, undefined, {
32
+ tone: {
33
+ info: {
34
+ root: string;
35
+ icon: string;
36
+ };
37
+ success: {
38
+ root: string;
39
+ icon: string;
40
+ };
41
+ warning: {
42
+ root: string;
43
+ icon: string;
44
+ };
45
+ danger: {
46
+ root: string;
47
+ icon: string;
48
+ };
49
+ };
50
+ }, {
51
+ root: string;
52
+ icon: string;
53
+ content: string;
54
+ title: string;
55
+ description: string;
56
+ action: string;
57
+ }, tailwind_variants.TVReturnTypeLike<{
58
+ tone: {
59
+ info: {
60
+ root: string;
61
+ icon: string;
62
+ };
63
+ success: {
64
+ root: string;
65
+ icon: string;
66
+ };
67
+ warning: {
68
+ root: string;
69
+ icon: string;
70
+ };
71
+ danger: {
72
+ root: string;
73
+ icon: string;
74
+ };
75
+ };
76
+ }, {
77
+ root: string;
78
+ icon: string;
79
+ content: string;
80
+ title: string;
81
+ description: string;
82
+ action: string;
83
+ }>>;
84
+ type AlertTone = NonNullable<VariantProps<typeof alertVariants>["tone"]>;
85
+ type AlertProps = Omit<React.ComponentProps<"div">, "title"> & {
86
+ tone?: AlertTone;
87
+ /** A decorative icon (hidden from assistive technology). */
88
+ icon?: React.ReactNode;
89
+ title?: React.ReactNode;
90
+ /** What to do: a Button, a link. */
91
+ action?: React.ReactNode;
92
+ };
93
+ declare function Alert({ tone, icon, title, action, className, children, ...props }: AlertProps): React.JSX.Element;
94
+
95
+ export { Alert, type AlertProps, type AlertTone, alertVariants };
package/dist/alert.js ADDED
@@ -0,0 +1,38 @@
1
+ // src/alert/alert.tsx
2
+ import { tv } from "tailwind-variants";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ var alertVariants = tv({
5
+ slots: {
6
+ root: "relative flex w-full items-start gap-3 rounded-xl border p-4 text-sm text-fg",
7
+ icon: "mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center [&>svg]:h-5 [&>svg]:w-5",
8
+ content: "flex min-w-0 flex-1 flex-col gap-1",
9
+ title: "font-semibold text-fg",
10
+ description: "text-fg-secondary",
11
+ action: "flex shrink-0 items-center gap-2 self-center"
12
+ },
13
+ variants: {
14
+ tone: {
15
+ info: { root: "border-info/40 bg-info/10", icon: "text-info" },
16
+ success: { root: "border-success/40 bg-success/10", icon: "text-success" },
17
+ warning: { root: "border-warning/50 bg-warning/10", icon: "text-warning" },
18
+ danger: { root: "border-danger/50 bg-danger/10", icon: "text-danger" }
19
+ }
20
+ },
21
+ defaultVariants: { tone: "info" }
22
+ });
23
+ function Alert({ tone = "info", icon, title, action, className, children, ...props }) {
24
+ const s = alertVariants({ tone });
25
+ return /* @__PURE__ */ jsxs("div", { role: tone === "danger" || tone === "warning" ? "alert" : "status", "data-tone": tone, className: s.root({ className }), ...props, children: [
26
+ icon && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: s.icon(), children: icon }),
27
+ /* @__PURE__ */ jsxs("div", { className: s.content(), children: [
28
+ title && /* @__PURE__ */ jsx("p", { className: s.title(), children: title }),
29
+ children && /* @__PURE__ */ jsx("div", { className: s.description(), children })
30
+ ] }),
31
+ action && /* @__PURE__ */ jsx("div", { className: s.action(), children: action })
32
+ ] });
33
+ }
34
+ export {
35
+ Alert,
36
+ alertVariants
37
+ };
38
+ //# sourceMappingURL=alert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/alert/alert.tsx"],"sourcesContent":["// Server-safe. A message in the flow of the page (not a toast): a tone, an icon, a title, a text, an action.\n// danger and warning are announced at once (role=\"alert\"); info and success politely (role=\"status\").\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\n// Soft tones (a tint, a tinted border, a coloured icon): the text stays a text role — the invariant status tokens are\n// too light for small text on a light surface (WCAG AA).\nexport const alertVariants = tv({\n slots: {\n root: \"relative flex w-full items-start gap-3 rounded-xl border p-4 text-sm text-fg\",\n icon: \"mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center [&>svg]:h-5 [&>svg]:w-5\",\n content: \"flex min-w-0 flex-1 flex-col gap-1\",\n title: \"font-semibold text-fg\",\n description: \"text-fg-secondary\",\n action: \"flex shrink-0 items-center gap-2 self-center\",\n },\n variants: {\n tone: {\n info: { root: \"border-info/40 bg-info/10\", icon: \"text-info\" },\n success: { root: \"border-success/40 bg-success/10\", icon: \"text-success\" },\n warning: { root: \"border-warning/50 bg-warning/10\", icon: \"text-warning\" },\n danger: { root: \"border-danger/50 bg-danger/10\", icon: \"text-danger\" },\n },\n },\n defaultVariants: { tone: \"info\" },\n});\n\nexport type AlertTone = NonNullable<VariantProps<typeof alertVariants>[\"tone\"]>;\n\nexport type AlertProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n tone?: AlertTone;\n /** A decorative icon (hidden from assistive technology). */\n icon?: React.ReactNode;\n title?: React.ReactNode;\n /** What to do: a Button, a link. */\n action?: React.ReactNode;\n};\n\nexport function Alert({ tone = \"info\", icon, title, action, className, children, ...props }: AlertProps) {\n const s = alertVariants({ tone });\n return (\n <div role={tone === \"danger\" || tone === \"warning\" ? \"alert\" : \"status\"} data-tone={tone} className={s.root({ className })} {...props}>\n {icon && (\n <span aria-hidden className={s.icon()}>\n {icon}\n </span>\n )}\n <div className={s.content()}>\n {title && <p className={s.title()}>{title}</p>}\n {children && <div className={s.description()}>{children}</div>}\n </div>\n {action && <div className={s.action()}>{action}</div>}\n </div>\n );\n}\n"],"mappings":";AAGA,SAAS,UAA6B;AAwC9B,cAIF,YAJE;AApCD,IAAM,gBAAgB,GAAG;AAAA,EAC9B,OAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,IACP,aAAa;AAAA,IACb,QAAQ;AAAA,EACV;AAAA,EACA,UAAU;AAAA,IACR,MAAM;AAAA,MACJ,MAAM,EAAE,MAAM,6BAA6B,MAAM,YAAY;AAAA,MAC7D,SAAS,EAAE,MAAM,mCAAmC,MAAM,eAAe;AAAA,MACzE,SAAS,EAAE,MAAM,mCAAmC,MAAM,eAAe;AAAA,MACzE,QAAQ,EAAE,MAAM,iCAAiC,MAAM,cAAc;AAAA,IACvE;AAAA,EACF;AAAA,EACA,iBAAiB,EAAE,MAAM,OAAO;AAClC,CAAC;AAaM,SAAS,MAAM,EAAE,OAAO,QAAQ,MAAM,OAAO,QAAQ,WAAW,UAAU,GAAG,MAAM,GAAe;AACvG,QAAM,IAAI,cAAc,EAAE,KAAK,CAAC;AAChC,SACE,qBAAC,SAAI,MAAM,SAAS,YAAY,SAAS,YAAY,UAAU,UAAU,aAAW,MAAM,WAAW,EAAE,KAAK,EAAE,UAAU,CAAC,GAAI,GAAG,OAC7H;AAAA,YACC,oBAAC,UAAK,eAAW,MAAC,WAAW,EAAE,KAAK,GACjC,gBACH;AAAA,IAEF,qBAAC,SAAI,WAAW,EAAE,QAAQ,GACvB;AAAA,eAAS,oBAAC,OAAE,WAAW,EAAE,MAAM,GAAI,iBAAM;AAAA,MACzC,YAAY,oBAAC,SAAI,WAAW,EAAE,YAAY,GAAI,UAAS;AAAA,OAC1D;AAAA,IACC,UAAU,oBAAC,SAAI,WAAW,EAAE,OAAO,GAAI,kBAAO;AAAA,KACjD;AAEJ;","names":[]}
@@ -0,0 +1,98 @@
1
+ import * as tailwind_variants from 'tailwind-variants';
2
+ import { VariantProps } from 'tailwind-variants';
3
+ import * as React from 'react';
4
+
5
+ declare const avatar: tailwind_variants.TVReturnType<{
6
+ size: {
7
+ xs: {
8
+ root: string;
9
+ };
10
+ sm: {
11
+ root: string;
12
+ };
13
+ md: {
14
+ root: string;
15
+ };
16
+ lg: {
17
+ root: string;
18
+ };
19
+ xl: {
20
+ root: string;
21
+ };
22
+ };
23
+ }, {
24
+ root: string;
25
+ image: string;
26
+ fallback: string;
27
+ group: string;
28
+ }, undefined, {
29
+ size: {
30
+ xs: {
31
+ root: string;
32
+ };
33
+ sm: {
34
+ root: string;
35
+ };
36
+ md: {
37
+ root: string;
38
+ };
39
+ lg: {
40
+ root: string;
41
+ };
42
+ xl: {
43
+ root: string;
44
+ };
45
+ };
46
+ }, {
47
+ root: string;
48
+ image: string;
49
+ fallback: string;
50
+ group: string;
51
+ }, tailwind_variants.TVReturnTypeLike<{
52
+ size: {
53
+ xs: {
54
+ root: string;
55
+ };
56
+ sm: {
57
+ root: string;
58
+ };
59
+ md: {
60
+ root: string;
61
+ };
62
+ lg: {
63
+ root: string;
64
+ };
65
+ xl: {
66
+ root: string;
67
+ };
68
+ };
69
+ }, {
70
+ root: string;
71
+ image: string;
72
+ fallback: string;
73
+ group: string;
74
+ }>>;
75
+ type AvatarSize = NonNullable<VariantProps<typeof avatar>["size"]>;
76
+ type AvatarProps = Omit<React.ComponentProps<"span">, "children"> & {
77
+ src?: string | null;
78
+ /** The image's text alternative (the person's name); empty when a visible name sits next to the avatar. */
79
+ alt?: string;
80
+ /** Shown while the image loads, when it fails or when there is none: initials, an icon. */
81
+ fallback?: React.ReactNode;
82
+ size?: AvatarSize;
83
+ };
84
+ declare function AvatarRoot({ src, alt, fallback, size, className, ...props }: AvatarProps): React.JSX.Element;
85
+ type AvatarGroupProps = React.ComponentProps<"div"> & {
86
+ /** How many avatars are shown; the rest becomes a `+n` avatar. */
87
+ max?: number;
88
+ /** The size of the `+n` avatar (match the avatars of the group). */
89
+ size?: AvatarSize;
90
+ };
91
+ /** Overlapping avatars; past `max`, a `+n` avatar counts the rest. */
92
+ declare function AvatarGroup({ max, size, className, children, ...props }: AvatarGroupProps): React.JSX.Element;
93
+ /** `Avatar` (an image with a fallback) and `Avatar.Group`. */
94
+ declare const Avatar: typeof AvatarRoot & {
95
+ Group: typeof AvatarGroup;
96
+ };
97
+
98
+ export { Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, AvatarRoot, type AvatarSize };
package/dist/avatar.js ADDED
@@ -0,0 +1,48 @@
1
+ "use client";
2
+
3
+ // src/avatar/avatar.tsx
4
+ import { Avatar as AvatarPrimitive } from "radix-ui";
5
+ import * as React from "react";
6
+ import { tv } from "tailwind-variants";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ var avatar = tv({
9
+ slots: {
10
+ root: "relative inline-flex shrink-0 overflow-hidden rounded-full bg-surface-3 align-middle",
11
+ image: "h-full w-full object-cover",
12
+ fallback: "flex h-full w-full items-center justify-center font-semibold uppercase text-fg-secondary",
13
+ group: "flex items-center -space-x-2 [&>*]:ring-2 [&>*]:ring-surface-0"
14
+ },
15
+ variants: {
16
+ size: {
17
+ xs: { root: "h-5 w-5 text-[9px]" },
18
+ sm: { root: "h-8 w-8 text-xs" },
19
+ md: { root: "h-10 w-10 text-sm" },
20
+ lg: { root: "h-14 w-14 text-base" },
21
+ xl: { root: "h-20 w-20 text-lg" }
22
+ }
23
+ },
24
+ defaultVariants: { size: "md" }
25
+ });
26
+ function AvatarRoot({ src, alt = "", fallback, size, className, ...props }) {
27
+ const s = avatar({ size });
28
+ return /* @__PURE__ */ jsxs(AvatarPrimitive.Root, { "data-size": size ?? "md", className: s.root({ className }), ...props, children: [
29
+ src && /* @__PURE__ */ jsx(AvatarPrimitive.Image, { src, alt, className: s.image() }),
30
+ /* @__PURE__ */ jsx(AvatarPrimitive.Fallback, { delayMs: src ? 300 : 0, className: s.fallback(), children: fallback })
31
+ ] });
32
+ }
33
+ function AvatarGroup({ max, size, className, children, ...props }) {
34
+ const items = React.Children.toArray(children);
35
+ const shown = max === void 0 ? items : items.slice(0, max);
36
+ const rest = items.length - shown.length;
37
+ return /* @__PURE__ */ jsxs("div", { className: avatar().group({ className }), ...props, children: [
38
+ shown,
39
+ rest > 0 && /* @__PURE__ */ jsx(AvatarRoot, { size, fallback: `+${rest}` })
40
+ ] });
41
+ }
42
+ var Avatar = Object.assign(AvatarRoot, { Group: AvatarGroup });
43
+ export {
44
+ Avatar,
45
+ AvatarGroup,
46
+ AvatarRoot
47
+ };
48
+ //# sourceMappingURL=avatar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/avatar/avatar.tsx"],"sourcesContent":["// Client: Radix tracks the image's loading state and shows the fallback until it has loaded (or when it fails).\nimport { Avatar as AvatarPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nconst avatar = tv({\n slots: {\n root: \"relative inline-flex shrink-0 overflow-hidden rounded-full bg-surface-3 align-middle\",\n image: \"h-full w-full object-cover\",\n fallback: \"flex h-full w-full items-center justify-center font-semibold uppercase text-fg-secondary\",\n group: \"flex items-center -space-x-2 [&>*]:ring-2 [&>*]:ring-surface-0\",\n },\n variants: {\n size: {\n xs: { root: \"h-5 w-5 text-[9px]\" },\n sm: { root: \"h-8 w-8 text-xs\" },\n md: { root: \"h-10 w-10 text-sm\" },\n lg: { root: \"h-14 w-14 text-base\" },\n xl: { root: \"h-20 w-20 text-lg\" },\n },\n },\n defaultVariants: { size: \"md\" },\n});\n\nexport type AvatarSize = NonNullable<VariantProps<typeof avatar>[\"size\"]>;\n\nexport type AvatarProps = Omit<React.ComponentProps<\"span\">, \"children\"> & {\n src?: string | null;\n /** The image's text alternative (the person's name); empty when a visible name sits next to the avatar. */\n alt?: string;\n /** Shown while the image loads, when it fails or when there is none: initials, an icon. */\n fallback?: React.ReactNode;\n size?: AvatarSize;\n};\n\nexport function AvatarRoot({ src, alt = \"\", fallback, size, className, ...props }: AvatarProps) {\n const s = avatar({ size });\n return (\n <AvatarPrimitive.Root data-size={size ?? \"md\"} className={s.root({ className })} {...props}>\n {src && <AvatarPrimitive.Image src={src} alt={alt} className={s.image()} />}\n <AvatarPrimitive.Fallback delayMs={src ? 300 : 0} className={s.fallback()}>\n {fallback}\n </AvatarPrimitive.Fallback>\n </AvatarPrimitive.Root>\n );\n}\n\nexport type AvatarGroupProps = React.ComponentProps<\"div\"> & {\n /** How many avatars are shown; the rest becomes a `+n` avatar. */\n max?: number;\n /** The size of the `+n` avatar (match the avatars of the group). */\n size?: AvatarSize;\n};\n\n/** Overlapping avatars; past `max`, a `+n` avatar counts the rest. */\nexport function AvatarGroup({ max, size, className, children, ...props }: AvatarGroupProps) {\n const items = React.Children.toArray(children);\n const shown = max === undefined ? items : items.slice(0, max);\n const rest = items.length - shown.length;\n return (\n <div className={avatar().group({ className })} {...props}>\n {shown}\n {rest > 0 && <AvatarRoot size={size} fallback={`+${rest}`} />}\n </div>\n );\n}\n\n/** `Avatar` (an image with a fallback) and `Avatar.Group`. */\nexport const Avatar = Object.assign(AvatarRoot, { Group: AvatarGroup });\n"],"mappings":";;;AACA,SAAS,UAAU,uBAAuB;AAC1C,YAAY,WAAW;AACvB,SAAS,UAA6B;AAmClC,SACU,KADV;AAjCJ,IAAM,SAAS,GAAG;AAAA,EAChB,OAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,UAAU;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EACA,UAAU;AAAA,IACR,MAAM;AAAA,MACJ,IAAI,EAAE,MAAM,qBAAqB;AAAA,MACjC,IAAI,EAAE,MAAM,kBAAkB;AAAA,MAC9B,IAAI,EAAE,MAAM,oBAAoB;AAAA,MAChC,IAAI,EAAE,MAAM,sBAAsB;AAAA,MAClC,IAAI,EAAE,MAAM,oBAAoB;AAAA,IAClC;AAAA,EACF;AAAA,EACA,iBAAiB,EAAE,MAAM,KAAK;AAChC,CAAC;AAaM,SAAS,WAAW,EAAE,KAAK,MAAM,IAAI,UAAU,MAAM,WAAW,GAAG,MAAM,GAAgB;AAC9F,QAAM,IAAI,OAAO,EAAE,KAAK,CAAC;AACzB,SACE,qBAAC,gBAAgB,MAAhB,EAAqB,aAAW,QAAQ,MAAM,WAAW,EAAE,KAAK,EAAE,UAAU,CAAC,GAAI,GAAG,OAClF;AAAA,WAAO,oBAAC,gBAAgB,OAAhB,EAAsB,KAAU,KAAU,WAAW,EAAE,MAAM,GAAG;AAAA,IACzE,oBAAC,gBAAgB,UAAhB,EAAyB,SAAS,MAAM,MAAM,GAAG,WAAW,EAAE,SAAS,GACrE,oBACH;AAAA,KACF;AAEJ;AAUO,SAAS,YAAY,EAAE,KAAK,MAAM,WAAW,UAAU,GAAG,MAAM,GAAqB;AAC1F,QAAM,QAAc,eAAS,QAAQ,QAAQ;AAC7C,QAAM,QAAQ,QAAQ,SAAY,QAAQ,MAAM,MAAM,GAAG,GAAG;AAC5D,QAAM,OAAO,MAAM,SAAS,MAAM;AAClC,SACE,qBAAC,SAAI,WAAW,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,GAAI,GAAG,OAChD;AAAA;AAAA,IACA,OAAO,KAAK,oBAAC,cAAW,MAAY,UAAU,IAAI,IAAI,IAAI;AAAA,KAC7D;AAEJ;AAGO,IAAM,SAAS,OAAO,OAAO,YAAY,EAAE,OAAO,YAAY,CAAC;","names":[]}
@@ -0,0 +1,64 @@
1
+ import * as tailwind_variants from 'tailwind-variants';
2
+ import { VariantProps } from 'tailwind-variants';
3
+ import * as React from 'react';
4
+
5
+ declare const badgeVariants: tailwind_variants.TVReturnType<{
6
+ tone: {
7
+ neutral: "bg-surface-3 text-fg-secondary";
8
+ accent: "bg-accent-soft text-fg ring-1 ring-inset ring-accent/40 [&>[data-dot]]:bg-accent";
9
+ success: "bg-success/15 text-fg ring-1 ring-inset ring-success/40 [&>[data-dot]]:bg-success";
10
+ warning: "bg-warning/15 text-fg ring-1 ring-inset ring-warning/40 [&>[data-dot]]:bg-warning";
11
+ danger: "bg-danger/15 text-fg ring-1 ring-inset ring-danger/40 [&>[data-dot]]:bg-danger";
12
+ /** On a media: veil and text invariant, identical in both themes. */
13
+ scrim: "bg-scrim text-fg-on-media backdrop-blur-md";
14
+ };
15
+ size: {
16
+ sm: "px-2 py-0.5 text-[10px]";
17
+ md: "px-2.5 py-1 text-[11px]";
18
+ };
19
+ pulse: {
20
+ true: "[&>[data-dot]]:motion-safe:animate-pulse";
21
+ };
22
+ }, undefined, "inline-flex shrink-0 items-center gap-1 whitespace-nowrap rounded-full font-semibold uppercase tracking-wider", {
23
+ tone: {
24
+ neutral: "bg-surface-3 text-fg-secondary";
25
+ accent: "bg-accent-soft text-fg ring-1 ring-inset ring-accent/40 [&>[data-dot]]:bg-accent";
26
+ success: "bg-success/15 text-fg ring-1 ring-inset ring-success/40 [&>[data-dot]]:bg-success";
27
+ warning: "bg-warning/15 text-fg ring-1 ring-inset ring-warning/40 [&>[data-dot]]:bg-warning";
28
+ danger: "bg-danger/15 text-fg ring-1 ring-inset ring-danger/40 [&>[data-dot]]:bg-danger";
29
+ /** On a media: veil and text invariant, identical in both themes. */
30
+ scrim: "bg-scrim text-fg-on-media backdrop-blur-md";
31
+ };
32
+ size: {
33
+ sm: "px-2 py-0.5 text-[10px]";
34
+ md: "px-2.5 py-1 text-[11px]";
35
+ };
36
+ pulse: {
37
+ true: "[&>[data-dot]]:motion-safe:animate-pulse";
38
+ };
39
+ }, undefined, tailwind_variants.TVReturnTypeLike<{
40
+ tone: {
41
+ neutral: "bg-surface-3 text-fg-secondary";
42
+ accent: "bg-accent-soft text-fg ring-1 ring-inset ring-accent/40 [&>[data-dot]]:bg-accent";
43
+ success: "bg-success/15 text-fg ring-1 ring-inset ring-success/40 [&>[data-dot]]:bg-success";
44
+ warning: "bg-warning/15 text-fg ring-1 ring-inset ring-warning/40 [&>[data-dot]]:bg-warning";
45
+ danger: "bg-danger/15 text-fg ring-1 ring-inset ring-danger/40 [&>[data-dot]]:bg-danger";
46
+ /** On a media: veil and text invariant, identical in both themes. */
47
+ scrim: "bg-scrim text-fg-on-media backdrop-blur-md";
48
+ };
49
+ size: {
50
+ sm: "px-2 py-0.5 text-[10px]";
51
+ md: "px-2.5 py-1 text-[11px]";
52
+ };
53
+ pulse: {
54
+ true: "[&>[data-dot]]:motion-safe:animate-pulse";
55
+ };
56
+ }, undefined>>;
57
+ type BadgeVariants = VariantProps<typeof badgeVariants>;
58
+ type BadgeProps = React.ComponentProps<"span"> & BadgeVariants & {
59
+ /** A small dot before the text, in the tone's colour (pulsing with `pulse`). */
60
+ dot?: boolean;
61
+ };
62
+ declare function Badge({ tone, size, pulse, dot, className, children, ...props }: BadgeProps): React.JSX.Element;
63
+
64
+ export { Badge, type BadgeProps, type BadgeVariants, badgeVariants };
package/dist/badge.js ADDED
@@ -0,0 +1,31 @@
1
+ // src/badge/badge.tsx
2
+ import { tv } from "tailwind-variants";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ var badgeVariants = tv({
5
+ base: "inline-flex shrink-0 items-center gap-1 whitespace-nowrap rounded-full font-semibold uppercase tracking-wider",
6
+ variants: {
7
+ tone: {
8
+ neutral: "bg-surface-3 text-fg-secondary",
9
+ accent: "bg-accent-soft text-fg ring-1 ring-inset ring-accent/40 [&>[data-dot]]:bg-accent",
10
+ success: "bg-success/15 text-fg ring-1 ring-inset ring-success/40 [&>[data-dot]]:bg-success",
11
+ warning: "bg-warning/15 text-fg ring-1 ring-inset ring-warning/40 [&>[data-dot]]:bg-warning",
12
+ danger: "bg-danger/15 text-fg ring-1 ring-inset ring-danger/40 [&>[data-dot]]:bg-danger",
13
+ /** On a media: veil and text invariant, identical in both themes. */
14
+ scrim: "bg-scrim text-fg-on-media backdrop-blur-md"
15
+ },
16
+ size: { sm: "px-2 py-0.5 text-[10px]", md: "px-2.5 py-1 text-[11px]" },
17
+ pulse: { true: "[&>[data-dot]]:motion-safe:animate-pulse" }
18
+ },
19
+ defaultVariants: { tone: "neutral", size: "sm" }
20
+ });
21
+ function Badge({ tone, size, pulse, dot, className, children, ...props }) {
22
+ return /* @__PURE__ */ jsxs("span", { "data-tone": tone ?? "neutral", className: badgeVariants({ tone, size, pulse, className }), ...props, children: [
23
+ dot && /* @__PURE__ */ jsx("span", { "data-dot": "", "aria-hidden": true, className: "h-1.5 w-1.5 rounded-full bg-current" }),
24
+ children
25
+ ] });
26
+ }
27
+ export {
28
+ Badge,
29
+ badgeVariants
30
+ };
31
+ //# sourceMappingURL=badge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/badge/badge.tsx"],"sourcesContent":["// Server-safe: no hook, no context.\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\n// Status tones are soft (a tint, a coloured dot, a text role): the invariant status tokens are too light to carry\n// small text on a light surface, or to sit under white text (WCAG AA).\nexport const badgeVariants = tv({\n base: \"inline-flex shrink-0 items-center gap-1 whitespace-nowrap rounded-full font-semibold uppercase tracking-wider\",\n variants: {\n tone: {\n neutral: \"bg-surface-3 text-fg-secondary\",\n accent: \"bg-accent-soft text-fg ring-1 ring-inset ring-accent/40 [&>[data-dot]]:bg-accent\",\n success: \"bg-success/15 text-fg ring-1 ring-inset ring-success/40 [&>[data-dot]]:bg-success\",\n warning: \"bg-warning/15 text-fg ring-1 ring-inset ring-warning/40 [&>[data-dot]]:bg-warning\",\n danger: \"bg-danger/15 text-fg ring-1 ring-inset ring-danger/40 [&>[data-dot]]:bg-danger\",\n /** On a media: veil and text invariant, identical in both themes. */\n scrim: \"bg-scrim text-fg-on-media backdrop-blur-md\",\n },\n size: { sm: \"px-2 py-0.5 text-[10px]\", md: \"px-2.5 py-1 text-[11px]\" },\n pulse: { true: \"[&>[data-dot]]:motion-safe:animate-pulse\" },\n },\n defaultVariants: { tone: \"neutral\", size: \"sm\" },\n});\n\nexport type BadgeVariants = VariantProps<typeof badgeVariants>;\n\nexport type BadgeProps = React.ComponentProps<\"span\"> &\n BadgeVariants & {\n /** A small dot before the text, in the tone's colour (pulsing with `pulse`). */\n dot?: boolean;\n };\n\nexport function Badge({ tone, size, pulse, dot, className, children, ...props }: BadgeProps) {\n return (\n <span data-tone={tone ?? \"neutral\"} className={badgeVariants({ tone, size, pulse, className })} {...props}>\n {dot && <span data-dot=\"\" aria-hidden className=\"h-1.5 w-1.5 rounded-full bg-current\" />}\n {children}\n </span>\n );\n}\n"],"mappings":";AAEA,SAAS,UAA6B;AAgClC,SACU,KADV;AA5BG,IAAM,gBAAgB,GAAG;AAAA,EAC9B,MAAM;AAAA,EACN,UAAU;AAAA,IACR,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,MACT,QAAQ;AAAA;AAAA,MAER,OAAO;AAAA,IACT;AAAA,IACA,MAAM,EAAE,IAAI,2BAA2B,IAAI,0BAA0B;AAAA,IACrE,OAAO,EAAE,MAAM,2CAA2C;AAAA,EAC5D;AAAA,EACA,iBAAiB,EAAE,MAAM,WAAW,MAAM,KAAK;AACjD,CAAC;AAUM,SAAS,MAAM,EAAE,MAAM,MAAM,OAAO,KAAK,WAAW,UAAU,GAAG,MAAM,GAAe;AAC3F,SACE,qBAAC,UAAK,aAAW,QAAQ,WAAW,WAAW,cAAc,EAAE,MAAM,MAAM,OAAO,UAAU,CAAC,GAAI,GAAG,OACjG;AAAA,WAAO,oBAAC,UAAK,YAAS,IAAG,eAAW,MAAC,WAAU,uCAAsC;AAAA,IACrF;AAAA,KACH;AAEJ;","names":[]}
@@ -0,0 +1,76 @@
1
+ import * as tailwind_variants from 'tailwind-variants';
2
+ import { VariantProps } from 'tailwind-variants';
3
+ import * as React from 'react';
4
+
5
+ declare const buttonVariants: tailwind_variants.TVReturnType<{
6
+ variant: {
7
+ primary: "bg-accent text-on-accent shadow-sm hover:bg-accent-hover";
8
+ secondary: "border border-border-default bg-surface-2 text-fg hover:border-border-strong hover:bg-surface-3";
9
+ outline: "border border-border-default bg-transparent text-fg hover:border-accent hover:text-accent";
10
+ ghost: "text-fg-secondary hover:bg-surface-2 hover:text-fg";
11
+ danger: "border border-danger/50 bg-danger/10 text-fg hover:border-danger hover:bg-danger/20";
12
+ };
13
+ size: {
14
+ sm: "h-8 px-3 text-xs";
15
+ md: "h-10 px-4 text-sm";
16
+ lg: "h-12 px-6 text-sm";
17
+ icon: "h-10 w-10";
18
+ };
19
+ shape: {
20
+ rounded: "rounded-lg";
21
+ pill: "rounded-full";
22
+ };
23
+ }, undefined, string, {
24
+ variant: {
25
+ primary: "bg-accent text-on-accent shadow-sm hover:bg-accent-hover";
26
+ secondary: "border border-border-default bg-surface-2 text-fg hover:border-border-strong hover:bg-surface-3";
27
+ outline: "border border-border-default bg-transparent text-fg hover:border-accent hover:text-accent";
28
+ ghost: "text-fg-secondary hover:bg-surface-2 hover:text-fg";
29
+ danger: "border border-danger/50 bg-danger/10 text-fg hover:border-danger hover:bg-danger/20";
30
+ };
31
+ size: {
32
+ sm: "h-8 px-3 text-xs";
33
+ md: "h-10 px-4 text-sm";
34
+ lg: "h-12 px-6 text-sm";
35
+ icon: "h-10 w-10";
36
+ };
37
+ shape: {
38
+ rounded: "rounded-lg";
39
+ pill: "rounded-full";
40
+ };
41
+ }, undefined, tailwind_variants.TVReturnTypeLike<{
42
+ variant: {
43
+ primary: "bg-accent text-on-accent shadow-sm hover:bg-accent-hover";
44
+ secondary: "border border-border-default bg-surface-2 text-fg hover:border-border-strong hover:bg-surface-3";
45
+ outline: "border border-border-default bg-transparent text-fg hover:border-accent hover:text-accent";
46
+ ghost: "text-fg-secondary hover:bg-surface-2 hover:text-fg";
47
+ danger: "border border-danger/50 bg-danger/10 text-fg hover:border-danger hover:bg-danger/20";
48
+ };
49
+ size: {
50
+ sm: "h-8 px-3 text-xs";
51
+ md: "h-10 px-4 text-sm";
52
+ lg: "h-12 px-6 text-sm";
53
+ icon: "h-10 w-10";
54
+ };
55
+ shape: {
56
+ rounded: "rounded-lg";
57
+ pill: "rounded-full";
58
+ };
59
+ }, undefined>>;
60
+ type ButtonVariants = VariantProps<typeof buttonVariants>;
61
+ type ButtonProps = React.ComponentProps<"button"> & ButtonVariants & {
62
+ /** Renders the child (e.g. a <Link>) instead of the <button>, with props, classes and ref merged. */
63
+ asChild?: boolean;
64
+ /** Disables and signals the wait (aria-busy, data-loading) — the product adds a spinner if it wants one. */
65
+ loading?: boolean;
66
+ };
67
+ declare function Button({ asChild, loading, variant, size, shape, className, disabled, ...props }: ButtonProps): React.JSX.Element;
68
+ type IconButtonProps = Omit<ButtonProps, "size"> & {
69
+ /** The accessible name (and tooltip) of the button — required, passed translated. */
70
+ label: string;
71
+ size?: ButtonProps["size"];
72
+ };
73
+ /** An icon-only button: `label` is required, it is its accessible name. */
74
+ declare function IconButton({ label, size, ...props }: IconButtonProps): React.JSX.Element;
75
+
76
+ export { Button, type ButtonProps, type ButtonVariants, IconButton, type IconButtonProps, buttonVariants };