@rinzai/zen 0.13.0 → 0.15.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/CHANGELOG.md +138 -0
- package/README.md +16 -1
- package/dist/carousel/index.d.ts.map +1 -1
- package/dist/carousel/index.js +5 -5
- package/dist/docs/README.md +205 -0
- package/dist/docs/accordion.md +122 -0
- package/dist/docs/alert.md +69 -0
- package/dist/docs/animated-number.md +105 -0
- package/dist/docs/avatar.md +91 -0
- package/dist/docs/backdrop.md +94 -0
- package/dist/docs/badge.md +37 -0
- package/dist/docs/breadcrumbs.md +44 -0
- package/dist/docs/button-group.md +76 -0
- package/dist/docs/button.md +131 -0
- package/dist/docs/calendar-heatmap.md +73 -0
- package/dist/docs/calendar.md +101 -0
- package/dist/docs/canvas.md +584 -0
- package/dist/docs/card.md +180 -0
- package/dist/docs/carousel.md +162 -0
- package/dist/docs/chart.md +325 -0
- package/dist/docs/checkbox.md +88 -0
- package/dist/docs/chip.md +52 -0
- package/dist/docs/code-block.md +116 -0
- package/dist/docs/code-input.md +52 -0
- package/dist/docs/collapse.md +46 -0
- package/dist/docs/color-picker.md +145 -0
- package/dist/docs/combobox.md +208 -0
- package/dist/docs/command-palette.md +65 -0
- package/dist/docs/date-picker.md +167 -0
- package/dist/docs/dialog.md +271 -0
- package/dist/docs/disclosure.md +88 -0
- package/dist/docs/editable-cell.md +90 -0
- package/dist/docs/empty-state.md +46 -0
- package/dist/docs/field.md +82 -0
- package/dist/docs/file-drop.md +44 -0
- package/dist/docs/filter-bar.md +66 -0
- package/dist/docs/header.md +51 -0
- package/dist/docs/hover-card.md +110 -0
- package/dist/docs/icon-picker.md +65 -0
- package/dist/docs/input-group.md +89 -0
- package/dist/docs/input.md +33 -0
- package/dist/docs/inset.md +57 -0
- package/dist/docs/kbd.md +34 -0
- package/dist/docs/keypad.md +51 -0
- package/dist/docs/load-more.md +66 -0
- package/dist/docs/locale-provider.md +99 -0
- package/dist/docs/marquee.md +86 -0
- package/dist/docs/menu.md +403 -0
- package/dist/docs/meter.md +102 -0
- package/dist/docs/money-input.md +169 -0
- package/dist/docs/month-picker.md +80 -0
- package/dist/docs/navigation-menu.md +157 -0
- package/dist/docs/number-field.md +129 -0
- package/dist/docs/page-header.md +44 -0
- package/dist/docs/page-transition.md +93 -0
- package/dist/docs/pagination.md +42 -0
- package/dist/docs/pills.md +85 -0
- package/dist/docs/player-bar.md +158 -0
- package/dist/docs/popover.md +200 -0
- package/dist/docs/progress-ring.md +70 -0
- package/dist/docs/radio-cards.md +82 -0
- package/dist/docs/radio-group.md +86 -0
- package/dist/docs/range-slider.md +64 -0
- package/dist/docs/repeat-picker.md +44 -0
- package/dist/docs/resizable.md +107 -0
- package/dist/docs/seek-bar.md +68 -0
- package/dist/docs/segmented.md +89 -0
- package/dist/docs/select.md +148 -0
- package/dist/docs/separator.md +76 -0
- package/dist/docs/side-nav.md +75 -0
- package/dist/docs/skeleton.md +89 -0
- package/dist/docs/slider.md +87 -0
- package/dist/docs/sortable.md +182 -0
- package/dist/docs/sparkline.md +56 -0
- package/dist/docs/spinner.md +69 -0
- package/dist/docs/status-pill.md +81 -0
- package/dist/docs/stepper.md +59 -0
- package/dist/docs/tab-bar.md +78 -0
- package/dist/docs/table-of-contents.md +43 -0
- package/dist/docs/table.md +714 -0
- package/dist/docs/tabs.md +140 -0
- package/dist/docs/tag-input.md +45 -0
- package/dist/docs/textarea.md +25 -0
- package/dist/docs/theme-toggle.md +85 -0
- package/dist/docs/theming.md +193 -0
- package/dist/docs/time-field.md +71 -0
- package/dist/docs/timeline.md +50 -0
- package/dist/docs/toast.md +53 -0
- package/dist/docs/toggle-group.md +83 -0
- package/dist/docs/toggle.md +38 -0
- package/dist/docs/toolbar.md +97 -0
- package/dist/docs/tooltip.md +60 -0
- package/dist/docs/tree.md +295 -0
- package/dist/docs/trend.md +50 -0
- package/dist/icons/arrow-path.d.ts +3 -0
- package/dist/icons/arrow-path.d.ts.map +1 -0
- package/dist/icons/arrows-right-left.d.ts +3 -0
- package/dist/icons/arrows-right-left.d.ts.map +1 -0
- package/dist/icons/backward.d.ts +3 -0
- package/dist/icons/backward.d.ts.map +1 -0
- package/dist/icons/forward.d.ts +3 -0
- package/dist/icons/forward.d.ts.map +1 -0
- package/dist/icons/heart-solid.d.ts +3 -0
- package/dist/icons/heart-solid.d.ts.map +1 -0
- package/dist/icons/heart.d.ts +3 -0
- package/dist/icons/heart.d.ts.map +1 -0
- package/dist/icons/musical-note.d.ts +3 -0
- package/dist/icons/musical-note.d.ts.map +1 -0
- package/dist/icons/pause.d.ts +3 -0
- package/dist/icons/pause.d.ts.map +1 -0
- package/dist/icons/play.d.ts +3 -0
- package/dist/icons/play.d.ts.map +1 -0
- package/dist/icons/queue-list.d.ts +3 -0
- package/dist/icons/queue-list.d.ts.map +1 -0
- package/dist/icons/speaker-wave.d.ts +3 -0
- package/dist/icons/speaker-wave.d.ts.map +1 -0
- package/dist/icons/speaker-x-mark.d.ts +3 -0
- package/dist/icons/speaker-x-mark.d.ts.map +1 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/lib.js +80 -74
- package/dist/marquee/index.d.ts +15 -0
- package/dist/marquee/index.d.ts.map +1 -0
- package/dist/marquee/index.js +90 -0
- package/dist/menu/index.d.ts.map +1 -1
- package/dist/menu/index.js +7 -6
- package/dist/number-field/index.d.ts +18 -2
- package/dist/number-field/index.d.ts.map +1 -1
- package/dist/number-field/index.js +65 -51
- package/dist/player-bar/index.d.ts +11 -0
- package/dist/player-bar/index.d.ts.map +1 -0
- package/dist/player-bar/index.js +17 -0
- package/dist/radio-cards/index.d.ts +2 -1
- package/dist/radio-cards/index.d.ts.map +1 -1
- package/dist/radio-cards/index.js +3 -3
- package/dist/seek-bar/index.d.ts +24 -0
- package/dist/seek-bar/index.d.ts.map +1 -0
- package/dist/seek-bar/index.js +120 -0
- package/dist/segmented/index.d.ts +3 -2
- package/dist/segmented/index.d.ts.map +1 -1
- package/dist/segmented/index.js +19 -18
- package/dist/slider/index.d.ts +2 -1
- package/dist/slider/index.d.ts.map +1 -1
- package/dist/tab-bar/index.d.ts +4 -2
- package/dist/tab-bar/index.d.ts.map +1 -1
- package/dist/tab-bar/index.js +24 -19
- package/dist/textarea/index.d.ts +6 -2
- package/dist/textarea/index.d.ts.map +1 -1
- package/dist/textarea/index.js +2 -1
- package/dist/theme.css +40 -0
- package/dist/utils/duration.d.ts +3 -0
- package/dist/utils/duration.d.ts.map +1 -0
- package/dist/utils/duration.js +7 -0
- package/dist/utils/messages.d.ts +2 -0
- package/dist/utils/messages.d.ts.map +1 -1
- package/dist/utils/messages.js +3 -1
- package/dist/utils/slot.d.ts +5 -2
- package/dist/utils/slot.d.ts.map +1 -1
- package/dist/utils/slot.js +16 -11
- package/dist/utils/useAnchoredPopup.d.ts.map +1 -1
- package/dist/utils/useAnchoredPopup.js +3 -2
- package/dist/utils/useHeightVar.d.ts +7 -0
- package/dist/utils/useHeightVar.d.ts.map +1 -0
- package/dist/utils/useHeightVar.js +17 -0
- package/dist/utils/useMediaPlayer.d.ts +38 -0
- package/dist/utils/useMediaPlayer.d.ts.map +1 -0
- package/dist/utils/useMediaPlayer.js +91 -0
- package/dist/utils/useMediaSession.d.ts +24 -0
- package/dist/utils/useMediaSession.d.ts.map +1 -0
- package/dist/utils/useMediaSession.js +76 -0
- package/dist/zen.css +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Animated Number
|
|
2
|
+
|
|
3
|
+
Numbers and amounts that count smoothly to each new value.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { AnimatedNumber, AnimatedMoney } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { AnimatedMoney, AnimatedNumber, Button, Stat, StatRow } from '@rinzai/zen';
|
|
15
|
+
import { useState } from 'react';
|
|
16
|
+
|
|
17
|
+
/** Numbers count to each new value (and up from zero when first shown). */
|
|
18
|
+
export default function Default() {
|
|
19
|
+
const [net, setNet] = useState(625400);
|
|
20
|
+
const [entries, setEntries] = useState(42);
|
|
21
|
+
return (
|
|
22
|
+
<div className="flex w-full max-w-lg flex-col gap-4">
|
|
23
|
+
<StatRow className="sm:grid-cols-2!">
|
|
24
|
+
<Stat
|
|
25
|
+
label="Net this month"
|
|
26
|
+
tone="positive"
|
|
27
|
+
value={<AnimatedMoney value={net} currency="USD" locale="en-US" />}
|
|
28
|
+
/>
|
|
29
|
+
<Stat label="Entries" value={<AnimatedNumber value={entries} />} />
|
|
30
|
+
</StatRow>
|
|
31
|
+
<Button
|
|
32
|
+
variant="outline"
|
|
33
|
+
className="self-start"
|
|
34
|
+
onClick={() => {
|
|
35
|
+
setNet((n) => n + Math.round((Math.random() - 0.4) * 300000));
|
|
36
|
+
setEntries((e) => e + 1);
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
Add an entry
|
|
40
|
+
</Button>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Roll
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { AnimatedMoney, Button, Stat } from '@rinzai/zen';
|
|
50
|
+
import { useState } from 'react';
|
|
51
|
+
|
|
52
|
+
/** With `roll`, each digit rolls to its new one, like an odometer. */
|
|
53
|
+
export default function Roll() {
|
|
54
|
+
const [total, setTotal] = useState(184250);
|
|
55
|
+
return (
|
|
56
|
+
<div className="flex w-full max-w-xs flex-col gap-4">
|
|
57
|
+
<Stat
|
|
58
|
+
label="Spent this month"
|
|
59
|
+
value={<AnimatedMoney roll value={total} currency="USD" locale="en-US" showDecimals />}
|
|
60
|
+
/>
|
|
61
|
+
<Button
|
|
62
|
+
variant="outline"
|
|
63
|
+
className="self-start"
|
|
64
|
+
onClick={() => setTotal((t) => t + Math.round(Math.random() * 40000))}
|
|
65
|
+
>
|
|
66
|
+
Add a purchase
|
|
67
|
+
</Button>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## API reference
|
|
74
|
+
|
|
75
|
+
### AnimatedNumber
|
|
76
|
+
|
|
77
|
+
A number that counts smoothly to each new value (and up from zero when it
|
|
78
|
+
first appears). Rounds to whole numbers while moving, so money in minor
|
|
79
|
+
units never shows fractions. With `roll`, each digit rolls to its new one
|
|
80
|
+
instead, like an odometer: easier to read when a total changes. Instant
|
|
81
|
+
with reduced motion.
|
|
82
|
+
|
|
83
|
+
| Prop | Type | Default | Description |
|
|
84
|
+
| --- | --- | --- | --- |
|
|
85
|
+
| `value` (required) | `number` | | |
|
|
86
|
+
| `className` | `string` | | |
|
|
87
|
+
| `duration` | `number` | `650` | Milliseconds per change. |
|
|
88
|
+
| `format` | `((value: number) => string)` | | Turns the (whole) number into text, e.g. with Intl.NumberFormat. |
|
|
89
|
+
| `fromZero` | `boolean` | `true` | Count up from zero when first shown (default), or start at the value. |
|
|
90
|
+
| `roll` | `boolean` | `false` | Roll each digit to its new one, like an odometer, instead of counting. |
|
|
91
|
+
|
|
92
|
+
### AnimatedMoney
|
|
93
|
+
|
|
94
|
+
An amount of money (minor units) that counts to each new value.
|
|
95
|
+
|
|
96
|
+
| Prop | Type | Default | Description |
|
|
97
|
+
| --- | --- | --- | --- |
|
|
98
|
+
| `currency` (required) | `string` | | |
|
|
99
|
+
| `value` (required) | `number` | | |
|
|
100
|
+
| `className` | `string` | | |
|
|
101
|
+
| `duration` | `number` | | Milliseconds per change. |
|
|
102
|
+
| `fromZero` | `boolean` | | Count up from zero when first shown (default), or start at the value. |
|
|
103
|
+
| `locale` | `string` | | Defaults to the LocaleProvider's. |
|
|
104
|
+
| `roll` | `boolean` | | Roll each digit to its new one, like an odometer, instead of counting. |
|
|
105
|
+
| `showDecimals` | `boolean` | `false` | |
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Avatar
|
|
2
|
+
|
|
3
|
+
A round picture, or the name's initial on the glowing accent. With an in-browser crop for uploads.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Avatar } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Avatar } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
const PHOTO =
|
|
17
|
+
'data:image/svg+xml;utf8,' +
|
|
18
|
+
encodeURIComponent(
|
|
19
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" fill="#2f2943"/><circle cx="32" cy="26" r="12" fill="#ab93ed"/><rect x="12" y="42" width="40" height="30" rx="15" fill="#ab93ed"/></svg>',
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
/** An image, or the name's initial on the accent. Size it with className. */
|
|
23
|
+
export default function Default() {
|
|
24
|
+
return (
|
|
25
|
+
<div className="flex items-center gap-4">
|
|
26
|
+
<Avatar name="rin@example.com" />
|
|
27
|
+
<Avatar name="Sam" className="size-12 text-lg" />
|
|
28
|
+
<Avatar name="Alex" src={PHOTO} alt="Alex" className="size-12" />
|
|
29
|
+
<div className="flex items-center gap-2 text-sm">
|
|
30
|
+
<Avatar name="Priya" className="size-7 text-xs" />
|
|
31
|
+
Priya
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Upload
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { Avatar, Button, cropImageToSquare, ImageCropError } from '@rinzai/zen';
|
|
42
|
+
import { useRef, useState } from 'react';
|
|
43
|
+
|
|
44
|
+
/** cropImageToSquare centres, crops and shrinks a chosen image in the browser to a small data URL. */
|
|
45
|
+
export default function Upload() {
|
|
46
|
+
const [src, setSrc] = useState<string | null>(null);
|
|
47
|
+
const [error, setError] = useState<string | null>(null);
|
|
48
|
+
const input = useRef<HTMLInputElement>(null);
|
|
49
|
+
return (
|
|
50
|
+
<div className="flex items-center gap-4">
|
|
51
|
+
<Avatar name="You" src={src} className="size-16 text-xl" />
|
|
52
|
+
<div className="flex flex-col gap-1">
|
|
53
|
+
<Button variant="outline" size="sm" onClick={() => input.current?.click()}>
|
|
54
|
+
Choose a picture
|
|
55
|
+
</Button>
|
|
56
|
+
{error && <span className="text-destructive text-xs">{error}</span>}
|
|
57
|
+
</div>
|
|
58
|
+
<input
|
|
59
|
+
ref={input}
|
|
60
|
+
type="file"
|
|
61
|
+
accept="image/*"
|
|
62
|
+
hidden
|
|
63
|
+
onChange={async (e) => {
|
|
64
|
+
const file = e.target.files?.[0];
|
|
65
|
+
if (!file) return;
|
|
66
|
+
try {
|
|
67
|
+
setSrc(await cropImageToSquare(file, 160));
|
|
68
|
+
setError(null);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
setError(err instanceof ImageCropError ? err.message : 'Something went wrong.');
|
|
71
|
+
}
|
|
72
|
+
}}
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## API reference
|
|
80
|
+
|
|
81
|
+
### Avatar
|
|
82
|
+
|
|
83
|
+
Round profile picture; without an image, the name's initial on the glowing
|
|
84
|
+
accent. Decorative (hidden from screen readers) unless you give it `alt`.
|
|
85
|
+
|
|
86
|
+
| Prop | Type | Default | Description |
|
|
87
|
+
| --- | --- | --- | --- |
|
|
88
|
+
| `name` (required) | `string` | | Name or email, for the initial. |
|
|
89
|
+
| `alt` | `string` | | Accessible name; leave out when the name is shown next to the avatar. |
|
|
90
|
+
| `className` | `string` | | Size and more, e.g. "size-12 text-lg" (default size-9). |
|
|
91
|
+
| `src` | `string \| null` | | Image URL (or data URL); without it, or if it fails to load, the initial is shown. |
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Backdrop
|
|
2
|
+
|
|
3
|
+
The page background: aurora glows over contours or dots, and a light that follows the pointer and catches card edges, or drifts round them by itself on pages with no pointer.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Backdrop } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Contours
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Backdrop, Button, Card, CardHeader, CardTitle } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
import topo from './topo.svg';
|
|
17
|
+
|
|
18
|
+
export default function Contours() {
|
|
19
|
+
return (
|
|
20
|
+
// Backdrop is fixed to the viewport; the transform keeps it inside this frame for the demo.
|
|
21
|
+
<div className="relative h-96 w-full [transform:translateZ(0)] overflow-hidden rounded-xl">
|
|
22
|
+
<Backdrop topoSrc={topo} />
|
|
23
|
+
<div className="flex h-full items-center justify-center p-8">
|
|
24
|
+
<Card className="w-72">
|
|
25
|
+
<CardHeader>
|
|
26
|
+
<CardTitle>Move the pointer</CardTitle>
|
|
27
|
+
</CardHeader>
|
|
28
|
+
<p className="text-muted-foreground mt-0! mb-4 text-sm">
|
|
29
|
+
Card edges and the contour lines light up near it.
|
|
30
|
+
</p>
|
|
31
|
+
<Button variant="outline">Outline buttons too</Button>
|
|
32
|
+
</Card>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Dots
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { Backdrop, Stat } from '@rinzai/zen';
|
|
43
|
+
|
|
44
|
+
export default function Dots() {
|
|
45
|
+
return (
|
|
46
|
+
// Backdrop is fixed to the viewport; the transform keeps it inside this frame for the demo.
|
|
47
|
+
<div className="relative h-96 w-full [transform:translateZ(0)] overflow-hidden rounded-xl">
|
|
48
|
+
<Backdrop pattern="dots" />
|
|
49
|
+
<div className="flex h-full items-center justify-center p-8">
|
|
50
|
+
<Stat label="Net this month" value="$3,330" tone="positive" className="w-60" />
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Ambient
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { Card, CardTitle, Input, Stat } from '@rinzai/zen';
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* For a page that never gets a pointer, such as an OBS browser source: `data-zen-glow="ambient"`
|
|
64
|
+
* sends the edge light slowly round each border by itself. Put it on `<html>` for the whole page, or
|
|
65
|
+
* on any element for what's inside it. `data-zen-page="transparent"` on `<html>` (with the base
|
|
66
|
+
* stylesheet) paints nothing behind the content, so the stream shows through.
|
|
67
|
+
*/
|
|
68
|
+
export default function Ambient() {
|
|
69
|
+
return (
|
|
70
|
+
<div data-zen-glow="ambient" className="flex flex-wrap items-start gap-4 p-4">
|
|
71
|
+
<Stat label="Now playing" value="Lo-fi beats" className="w-56" />
|
|
72
|
+
<Card className="w-64">
|
|
73
|
+
<CardTitle className="mb-3">Chat</CardTitle>
|
|
74
|
+
<Input placeholder="Say something" aria-label="Message" />
|
|
75
|
+
</Card>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## API reference
|
|
82
|
+
|
|
83
|
+
### Backdrop
|
|
84
|
+
|
|
85
|
+
Fixed page background: slow aurora glows over a faint pattern (topographic
|
|
86
|
+
contours or a dot grid), with a light that follows the pointer. It also
|
|
87
|
+
lights `.glow-edge` and `.glow-border` borders (cards, outline buttons,
|
|
88
|
+
dialogs, fields) near the pointer. Render once, behind everything.
|
|
89
|
+
|
|
90
|
+
| Prop | Type | Default | Description |
|
|
91
|
+
| --- | --- | --- | --- |
|
|
92
|
+
| `className` | `string` | | Classes for the backdrop layer (e.g. a view-transition-name). |
|
|
93
|
+
| `pattern` | `"contours" \| "dots"` | | Background pattern. Defaults to contours when `topoSrc` is given, dots otherwise. |
|
|
94
|
+
| `topoSrc` | `string` | | URL of a contour-line image (strokes on transparent), used as a mask. |
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Badge
|
|
2
|
+
|
|
3
|
+
A small label for status, counts and tags.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Badge } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Variants
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Badge } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
export default function Variants() {
|
|
17
|
+
return (
|
|
18
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
19
|
+
<Badge>New</Badge>
|
|
20
|
+
<Badge variant="secondary">Draft</Badge>
|
|
21
|
+
<Badge tone="negative">Overdue</Badge>
|
|
22
|
+
<Badge variant="outline">Archived</Badge>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## API reference
|
|
29
|
+
|
|
30
|
+
### Badge
|
|
31
|
+
|
|
32
|
+
A small label for status, counts and tags; `tone="negative"` for bad news.
|
|
33
|
+
|
|
34
|
+
| Prop | Type | Default | Description |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| `tone` | `"default" \| "negative" \| null` | | |
|
|
37
|
+
| `variant` | `"default" \| "outline" \| "secondary" \| null` | | |
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Breadcrumbs
|
|
2
|
+
|
|
3
|
+
Where a page sits: its parents as links, then the page itself. Works with any router.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Breadcrumbs, Breadcrumb } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Breadcrumb, Breadcrumbs } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
/** Parents as links (with a router, Breadcrumb asChild around its Link), then the current page. */
|
|
17
|
+
export default function Default() {
|
|
18
|
+
return (
|
|
19
|
+
<Breadcrumbs>
|
|
20
|
+
<Breadcrumb href="#">Budgets</Breadcrumb>
|
|
21
|
+
<Breadcrumb href="#">2026</Breadcrumb>
|
|
22
|
+
<Breadcrumb current>September</Breadcrumb>
|
|
23
|
+
</Breadcrumbs>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## API reference
|
|
29
|
+
|
|
30
|
+
### Breadcrumbs
|
|
31
|
+
|
|
32
|
+
Where a page sits: its parents as links, then the page itself. Give the last crumb `current`.
|
|
33
|
+
|
|
34
|
+
No props of its own; it accepts the props of the element it renders.
|
|
35
|
+
|
|
36
|
+
### Breadcrumb
|
|
37
|
+
|
|
38
|
+
One crumb: a link, or with `current`, the page you're on (not a link). With
|
|
39
|
+
`asChild`, the styles go onto your own link element (a router's Link).
|
|
40
|
+
|
|
41
|
+
| Prop | Type | Default | Description |
|
|
42
|
+
| --- | --- | --- | --- |
|
|
43
|
+
| `asChild` | `boolean` | | Style the single child element (e.g. a router link) instead of rendering an <a>. |
|
|
44
|
+
| `current` | `boolean` | | The page you're on: the last crumb. |
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Button Group
|
|
2
|
+
|
|
3
|
+
Buttons joined into one, rounded only at the ends: a split button, or Previous, Today and Next.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { ButtonGroup } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Button, ButtonGroup } from '@rinzai/zen';
|
|
15
|
+
// Icons from Heroicons (https://heroicons.com): ChevronLeft (chevron-left), ChevronRight (chevron-right).
|
|
16
|
+
|
|
17
|
+
/** Previous, today and next, joined: one control for moving through months. */
|
|
18
|
+
export default function Default() {
|
|
19
|
+
return (
|
|
20
|
+
<ButtonGroup aria-label="Month">
|
|
21
|
+
<Button variant="outline" size="icon" aria-label="Previous month">
|
|
22
|
+
<ChevronLeft className="size-4 rtl:-scale-x-100" />
|
|
23
|
+
</Button>
|
|
24
|
+
<Button variant="outline">This month</Button>
|
|
25
|
+
<Button variant="outline" size="icon" aria-label="Next month">
|
|
26
|
+
<ChevronRight className="size-4 rtl:-scale-x-100" />
|
|
27
|
+
</Button>
|
|
28
|
+
</ButtonGroup>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Split
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { Button, ButtonGroup, buttonVariants, Menu, MenuContent, MenuItem, MenuTrigger, useToast } from '@rinzai/zen';
|
|
37
|
+
// Icons from Heroicons (https://heroicons.com): ChevronDown (chevron-down).
|
|
38
|
+
|
|
39
|
+
/** A split button: the usual action, and a menu of the others. */
|
|
40
|
+
export default function Split() {
|
|
41
|
+
const toast = useToast();
|
|
42
|
+
return (
|
|
43
|
+
<ButtonGroup>
|
|
44
|
+
<Button variant="outline" onClick={() => toast('Exported as CSV', { tone: 'positive' })}>
|
|
45
|
+
Export CSV
|
|
46
|
+
</Button>
|
|
47
|
+
<Menu>
|
|
48
|
+
<MenuTrigger
|
|
49
|
+
aria-label="More export formats"
|
|
50
|
+
className={buttonVariants({ variant: 'outline', size: 'icon' })}
|
|
51
|
+
>
|
|
52
|
+
<ChevronDown className="size-4" />
|
|
53
|
+
</MenuTrigger>
|
|
54
|
+
<MenuContent align="end">
|
|
55
|
+
<MenuItem onSelect={() => toast('Exported as Excel', { tone: 'positive' })}>Excel</MenuItem>
|
|
56
|
+
<MenuItem onSelect={() => toast('Exported as PDF', { tone: 'positive' })}>PDF</MenuItem>
|
|
57
|
+
</MenuContent>
|
|
58
|
+
</Menu>
|
|
59
|
+
</ButtonGroup>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## API reference
|
|
65
|
+
|
|
66
|
+
### ButtonGroup
|
|
67
|
+
|
|
68
|
+
Buttons joined into one: shared edges, rounded only at the ends, e.g. a
|
|
69
|
+
split button (an action and a menu of others) or Previous / Today / Next.
|
|
70
|
+
Best with `outline` buttons, whose hairlines meet. Upright with
|
|
71
|
+
`orientation="vertical"`. Give it an aria-label when the buttons' names
|
|
72
|
+
don't say what they're for together.
|
|
73
|
+
|
|
74
|
+
| Prop | Type | Default | Description |
|
|
75
|
+
| --- | --- | --- | --- |
|
|
76
|
+
| `orientation` | `"horizontal" \| "vertical"` | `horizontal` | |
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Button
|
|
2
|
+
|
|
3
|
+
Actions, from the one glowing primary action to quiet ghost buttons.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Button } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Variants
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Button } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
export default function Variants() {
|
|
17
|
+
return (
|
|
18
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
19
|
+
<Button>Save changes</Button>
|
|
20
|
+
<Button variant="secondary">Export</Button>
|
|
21
|
+
<Button variant="outline">Cancel</Button>
|
|
22
|
+
<Button variant="ghost">Skip</Button>
|
|
23
|
+
<Button tone="negative">Delete</Button>
|
|
24
|
+
<Button variant="link">Learn more</Button>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Sizes
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { Button } from '@rinzai/zen';
|
|
34
|
+
|
|
35
|
+
export default function Sizes() {
|
|
36
|
+
return (
|
|
37
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
38
|
+
<Button size="sm">Small</Button>
|
|
39
|
+
<Button>Default</Button>
|
|
40
|
+
<Button size="lg">Large</Button>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Tones
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { Button } from '@rinzai/zen';
|
|
50
|
+
// Icons from Heroicons (https://heroicons.com): XMark (x-mark).
|
|
51
|
+
|
|
52
|
+
/** A link sits in the text and truncates; `tone` quiets it or makes it red. An icon button with a destructive tone turns red only when pointed at. */
|
|
53
|
+
export default function Tones() {
|
|
54
|
+
return (
|
|
55
|
+
<div className="flex flex-col gap-3 text-sm">
|
|
56
|
+
<p>
|
|
57
|
+
Saved. <Button variant="link">Undo</Button> or{' '}
|
|
58
|
+
<Button variant="link" tone="muted">
|
|
59
|
+
see history
|
|
60
|
+
</Button>
|
|
61
|
+
</p>
|
|
62
|
+
<div className="flex items-center gap-3">
|
|
63
|
+
<Button variant="link" tone="negative">
|
|
64
|
+
Delete account
|
|
65
|
+
</Button>
|
|
66
|
+
<Button variant="ghost" size="icon" tone="negative" aria-label="Remove">
|
|
67
|
+
<XMark className="size-4" />
|
|
68
|
+
</Button>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Loading
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { Button } from '@rinzai/zen';
|
|
79
|
+
import { useState } from 'react';
|
|
80
|
+
|
|
81
|
+
export default function Loading() {
|
|
82
|
+
const [saving, setSaving] = useState(false);
|
|
83
|
+
return (
|
|
84
|
+
<Button
|
|
85
|
+
loading={saving}
|
|
86
|
+
onClick={() => {
|
|
87
|
+
setSaving(true);
|
|
88
|
+
setTimeout(() => setSaving(false), 1500);
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
{saving ? 'Saving…' : 'Save'}
|
|
92
|
+
</Button>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### As link
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import { Button } from '@rinzai/zen';
|
|
101
|
+
|
|
102
|
+
/** asChild puts the button's styles on your own element, here a link (a router's Link works the same way). */
|
|
103
|
+
export default function AsLink() {
|
|
104
|
+
return (
|
|
105
|
+
<div className="flex gap-3">
|
|
106
|
+
<Button asChild>
|
|
107
|
+
<a href="#get-started">Get started</a>
|
|
108
|
+
</Button>
|
|
109
|
+
<Button asChild variant="outline">
|
|
110
|
+
<a href="#docs">Read the docs</a>
|
|
111
|
+
</Button>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## API reference
|
|
118
|
+
|
|
119
|
+
### Button
|
|
120
|
+
|
|
121
|
+
A button in one of Zen's styles. With `asChild`, the styles go onto your own
|
|
122
|
+
single child element instead, such as a router's Link (`loading` and `type`
|
|
123
|
+
then don't apply).
|
|
124
|
+
|
|
125
|
+
| Prop | Type | Default | Description |
|
|
126
|
+
| --- | --- | --- | --- |
|
|
127
|
+
| `asChild` | `boolean` | `false` | Put the button's styles on the single child element (e.g. a link) instead of a <button>. |
|
|
128
|
+
| `loading` | `boolean` | `false` | Shows a spinner and disables the button while an action runs. |
|
|
129
|
+
| `size` | `"default" \| "icon-lg" \| "icon-sm" \| "icon-xs" \| "icon" \| "lg" \| "sm" \| null` | | |
|
|
130
|
+
| `tone` | `"default" \| "inherit" \| "muted" \| "negative" \| null` | | |
|
|
131
|
+
| `variant` | `"default" \| "ghost" \| "link" \| "outline" \| "secondary" \| null` | | |
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Calendar Heatmap
|
|
2
|
+
|
|
3
|
+
A month of days shaded by their value, like spending per day.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { CalendarHeatmap } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { CalendarHeatmap, type DateString, formatDate, formatMoney } from '@rinzai/zen';
|
|
15
|
+
import { useState } from 'react';
|
|
16
|
+
|
|
17
|
+
const SPENT: Partial<Record<DateString, number>> = {
|
|
18
|
+
'2026-09-01': 185000,
|
|
19
|
+
'2026-09-03': 3840,
|
|
20
|
+
'2026-09-05': 2500,
|
|
21
|
+
'2026-09-06': 7800,
|
|
22
|
+
'2026-09-08': 1460,
|
|
23
|
+
'2026-09-12': 16450,
|
|
24
|
+
'2026-09-13': 9150,
|
|
25
|
+
'2026-09-15': 4200,
|
|
26
|
+
'2026-09-19': 3180,
|
|
27
|
+
'2026-09-20': 41200,
|
|
28
|
+
'2026-09-22': 1199,
|
|
29
|
+
'2026-09-26': 6420,
|
|
30
|
+
'2026-09-27': 2210,
|
|
31
|
+
};
|
|
32
|
+
const money = (cents: number) => formatMoney(cents, 'USD', 'en-US', { showDecimals: false });
|
|
33
|
+
|
|
34
|
+
/** What was spent each day of a month: pick a day to see it. */
|
|
35
|
+
export default function Default() {
|
|
36
|
+
const [day, setDay] = useState<DateString | null>('2026-09-12');
|
|
37
|
+
return (
|
|
38
|
+
<div className="flex w-full max-w-xs flex-col gap-3">
|
|
39
|
+
<CalendarHeatmap
|
|
40
|
+
month="2026-09"
|
|
41
|
+
values={SPENT}
|
|
42
|
+
locale="en-US"
|
|
43
|
+
format={money}
|
|
44
|
+
onSelectedChange={setDay}
|
|
45
|
+
selected={day}
|
|
46
|
+
/>
|
|
47
|
+
<p className="text-muted-foreground mt-0! text-sm">
|
|
48
|
+
{day ? `${formatDate(day, 'en-US', 'long')}: ${money(SPENT[day] ?? 0)} spent` : 'Pick a day'}
|
|
49
|
+
</p>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## API reference
|
|
56
|
+
|
|
57
|
+
### CalendarHeatmap
|
|
58
|
+
|
|
59
|
+
A month as a grid of days, each shaded by its value (e.g. what was spent
|
|
60
|
+
that day): busy days stand out at a glance. Hover a day for its figure;
|
|
61
|
+
with `onSelectedChange`, days are buttons.
|
|
62
|
+
|
|
63
|
+
| Prop | Type | Default | Description |
|
|
64
|
+
| --- | --- | --- | --- |
|
|
65
|
+
| `month` (required) | `string` | | |
|
|
66
|
+
| `values` (required) | `Partial<Record<string, number>>` | | Each day's value, by date ("2026-09-03"); days missing have none. |
|
|
67
|
+
| `className` | `string` | | |
|
|
68
|
+
| `format` | `((value: number) => string)` | | Turns a value into text for its label, e.g. as money. |
|
|
69
|
+
| `label` | `string` | | Accessible name (default: the month, e.g. "September 2026"). |
|
|
70
|
+
| `locale` | `string` | | |
|
|
71
|
+
| `onSelectedChange` | `((day: string) => void)` | | Makes the days buttons. |
|
|
72
|
+
| `selected` | `string \| null` | | |
|
|
73
|
+
| `weekStartsOn` | `number` | | 0 for Sunday … 6 for Saturday (default: the locale's). |
|