@krizaka/ui 1.2.0 → 2.0.0-beta.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.
- package/README.md +114 -4
- package/dist/avatar.d.ts +98 -0
- package/dist/avatar.js +48 -0
- package/dist/avatar.js.map +1 -0
- package/dist/badge.d.ts +64 -0
- package/dist/badge.js +31 -0
- package/dist/badge.js.map +1 -0
- package/dist/button.d.ts +76 -0
- package/dist/button.js +46 -0
- package/dist/button.js.map +1 -0
- package/dist/chunk-CPMZS4XA.js +11 -0
- package/dist/chunk-CPMZS4XA.js.map +1 -0
- package/dist/cn.d.ts +10 -0
- package/dist/cn.js +7 -0
- package/dist/cn.js.map +1 -0
- package/dist/countdown.d.ts +46 -0
- package/dist/countdown.js +74 -0
- package/dist/countdown.js.map +1 -0
- package/dist/empty-state.d.ts +63 -0
- package/dist/empty-state.js +26 -0
- package/dist/empty-state.js.map +1 -0
- package/dist/field.d.ts +80 -0
- package/dist/field.js +47 -0
- package/dist/field.js.map +1 -0
- package/dist/index.d.ts +9 -9
- package/dist/index.js +5 -7
- package/dist/index.js.map +1 -1
- package/dist/native.js +3 -3
- package/dist/native.js.map +1 -1
- package/dist/skeleton.d.ts +28 -0
- package/dist/skeleton.js +18 -0
- package/dist/skeleton.js.map +1 -0
- package/dist/slot.d.ts +11 -0
- package/dist/slot.js +9 -0
- package/dist/slot.js.map +1 -0
- package/dist/spinner.d.ts +31 -0
- package/dist/spinner.js +31 -0
- package/dist/spinner.js.map +1 -0
- package/dist/theme.d.ts +95 -0
- package/dist/theme.js +140 -0
- package/dist/theme.js.map +1 -0
- package/package.json +185 -10
- package/tailwind.css +3 -0
package/README.md
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
# @krizaka/ui
|
|
6
6
|
|
|
7
|
-
**The Krizaka
|
|
8
|
-
The
|
|
9
|
-
reveal, roll, shine
|
|
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
|
[](https://www.npmjs.com/package/@krizaka/ui)
|
|
12
13
|
[](https://github.com/krizaka/krizaka-ui/actions/workflows/ci.yml)
|
|
@@ -20,9 +21,103 @@ reveal, roll, shine and open.
|
|
|
20
21
|
npm install @krizaka/ui
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
React 18 or 19
|
|
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 first layer (atoms). Cards, dialogs, tabs, toasts and menus follow in the next betas.
|
|
31
|
+
|
|
32
|
+
### Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install @krizaka/ui@beta @krizaka/tailwind@beta @krizaka/tokens@beta tailwindcss
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```css
|
|
39
|
+
/* app/globals.css — the preset (tokens as utilities), then the classes the primitives use */
|
|
40
|
+
@import "tailwindcss";
|
|
41
|
+
@import "@krizaka/tailwind";
|
|
42
|
+
@import "@krizaka/ui/tailwind.css";
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { Button, IconButton } from "@krizaka/ui/button";
|
|
47
|
+
import { Badge } from "@krizaka/ui/badge";
|
|
48
|
+
import { Field, Input } from "@krizaka/ui/field";
|
|
49
|
+
import { ThemeProvider, ThemeScript, ThemeToggle } from "@krizaka/ui/theme";
|
|
50
|
+
|
|
51
|
+
<Button variant="primary" loading={saving}>{t("save")}</Button>
|
|
52
|
+
<Button asChild variant="outline"><Link href="/docs">{t("docs")}</Link></Button>
|
|
53
|
+
<Badge tone="danger" dot pulse>{t("endsSoon")}</Badge>
|
|
54
|
+
<Field.Root>
|
|
55
|
+
<Field.Label htmlFor="email">{t("email")}</Field.Label>
|
|
56
|
+
<Input id="email" invalid={!!error} aria-describedby="email-error" />
|
|
57
|
+
{error && <Field.Error id="email-error">{error}</Field.Error>}
|
|
58
|
+
</Field.Root>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**React 19 is required by the primitives** (`ref` is a prop, no `forwardRef`). The package keeps `react >= 18` as its
|
|
62
|
+
peer range, because the marks and the motion still run on React 18: one `package.json` cannot hold both ranges.
|
|
63
|
+
`tailwindcss` and `@krizaka/tailwind` are optional peers — a native app or a marks-only site never installs them.
|
|
64
|
+
|
|
65
|
+
### The component contract (12 points)
|
|
66
|
+
|
|
67
|
+
1. **Composed when there is structure** (`Field.*`, `Avatar.Group`), **flat for an atom** (`Button`, `Badge`, `Input`).
|
|
68
|
+
2. **Variants exported next to the component** (`buttonVariants`, `badgeVariants`): a `<Link>` or a Server Component
|
|
69
|
+
is styled without importing the component.
|
|
70
|
+
3. **`asChild`** (Radix `Slot`) rather than `as`: props, `className` and `ref` are merged into the child.
|
|
71
|
+
4. **`className` is always merged last** (tailwind-merge, through `cn`): the product's override wins.
|
|
72
|
+
5. **Words are props** (`label`, `closeLabel`, `units`, `title`) — there is no `t()` in the kit. Each app re-exports the
|
|
73
|
+
primitives with its words from its own `components/ui/`.
|
|
74
|
+
6. **Controlled and uncontrolled**: `value`/`defaultValue` + `onValueChange`, `open`/`defaultOpen` + `onOpenChange`.
|
|
75
|
+
7. **State is exposed as `data-*`** (`data-tone`, `data-loading`, `data-invalid`, `data-urgent`, `data-mode`): a
|
|
76
|
+
product styles a state without a new variant.
|
|
77
|
+
8. **Server by default**: `"use client"` only on the entries with a hook or a portal (`avatar`, `theme`, `countdown`).
|
|
78
|
+
One build entry per component; the directive is kept per entry.
|
|
79
|
+
9. **React 19 minimum for the primitives**; the marks stay `react >= 18`.
|
|
80
|
+
10. **Accessibility is delegated**: non-trivial interactions on Radix UI (`radix-ui`), the command palette on cmdk,
|
|
81
|
+
toasts on sonner — no hand-written focus trap.
|
|
82
|
+
11. **Every primitive ships** types, a story (every variant, dark and light, screenshot-tested), an RTL + axe test and
|
|
83
|
+
an entry here.
|
|
84
|
+
12. **Motion**: `prefers-reduced-motion` is honoured (`motion-safe:`); no component animates a layout property.
|
|
85
|
+
|
|
86
|
+
Colours are roles of the preset (`bg-surface-2`, `text-fg-secondary`, `border-border-default`, `text-accent`,
|
|
87
|
+
`text-danger`), never a palette step nor `light:`: a theme — dark, light or a product's — is a set of tokens.
|
|
88
|
+
|
|
89
|
+
### Entries
|
|
90
|
+
|
|
91
|
+
| Entry | Exports | Runs |
|
|
92
|
+
| :-- | :-- | :-- |
|
|
93
|
+
| `@krizaka/ui/cn` | `cn(...classes)` — clsx + tailwind-merge (the engine `tailwind-variants` ships) | server |
|
|
94
|
+
| `@krizaka/ui/slot` | `Slot`, `VisuallyHidden` (Radix) | server |
|
|
95
|
+
| `@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 |
|
|
96
|
+
| `@krizaka/ui/badge` | `Badge` (`tone` neutral·accent·success·warning·danger·scrim, `size` sm·md, `dot`, `pulse`), `badgeVariants` | server |
|
|
97
|
+
| `@krizaka/ui/avatar` | `Avatar` (`src`, `alt`, `fallback`, `size` xs·sm·md·lg·xl), `Avatar.Group` (`max`) | client |
|
|
98
|
+
| `@krizaka/ui/field` | `Field.Root/Label/Hint/Error`, `Input`, `Textarea`, `Select` (native, styled) — `invalid` | server |
|
|
99
|
+
| `@krizaka/ui/theme` | `ThemeScript` (in `<head>`, no flash), `ThemeProvider`, `useTheme()` → `{ mode, setMode, theme, setTheme }`, `ThemeToggle` (`label`) | client |
|
|
100
|
+
| `@krizaka/ui/skeleton` | `Skeleton` (`shape` text·circle·rect) | server |
|
|
101
|
+
| `@krizaka/ui/empty-state` | `EmptyState` (`icon`, `title`, `description`, `action`) | server |
|
|
102
|
+
| `@krizaka/ui/spinner` | `Spinner` (`label`, `size`) | server |
|
|
103
|
+
| `@krizaka/ui/countdown` | `Countdown` (`target`, `label`, `units`, `size`, `urgentBelowMs`, `skewMs`), `useCountdown`, `splitDuration` | client |
|
|
104
|
+
| `@krizaka/ui` | the marks, `MotionObserver`, `RotatingWord`, `cx` (unchanged) | client |
|
|
105
|
+
| `@krizaka/ui/native` | the marks for React Native (unchanged) | native |
|
|
106
|
+
| `@krizaka/ui/motion.css`, `@krizaka/ui/tailwind.css` | the motion signature; the `@source` of the primitives' classes | CSS |
|
|
107
|
+
|
|
108
|
+
The theme is one mechanism for every product: dark by default, `html.light` for light, `html.theme-<name>` for a named
|
|
109
|
+
theme, persisted as `kz-theme` (and `kz-theme-name`).
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
// app/layout.tsx
|
|
113
|
+
<html lang={locale} suppressHydrationWarning>
|
|
114
|
+
<head><ThemeScript /></head>
|
|
115
|
+
<body><ThemeProvider>{children}</ThemeProvider></body>
|
|
116
|
+
</html>
|
|
117
|
+
|
|
118
|
+
<ThemeToggle label={(mode) => t(`theme.${mode}`)} />
|
|
119
|
+
```
|
|
120
|
+
|
|
26
121
|
## React Native
|
|
27
122
|
|
|
28
123
|
The marks have one geometry; `@krizaka/ui/native` draws it with `react-native-svg` for the apps
|
|
@@ -84,6 +179,21 @@ With Tailwind CSS v4, give every transition the same easing:
|
|
|
84
179
|
}
|
|
85
180
|
```
|
|
86
181
|
|
|
182
|
+
## With Tailwind CSS v4
|
|
183
|
+
|
|
184
|
+
The components are styled with the [`@krizaka/tailwind`](https://www.npmjs.com/package/@krizaka/tailwind) utilities.
|
|
185
|
+
Import the preset, then `@krizaka/ui/tailwind.css`: it points Tailwind at the package (`@source "./dist"`), so the
|
|
186
|
+
classes the components use are generated in your stylesheet.
|
|
187
|
+
|
|
188
|
+
```css
|
|
189
|
+
@import "tailwindcss";
|
|
190
|
+
@import "@krizaka/tailwind";
|
|
191
|
+
@import "@krizaka/ui/tailwind.css";
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Every component, in dark and light and under each product identity:
|
|
195
|
+
[krizaka.github.io/krizaka-ui/latest](https://krizaka.github.io/krizaka-ui/latest/).
|
|
196
|
+
|
|
87
197
|
## Develop
|
|
88
198
|
|
|
89
199
|
This package lives in the [`krizaka-ui`](https://github.com/krizaka/krizaka-ui) monorepo (pnpm + Turborepo), in
|
package/dist/avatar.d.ts
ADDED
|
@@ -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":[]}
|
package/dist/badge.d.ts
ADDED
|
@@ -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":[]}
|
package/dist/button.d.ts
ADDED
|
@@ -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 };
|
package/dist/button.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/button/button.tsx
|
|
2
|
+
import { Slot } from "radix-ui";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
var buttonVariants = tv({
|
|
6
|
+
base: "inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap font-semibold transition-all focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface-0 disabled:pointer-events-none disabled:opacity-40 data-loading:cursor-progress active:scale-[0.98]",
|
|
7
|
+
variants: {
|
|
8
|
+
variant: {
|
|
9
|
+
primary: "bg-accent text-on-accent shadow-sm hover:bg-accent-hover",
|
|
10
|
+
secondary: "border border-border-default bg-surface-2 text-fg hover:border-border-strong hover:bg-surface-3",
|
|
11
|
+
outline: "border border-border-default bg-transparent text-fg hover:border-accent hover:text-accent",
|
|
12
|
+
ghost: "text-fg-secondary hover:bg-surface-2 hover:text-fg",
|
|
13
|
+
// The danger token is invariant and too light for small text on a light surface (3.6:1): the label stays a
|
|
14
|
+
// text role, the border and the tint carry the danger.
|
|
15
|
+
danger: "border border-danger/50 bg-danger/10 text-fg hover:border-danger hover:bg-danger/20"
|
|
16
|
+
},
|
|
17
|
+
size: { sm: "h-8 px-3 text-xs", md: "h-10 px-4 text-sm", lg: "h-12 px-6 text-sm", icon: "h-10 w-10" },
|
|
18
|
+
shape: { rounded: "rounded-lg", pill: "rounded-full" }
|
|
19
|
+
},
|
|
20
|
+
defaultVariants: { variant: "secondary", size: "md", shape: "rounded" }
|
|
21
|
+
});
|
|
22
|
+
function Button({ asChild, loading, variant, size, shape, className, disabled, ...props }) {
|
|
23
|
+
const Comp = asChild ? Slot.Root : "button";
|
|
24
|
+
return /* @__PURE__ */ jsx(
|
|
25
|
+
Comp,
|
|
26
|
+
{
|
|
27
|
+
type: asChild ? void 0 : "button",
|
|
28
|
+
"aria-busy": loading || void 0,
|
|
29
|
+
"data-loading": loading ? "" : void 0,
|
|
30
|
+
"data-variant": variant ?? "secondary",
|
|
31
|
+
disabled: asChild ? void 0 : disabled || loading,
|
|
32
|
+
"aria-disabled": asChild && (disabled || loading) ? true : void 0,
|
|
33
|
+
className: buttonVariants({ variant, size, shape, className }),
|
|
34
|
+
...props
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
function IconButton({ label, size = "icon", ...props }) {
|
|
39
|
+
return /* @__PURE__ */ jsx(Button, { "aria-label": label, title: label, size, ...props });
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
Button,
|
|
43
|
+
IconButton,
|
|
44
|
+
buttonVariants
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=button.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/button/button.tsx"],"sourcesContent":["// Server-safe: no hook, no context. `buttonVariants` styles a <Link> or a Server Component without the component.\nimport { Slot } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nexport const buttonVariants = tv({\n base:\n \"inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap font-semibold transition-all \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface-0 \" +\n \"disabled:pointer-events-none disabled:opacity-40 data-loading:cursor-progress active:scale-[0.98]\",\n variants: {\n variant: {\n primary: \"bg-accent text-on-accent shadow-sm hover:bg-accent-hover\",\n secondary: \"border border-border-default bg-surface-2 text-fg hover:border-border-strong hover:bg-surface-3\",\n outline: \"border border-border-default bg-transparent text-fg hover:border-accent hover:text-accent\",\n ghost: \"text-fg-secondary hover:bg-surface-2 hover:text-fg\",\n // The danger token is invariant and too light for small text on a light surface (3.6:1): the label stays a\n // text role, the border and the tint carry the danger.\n danger: \"border border-danger/50 bg-danger/10 text-fg hover:border-danger hover:bg-danger/20\",\n },\n size: { sm: \"h-8 px-3 text-xs\", md: \"h-10 px-4 text-sm\", lg: \"h-12 px-6 text-sm\", icon: \"h-10 w-10\" },\n shape: { rounded: \"rounded-lg\", pill: \"rounded-full\" },\n },\n defaultVariants: { variant: \"secondary\", size: \"md\", shape: \"rounded\" },\n});\n\nexport type ButtonVariants = VariantProps<typeof buttonVariants>;\n\nexport type ButtonProps = React.ComponentProps<\"button\"> &\n ButtonVariants & {\n /** Renders the child (e.g. a <Link>) instead of the <button>, with props, classes and ref merged. */\n asChild?: boolean;\n /** Disables and signals the wait (aria-busy, data-loading) — the product adds a spinner if it wants one. */\n loading?: boolean;\n };\n\nexport function Button({ asChild, loading, variant, size, shape, className, disabled, ...props }: ButtonProps) {\n const Comp = asChild ? Slot.Root : \"button\";\n return (\n <Comp\n type={asChild ? undefined : \"button\"}\n aria-busy={loading || undefined}\n data-loading={loading ? \"\" : undefined}\n data-variant={variant ?? \"secondary\"}\n disabled={asChild ? undefined : disabled || loading}\n aria-disabled={asChild && (disabled || loading) ? true : undefined}\n className={buttonVariants({ variant, size, shape, className })}\n {...props}\n />\n );\n}\n\nexport type IconButtonProps = Omit<ButtonProps, \"size\"> & {\n /** The accessible name (and tooltip) of the button — required, passed translated. */\n label: string;\n size?: ButtonProps[\"size\"];\n};\n\n/** An icon-only button: `label` is required, it is its accessible name. */\nexport function IconButton({ label, size = \"icon\", ...props }: IconButtonProps) {\n return <Button aria-label={label} title={label} size={size} {...props} />;\n}\n"],"mappings":";AACA,SAAS,YAAY;AAErB,SAAS,UAA6B;AAoClC;AAlCG,IAAM,iBAAiB,GAAG;AAAA,EAC/B,MACE;AAAA,EAGF,UAAU;AAAA,IACR,SAAS;AAAA,MACP,SAAS;AAAA,MACT,WAAW;AAAA,MACX,SAAS;AAAA,MACT,OAAO;AAAA;AAAA;AAAA,MAGP,QAAQ;AAAA,IACV;AAAA,IACA,MAAM,EAAE,IAAI,oBAAoB,IAAI,qBAAqB,IAAI,qBAAqB,MAAM,YAAY;AAAA,IACpG,OAAO,EAAE,SAAS,cAAc,MAAM,eAAe;AAAA,EACvD;AAAA,EACA,iBAAiB,EAAE,SAAS,aAAa,MAAM,MAAM,OAAO,UAAU;AACxE,CAAC;AAYM,SAAS,OAAO,EAAE,SAAS,SAAS,SAAS,MAAM,OAAO,WAAW,UAAU,GAAG,MAAM,GAAgB;AAC7G,QAAM,OAAO,UAAU,KAAK,OAAO;AACnC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,UAAU,SAAY;AAAA,MAC5B,aAAW,WAAW;AAAA,MACtB,gBAAc,UAAU,KAAK;AAAA,MAC7B,gBAAc,WAAW;AAAA,MACzB,UAAU,UAAU,SAAY,YAAY;AAAA,MAC5C,iBAAe,YAAY,YAAY,WAAW,OAAO;AAAA,MACzD,WAAW,eAAe,EAAE,SAAS,MAAM,OAAO,UAAU,CAAC;AAAA,MAC5D,GAAG;AAAA;AAAA,EACN;AAEJ;AASO,SAAS,WAAW,EAAE,OAAO,OAAO,QAAQ,GAAG,MAAM,GAAoB;AAC9E,SAAO,oBAAC,UAAO,cAAY,OAAO,OAAO,OAAO,MAAa,GAAG,OAAO;AACzE;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cn.ts"],"sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { cn as merge } from \"tailwind-variants\";\n\nexport type { ClassValue };\n\n/**\n * Merges Tailwind classes: the last one wins — a product's `className` overrides the primitive's. The merge engine is\n * tailwind-merge as tailwind-variants ships it (the one `tv` uses), so the platform carries a single copy of it.\n */\nexport function cn(...inputs: ClassValue[]): string {\n return merge(clsx(inputs)) ?? \"\";\n}\n"],"mappings":";AAAA,SAA0B,YAAY;AACtC,SAAS,MAAM,aAAa;AAQrB,SAAS,MAAM,QAA8B;AAClD,SAAO,MAAM,KAAK,MAAM,CAAC,KAAK;AAChC;","names":[]}
|
package/dist/cn.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ClassValue } from 'clsx';
|
|
2
|
+
export { ClassValue } from 'clsx';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Merges Tailwind classes: the last one wins — a product's `className` overrides the primitive's. The merge engine is
|
|
6
|
+
* tailwind-merge as tailwind-variants ships it (the one `tv` uses), so the platform carries a single copy of it.
|
|
7
|
+
*/
|
|
8
|
+
declare function cn(...inputs: ClassValue[]): string;
|
|
9
|
+
|
|
10
|
+
export { cn };
|
package/dist/cn.js
ADDED
package/dist/cn.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/** Milliseconds left until `target` (never negative), ticking every second. `skewMs` = server clock − this clock. */
|
|
4
|
+
declare function useCountdown(target: Date | string | number, skewMs?: number): number;
|
|
5
|
+
/** Splits a duration into days, hours, minutes and seconds. */
|
|
6
|
+
declare function splitDuration(ms: number): {
|
|
7
|
+
d: number;
|
|
8
|
+
h: number;
|
|
9
|
+
m: number;
|
|
10
|
+
s: number;
|
|
11
|
+
};
|
|
12
|
+
/** The short unit labels, passed translated (e.g. `{ d: "d", h: "h", m: "m", s: "s" }`). */
|
|
13
|
+
type CountdownUnits = {
|
|
14
|
+
d: string;
|
|
15
|
+
h: string;
|
|
16
|
+
m: string;
|
|
17
|
+
s: string;
|
|
18
|
+
};
|
|
19
|
+
/** The three segments shown: days, hours, minutes when there are days; else hours, minutes, seconds. */
|
|
20
|
+
declare function countdownParts(ms: number, units: CountdownUnits): [number, string][];
|
|
21
|
+
|
|
22
|
+
type CountdownProps = {
|
|
23
|
+
target: Date | string | number;
|
|
24
|
+
/** Server clock − this clock, in milliseconds. */
|
|
25
|
+
skewMs?: number;
|
|
26
|
+
/** The short unit labels, passed translated. */
|
|
27
|
+
units: CountdownUnits;
|
|
28
|
+
/** Below this many milliseconds it turns urgent (`text-danger`, the last segment pulses). Default 60 s. */
|
|
29
|
+
urgentBelowMs?: number;
|
|
30
|
+
size?: "sm" | "md" | "lg";
|
|
31
|
+
/** Accessible name, e.g. "Ends in". */
|
|
32
|
+
label: string;
|
|
33
|
+
className?: string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Time left until a moment, as segments (2d 04h 13m 09s — days only when there are some) in tabular figures. Under
|
|
37
|
+
* `urgentBelowMs` it turns to the danger role and its last segment pulses (no motion under prefers-reduced-motion).
|
|
38
|
+
* Words come from the app: `units` are the short unit labels. `role="timer"` without live announcements — the app
|
|
39
|
+
* announces what matters. State: `data-urgent`, `data-ended`.
|
|
40
|
+
*
|
|
41
|
+
* The danger role is large-text safe (≥ 3:1) from `size="md"`; at `size="sm"` an urgent countdown in light mode is
|
|
42
|
+
* under the 4.5:1 of small text.
|
|
43
|
+
*/
|
|
44
|
+
declare function Countdown({ target, skewMs, units, urgentBelowMs, size, label, className }: CountdownProps): React.JSX.Element;
|
|
45
|
+
|
|
46
|
+
export { Countdown, type CountdownProps, type CountdownUnits, countdownParts, splitDuration, useCountdown };
|