@rinzai/zen 0.14.0 → 0.16.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.
Files changed (225) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/README.md +16 -1
  3. package/dist/animated-number/index.js +14 -14
  4. package/dist/breadcrumbs/index.js +14 -14
  5. package/dist/calendar/index.js +29 -29
  6. package/dist/calendar-heatmap/index.js +12 -12
  7. package/dist/canvas/index.js +19 -19
  8. package/dist/carousel/index.d.ts.map +1 -1
  9. package/dist/carousel/index.js +29 -29
  10. package/dist/chart/index.js +7 -7
  11. package/dist/code-block/index.js +5 -5
  12. package/dist/collapse/index.js +6 -6
  13. package/dist/color-picker/index.js +10 -10
  14. package/dist/combobox/index.js +61 -61
  15. package/dist/command-palette/index.js +5 -5
  16. package/dist/confirm-dialog/index.js +4 -4
  17. package/dist/date-picker/index.js +15 -15
  18. package/dist/dialog/index.d.ts.map +1 -1
  19. package/dist/dialog/index.js +45 -65
  20. package/dist/docs/README.md +205 -0
  21. package/dist/docs/accordion.md +122 -0
  22. package/dist/docs/alert.md +69 -0
  23. package/dist/docs/animated-number.md +105 -0
  24. package/dist/docs/avatar.md +91 -0
  25. package/dist/docs/backdrop.md +94 -0
  26. package/dist/docs/badge.md +37 -0
  27. package/dist/docs/breadcrumbs.md +44 -0
  28. package/dist/docs/button-group.md +76 -0
  29. package/dist/docs/button.md +131 -0
  30. package/dist/docs/calendar-heatmap.md +73 -0
  31. package/dist/docs/calendar.md +101 -0
  32. package/dist/docs/canvas.md +584 -0
  33. package/dist/docs/card.md +180 -0
  34. package/dist/docs/carousel.md +162 -0
  35. package/dist/docs/chart.md +325 -0
  36. package/dist/docs/checkbox.md +88 -0
  37. package/dist/docs/chip.md +52 -0
  38. package/dist/docs/code-block.md +116 -0
  39. package/dist/docs/code-input.md +52 -0
  40. package/dist/docs/collapse.md +46 -0
  41. package/dist/docs/color-picker.md +145 -0
  42. package/dist/docs/combobox.md +208 -0
  43. package/dist/docs/command-palette.md +65 -0
  44. package/dist/docs/date-picker.md +167 -0
  45. package/dist/docs/dialog.md +271 -0
  46. package/dist/docs/disclosure.md +88 -0
  47. package/dist/docs/editable-cell.md +90 -0
  48. package/dist/docs/empty-state.md +46 -0
  49. package/dist/docs/field.md +82 -0
  50. package/dist/docs/file-drop.md +44 -0
  51. package/dist/docs/filter-bar.md +66 -0
  52. package/dist/docs/header.md +51 -0
  53. package/dist/docs/hover-card.md +110 -0
  54. package/dist/docs/icon-picker.md +65 -0
  55. package/dist/docs/input-group.md +89 -0
  56. package/dist/docs/input.md +33 -0
  57. package/dist/docs/inset.md +57 -0
  58. package/dist/docs/kbd.md +34 -0
  59. package/dist/docs/keypad.md +51 -0
  60. package/dist/docs/load-more.md +66 -0
  61. package/dist/docs/locale-provider.md +99 -0
  62. package/dist/docs/marquee.md +86 -0
  63. package/dist/docs/menu.md +456 -0
  64. package/dist/docs/meter.md +102 -0
  65. package/dist/docs/money-input.md +169 -0
  66. package/dist/docs/month-picker.md +80 -0
  67. package/dist/docs/navigation-menu.md +157 -0
  68. package/dist/docs/number-field.md +129 -0
  69. package/dist/docs/page-header.md +44 -0
  70. package/dist/docs/page-transition.md +93 -0
  71. package/dist/docs/pagination.md +42 -0
  72. package/dist/docs/pills.md +85 -0
  73. package/dist/docs/player-bar.md +158 -0
  74. package/dist/docs/popover.md +200 -0
  75. package/dist/docs/progress-ring.md +70 -0
  76. package/dist/docs/radio-cards.md +82 -0
  77. package/dist/docs/radio-group.md +86 -0
  78. package/dist/docs/range-slider.md +64 -0
  79. package/dist/docs/repeat-picker.md +44 -0
  80. package/dist/docs/resizable.md +107 -0
  81. package/dist/docs/seek-bar.md +97 -0
  82. package/dist/docs/segmented.md +89 -0
  83. package/dist/docs/select.md +148 -0
  84. package/dist/docs/separator.md +76 -0
  85. package/dist/docs/side-nav.md +75 -0
  86. package/dist/docs/skeleton.md +89 -0
  87. package/dist/docs/slider.md +87 -0
  88. package/dist/docs/sortable.md +244 -0
  89. package/dist/docs/sparkline.md +56 -0
  90. package/dist/docs/spinner.md +69 -0
  91. package/dist/docs/status-pill.md +81 -0
  92. package/dist/docs/stepper.md +59 -0
  93. package/dist/docs/tab-bar.md +78 -0
  94. package/dist/docs/table-of-contents.md +43 -0
  95. package/dist/docs/table.md +714 -0
  96. package/dist/docs/tabs.md +140 -0
  97. package/dist/docs/tag-input.md +45 -0
  98. package/dist/docs/textarea.md +25 -0
  99. package/dist/docs/theme-toggle.md +85 -0
  100. package/dist/docs/theming.md +193 -0
  101. package/dist/docs/time-field.md +71 -0
  102. package/dist/docs/timeline.md +50 -0
  103. package/dist/docs/toast.md +53 -0
  104. package/dist/docs/toggle-group.md +83 -0
  105. package/dist/docs/toggle.md +38 -0
  106. package/dist/docs/toolbar.md +97 -0
  107. package/dist/docs/tooltip.md +102 -0
  108. package/dist/docs/tree.md +295 -0
  109. package/dist/docs/trend.md +50 -0
  110. package/dist/filter-bar/index.js +6 -6
  111. package/dist/header/index.js +6 -6
  112. package/dist/icon-picker/index.js +5 -5
  113. package/dist/icons/arrow-path.d.ts +3 -0
  114. package/dist/icons/arrow-path.d.ts.map +1 -0
  115. package/dist/icons/arrows-right-left.d.ts +3 -0
  116. package/dist/icons/arrows-right-left.d.ts.map +1 -0
  117. package/dist/icons/backward.d.ts +3 -0
  118. package/dist/icons/backward.d.ts.map +1 -0
  119. package/dist/icons/forward.d.ts +3 -0
  120. package/dist/icons/forward.d.ts.map +1 -0
  121. package/dist/icons/heart-solid.d.ts +3 -0
  122. package/dist/icons/heart-solid.d.ts.map +1 -0
  123. package/dist/icons/heart.d.ts +3 -0
  124. package/dist/icons/heart.d.ts.map +1 -0
  125. package/dist/icons/musical-note.d.ts +3 -0
  126. package/dist/icons/musical-note.d.ts.map +1 -0
  127. package/dist/icons/pause.d.ts +3 -0
  128. package/dist/icons/pause.d.ts.map +1 -0
  129. package/dist/icons/play.d.ts +3 -0
  130. package/dist/icons/play.d.ts.map +1 -0
  131. package/dist/icons/queue-list.d.ts +3 -0
  132. package/dist/icons/queue-list.d.ts.map +1 -0
  133. package/dist/icons/speaker-wave.d.ts +3 -0
  134. package/dist/icons/speaker-wave.d.ts.map +1 -0
  135. package/dist/icons/speaker-x-mark.d.ts +3 -0
  136. package/dist/icons/speaker-x-mark.d.ts.map +1 -0
  137. package/dist/index.d.ts +7 -1
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/keypad/index.js +5 -5
  140. package/dist/lib.js +86 -80
  141. package/dist/load-more/index.js +5 -5
  142. package/dist/marquee/index.d.ts +15 -0
  143. package/dist/marquee/index.d.ts.map +1 -0
  144. package/dist/marquee/index.js +90 -0
  145. package/dist/menu/index.d.ts +7 -1
  146. package/dist/menu/index.d.ts.map +1 -1
  147. package/dist/menu/index.js +222 -178
  148. package/dist/money-input/index.js +54 -54
  149. package/dist/month-picker/index.js +11 -11
  150. package/dist/number-field/index.js +5 -5
  151. package/dist/pagination/index.js +12 -12
  152. package/dist/player-bar/index.d.ts +11 -0
  153. package/dist/player-bar/index.d.ts.map +1 -0
  154. package/dist/player-bar/index.js +17 -0
  155. package/dist/radio-cards/index.d.ts +2 -1
  156. package/dist/radio-cards/index.d.ts.map +1 -1
  157. package/dist/radio-cards/index.js +3 -3
  158. package/dist/range-slider/index.js +11 -11
  159. package/dist/repeat-picker/index.js +5 -5
  160. package/dist/resizable/index.js +22 -22
  161. package/dist/seek-bar/index.d.ts +26 -0
  162. package/dist/seek-bar/index.d.ts.map +1 -0
  163. package/dist/seek-bar/index.js +121 -0
  164. package/dist/segmented/index.d.ts +3 -2
  165. package/dist/segmented/index.d.ts.map +1 -1
  166. package/dist/segmented/index.js +19 -18
  167. package/dist/select/index.js +21 -21
  168. package/dist/slider/index.d.ts +2 -1
  169. package/dist/slider/index.d.ts.map +1 -1
  170. package/dist/sortable/index.d.ts +13 -1
  171. package/dist/sortable/index.d.ts.map +1 -1
  172. package/dist/sortable/index.js +74 -55
  173. package/dist/sortable/shared.js +14 -14
  174. package/dist/sortable/tree.js +17 -17
  175. package/dist/sparkline/index.js +4 -4
  176. package/dist/stepper/index.js +13 -13
  177. package/dist/tab-bar/index.d.ts +4 -2
  178. package/dist/tab-bar/index.d.ts.map +1 -1
  179. package/dist/tab-bar/index.js +24 -19
  180. package/dist/table/index.js +26 -26
  181. package/dist/table/selection.js +9 -9
  182. package/dist/table-of-contents/index.js +11 -11
  183. package/dist/tag-input/index.js +5 -5
  184. package/dist/textarea/index.d.ts +6 -2
  185. package/dist/textarea/index.d.ts.map +1 -1
  186. package/dist/textarea/index.js +2 -1
  187. package/dist/theme-toggle/index.js +4 -4
  188. package/dist/theme.css +143 -0
  189. package/dist/time-field/index.js +21 -21
  190. package/dist/toast/index.d.ts.map +1 -1
  191. package/dist/toast/index.js +41 -41
  192. package/dist/tooltip/index.d.ts +9 -3
  193. package/dist/tooltip/index.d.ts.map +1 -1
  194. package/dist/tooltip/index.js +19 -16
  195. package/dist/tree/index.js +34 -34
  196. package/dist/trend/index.js +5 -5
  197. package/dist/utils/duration.d.ts +3 -0
  198. package/dist/utils/duration.d.ts.map +1 -0
  199. package/dist/utils/duration.js +7 -0
  200. package/dist/utils/messages.d.ts +3 -0
  201. package/dist/utils/messages.d.ts.map +1 -1
  202. package/dist/utils/messages.js +4 -1
  203. package/dist/utils/slot.d.ts +5 -2
  204. package/dist/utils/slot.d.ts.map +1 -1
  205. package/dist/utils/slot.js +16 -11
  206. package/dist/utils/swipe.d.ts +7 -0
  207. package/dist/utils/swipe.d.ts.map +1 -1
  208. package/dist/utils/swipe.js +45 -25
  209. package/dist/utils/useAnchoredPopup.d.ts +2 -1
  210. package/dist/utils/useAnchoredPopup.d.ts.map +1 -1
  211. package/dist/utils/useAnchoredPopup.js +10 -5
  212. package/dist/utils/useHeightVar.d.ts +7 -0
  213. package/dist/utils/useHeightVar.d.ts.map +1 -0
  214. package/dist/utils/useHeightVar.js +17 -0
  215. package/dist/utils/useMediaPlayer.d.ts +38 -0
  216. package/dist/utils/useMediaPlayer.d.ts.map +1 -0
  217. package/dist/utils/useMediaPlayer.js +91 -0
  218. package/dist/utils/useMediaSession.d.ts +24 -0
  219. package/dist/utils/useMediaSession.d.ts.map +1 -0
  220. package/dist/utils/useMediaSession.js +76 -0
  221. package/dist/utils/useVirtualList.d.ts +6 -1
  222. package/dist/utils/useVirtualList.d.ts.map +1 -1
  223. package/dist/utils/useVirtualList.js +12 -12
  224. package/dist/zen.css +1 -1
  225. package/package.json +4 -2
@@ -0,0 +1,69 @@
1
+ # Alert
2
+
3
+ A notice, warning or failure beside what it is about: a tinted box, or a plain line in a form or card.
4
+
5
+ ```tsx
6
+ import { Alert } from '@rinzai/zen';
7
+ ```
8
+
9
+ ## Examples
10
+
11
+ ### Default
12
+
13
+ ```tsx
14
+ import { Alert } from '@rinzai/zen';
15
+
16
+ /** A tinted box per tone; a `title` leads, and the text under it is quieter. */
17
+ export default function Default() {
18
+ return (
19
+ <div className="flex w-full max-w-md flex-col gap-3">
20
+ <Alert>Changes to this item apply from next month.</Alert>
21
+ <Alert tone="positive">Your data is up to date on every device.</Alert>
22
+ <Alert tone="negative" title="Your data will be deleted for good.">
23
+ Without your password or recovery key, nobody can open it. Starting over gives you a new, empty vault.
24
+ </Alert>
25
+ </div>
26
+ );
27
+ }
28
+ ```
29
+
30
+ ### Plain
31
+
32
+ ```tsx
33
+ import { Alert, Card, CardHeader, CardTitle, Spinner } from '@rinzai/zen';
34
+
35
+ /** `plain`: just the icon and text, for a line inside a card or a form. Any icon, or none. */
36
+ export default function Plain() {
37
+ return (
38
+ <Card className="flex w-full max-w-sm flex-col gap-3">
39
+ <CardHeader className="mb-0">
40
+ <CardTitle>Currency</CardTitle>
41
+ </CardHeader>
42
+ <Alert plain icon={<Spinner className="text-primary" />}>
43
+ Getting today’s rate…
44
+ </Alert>
45
+ <Alert plain tone="negative">
46
+ Couldn’t get today’s rate, so converting isn’t possible right now.
47
+ </Alert>
48
+ </Card>
49
+ );
50
+ }
51
+ ```
52
+
53
+ ## API reference
54
+
55
+ ### Alert
56
+
57
+ A message beside the content it's about: a notice, a warning, a failure.
58
+ A tinted box by default, or with `plain`, just the icon and text in the
59
+ tone's colour. With a `title`, the title takes the tone and the text under
60
+ it is quieter. Bad news is announced at once (role="alert"), anything else
61
+ politely (role="status"); pass `role` to change that, e.g. "note" for a
62
+ message that doesn't change.
63
+
64
+ | Prop | Type | Default | Description |
65
+ | --- | --- | --- | --- |
66
+ | `icon` | `ReactNode` | | Replaces the tone's icon (info, check, warning); `null` for none. |
67
+ | `plain` | `boolean` | `false` | Just the icon and text, no box: for a line inside a form or a card. |
68
+ | `title` | `ReactNode` | | A first line in the tone's colour; the text under it is quieter. |
69
+ | `tone` | `"default" \| "negative" \| "positive"` | `default` | `positive` in the accent colour, `negative` in red for bad news. |
@@ -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` | | |