@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,88 @@
1
+ # Checkbox
2
+
3
+ A native checkbox whose tick draws itself in, with a label, a description and a mixed state.
4
+
5
+ ```tsx
6
+ import { Checkbox } from '@rinzai/zen';
7
+ ```
8
+
9
+ ## Examples
10
+
11
+ ### Default
12
+
13
+ ```tsx
14
+ import { Checkbox } from '@rinzai/zen';
15
+ import { useState } from 'react';
16
+
17
+ /** With a label and a description; the tick draws itself in. */
18
+ export default function Default() {
19
+ const [remind, setRemind] = useState(true);
20
+ const [share, setShare] = useState(false);
21
+ return (
22
+ <div className="flex flex-col gap-4">
23
+ <Checkbox checked={remind} onCheckedChange={setRemind} description="A nudge on the 1st of each month.">
24
+ Remind me to log spending
25
+ </Checkbox>
26
+ <Checkbox checked={share} onCheckedChange={setShare}>
27
+ Share anonymous usage data
28
+ </Checkbox>
29
+ <Checkbox disabled>Sync to the cloud (coming soon)</Checkbox>
30
+ </div>
31
+ );
32
+ }
33
+ ```
34
+
35
+ ### Select all
36
+
37
+ ```tsx
38
+ import { Checkbox } from '@rinzai/zen';
39
+ import { useState } from 'react';
40
+
41
+ const ACCOUNTS = ['Salary account', 'Credit card', 'Savings'];
42
+
43
+ /** A "select all" that shows a dash while only some are checked. */
44
+ export default function SelectAll() {
45
+ const [picked, setPicked] = useState<string[]>(['Credit card']);
46
+ const all = picked.length === ACCOUNTS.length;
47
+ return (
48
+ <div className="flex flex-col gap-3">
49
+ <Checkbox
50
+ checked={all}
51
+ indeterminate={picked.length > 0 && !all}
52
+ onCheckedChange={() => setPicked(all ? [] : ACCOUNTS)}
53
+ className="font-medium"
54
+ >
55
+ All accounts
56
+ </Checkbox>
57
+ <div className="flex flex-col gap-3 pl-7">
58
+ {ACCOUNTS.map((a) => (
59
+ <Checkbox
60
+ key={a}
61
+ checked={picked.includes(a)}
62
+ onCheckedChange={(on) => setPicked((p) => (on ? [...p, a] : p.filter((x) => x !== a)))}
63
+ >
64
+ {a}
65
+ </Checkbox>
66
+ ))}
67
+ </div>
68
+ </div>
69
+ );
70
+ }
71
+ ```
72
+
73
+ ## API reference
74
+
75
+ ### Checkbox
76
+
77
+ A checkbox: a native one, styled, so forms, the keyboard and screen readers
78
+ work as usual. The tick draws itself in when checked; `indeterminate` shows a
79
+ dash (e.g. "some selected"). With children, they're its label (and
80
+ `description` a line under it); without, label it yourself or put it in a Field.
81
+
82
+ | Prop | Type | Default | Description |
83
+ | --- | --- | --- | --- |
84
+ | `children` | `ReactNode` | | The label. |
85
+ | `className` | `string` | | Classes for the label (or, without one, the box). |
86
+ | `description` | `ReactNode` | | A muted line under the label. |
87
+ | `indeterminate` | `boolean` | `false` | Shows a dash instead of the tick, e.g. when some of a group are checked. |
88
+ | `onCheckedChange` | `((checked: boolean) => void)` | | |
@@ -0,0 +1,52 @@
1
+ # Chip
2
+
3
+ Small pressable buttons for quick picks beside a field, like common tenures.
4
+
5
+ ```tsx
6
+ import { Chip } from '@rinzai/zen';
7
+ ```
8
+
9
+ ## Examples
10
+
11
+ ### Default
12
+
13
+ ```tsx
14
+ import { Chip, Input } from '@rinzai/zen';
15
+ import { useState } from 'react';
16
+
17
+ const YEARS = [5, 10, 15, 20, 30];
18
+
19
+ /** Quick picks beside a field: the chip matching the field's value shows pressed. */
20
+ export default function Default() {
21
+ const [years, setYears] = useState('20');
22
+ return (
23
+ <div className="flex flex-col gap-3">
24
+ <Input
25
+ aria-label="Tenure in years"
26
+ value={years}
27
+ onChange={(e) => setYears(e.target.value)}
28
+ className="w-24"
29
+ />
30
+ <div role="group" aria-label="Common tenures" className="flex flex-wrap gap-1.5">
31
+ {YEARS.map((y) => (
32
+ <Chip key={y} pressed={years === String(y)} onClick={() => setYears(String(y))}>
33
+ {y} yrs
34
+ </Chip>
35
+ ))}
36
+ </div>
37
+ </div>
38
+ );
39
+ }
40
+ ```
41
+
42
+ ## API reference
43
+
44
+ ### Chip
45
+
46
+ A small rounded button that can be pressed in, for quick picks beside a
47
+ field (e.g. common tenures): `pressed` marks the one matching the field.
48
+ For choosing exactly one of a few options, use Segmented.
49
+
50
+ | Prop | Type | Default | Description |
51
+ | --- | --- | --- | --- |
52
+ | `pressed` | `boolean` | | Shown pressed in (aria-pressed). |
@@ -0,0 +1,116 @@
1
+ # Code Block
2
+
3
+ Code with a copy button; bring your own highlighter for colours. The copy button runs on useCopyToClipboard, which you can use for copy buttons anywhere.
4
+
5
+ ```tsx
6
+ import { CodeBlock, useCopyToClipboard } from '@rinzai/zen';
7
+ ```
8
+
9
+ ## Examples
10
+
11
+ ### Highlighted
12
+
13
+ ```tsx
14
+ import { CodeBlock } from '@rinzai/zen';
15
+ import { highlight } from 'sugar-high';
16
+
17
+ const CODE = `import { Button } from '@rinzai/zen';
18
+
19
+ export default function Save() {
20
+ return <Button loading>Saving…</Button>;
21
+ }`;
22
+
23
+ /** Colours come from any highlighter that turns code into HTML; here sugar-high. Hover to copy. */
24
+ export default function Highlighted() {
25
+ return (
26
+ <CodeBlock
27
+ code={CODE}
28
+ language="typescript"
29
+ highlight={(code) => highlight(code)}
30
+ className="w-full max-w-xl"
31
+ />
32
+ );
33
+ }
34
+ ```
35
+
36
+ ### Plain
37
+
38
+ ```tsx
39
+ import { CodeBlock } from '@rinzai/zen';
40
+
41
+ /** Without a highlighter, the code shows as plain text. */
42
+ export default function Plain() {
43
+ return <CodeBlock code="pnpm add @rinzai/zen" language="shell" className="w-full max-w-xl" />;
44
+ }
45
+ ```
46
+
47
+ ### In card
48
+
49
+ ```tsx
50
+ import { Card, CardHeader, CardTitle, CodeBlock } from '@rinzai/zen';
51
+
52
+ /** Inside a card or panel that already has a surface, `variant="plain"` drops the block's own. */
53
+ export default function InCard() {
54
+ return (
55
+ <Card className="w-full max-w-xl overflow-hidden p-0!">
56
+ <CardHeader className="mb-0 px-5 pt-5">
57
+ <CardTitle>Install</CardTitle>
58
+ </CardHeader>
59
+ <CodeBlock variant="plain" code="pnpm add @rinzai/zen" language="shell" />
60
+ </Card>
61
+ );
62
+ }
63
+ ```
64
+
65
+ ### Copy button
66
+
67
+ ```tsx
68
+ import { Button, InputGroup, InputGroupAddon, InputGroupInput, useCopyToClipboard } from '@rinzai/zen';
69
+ import { useRef } from 'react';
70
+
71
+ const LINK = 'https://zen.rinzai.dev/components/code-block/';
72
+
73
+ /**
74
+ * A copy button of your own, with the hook CodeBlock copies with: `copy` resolves false when the
75
+ * browser won't copy, and here the link is then selected to copy by hand.
76
+ */
77
+ export default function CopyButton() {
78
+ const { copy, copied } = useCopyToClipboard();
79
+ const input = useRef<HTMLInputElement>(null);
80
+ return (
81
+ <InputGroup className="w-full max-w-sm pr-1">
82
+ <InputGroupInput ref={input} value={LINK} readOnly aria-label="Link" />
83
+ <InputGroupAddon>
84
+ <Button
85
+ variant="ghost"
86
+ size="sm"
87
+ className="h-7"
88
+ onClick={async () => {
89
+ if (await copy(LINK)) return;
90
+ input.current?.focus();
91
+ input.current?.select();
92
+ }}
93
+ >
94
+ {copied ? 'Copied' : 'Copy'}
95
+ </Button>
96
+ </InputGroupAddon>
97
+ </InputGroup>
98
+ );
99
+ }
100
+ ```
101
+
102
+ ## API reference
103
+
104
+ ### CodeBlock
105
+
106
+ Code on a glass panel whose edge catches the pointer light, with a copy
107
+ button. Give it a max height (className) and the code scrolls inside. Zen ships no highlighter: pass `highlight` (code → HTML string, e.g.
108
+ sugar-high's `highlight`) for colours.
109
+
110
+ | Prop | Type | Default | Description |
111
+ | --- | --- | --- | --- |
112
+ | `code` (required) | `string` | | |
113
+ | `copyable` | `boolean` | `true` | Show the copy button (on hover and focus). |
114
+ | `highlight` | `((code: string, language?: string) => string)` | | Turns the code into HTML with coloured tokens. Without it, plain text. |
115
+ | `language` | `string` | | Passed to `highlight`, and set as data-language. |
116
+ | `variant` | `"glass" \| "plain"` | `glass` | `glass` (default): its own lit glass panel. `plain`: no surface, for code inside a card or panel. |
@@ -0,0 +1,52 @@
1
+ # Code Input
2
+
3
+ A one-time code, a box per character, that types, pastes and autofills from a text message like any field.
4
+
5
+ ```tsx
6
+ import { CodeInput } from '@rinzai/zen';
7
+ ```
8
+
9
+ ## Examples
10
+
11
+ ### Default
12
+
13
+ ```tsx
14
+ import { CodeInput, Field } from '@rinzai/zen';
15
+ import { useState } from 'react';
16
+
17
+ /** A six-digit sign-in code, checked as soon as it's complete. */
18
+ export default function Default() {
19
+ const [code, setCode] = useState('');
20
+ const [checked, setChecked] = useState<string>();
21
+ return (
22
+ <Field label="Sign-in code" hint={checked ? `Checking ${checked}…` : 'We sent it to your phone.'}>
23
+ <CodeInput
24
+ value={code}
25
+ onValueChange={(next) => {
26
+ setCode(next);
27
+ setChecked(undefined);
28
+ }}
29
+ onComplete={setChecked}
30
+ />
31
+ </Field>
32
+ );
33
+ }
34
+ ```
35
+
36
+ ## API reference
37
+
38
+ ### CodeInput
39
+
40
+ A one-time code (sign-in, verification): a box per character. It's one real
41
+ field under the boxes, so typing, pasting, deleting and the phone's
42
+ autofill from a text message all work as in any field. `onComplete` gets the
43
+ code once every box is filled.
44
+
45
+ | Prop | Type | Default | Description |
46
+ | --- | --- | --- | --- |
47
+ | `allowed` | `RegExp` | `/\d/` | Which characters count (one at a time); digits by default. Others typed or pasted are dropped. |
48
+ | `defaultValue` | `string` | | |
49
+ | `length` | `number` | `6` | How many characters (boxes). |
50
+ | `onComplete` | `((code: string) => void)` | | Called with the code when the last box is filled. |
51
+ | `onValueChange` | `((value: string) => void)` | | |
52
+ | `value` | `string` | | |
@@ -0,0 +1,46 @@
1
+ # Collapse
2
+
3
+ Shows as many items as fit on one line, and the rest behind "+N".
4
+
5
+ ```tsx
6
+ import { Collapse } from '@rinzai/zen';
7
+ ```
8
+
9
+ ## Examples
10
+
11
+ ### Default
12
+
13
+ ```tsx
14
+ import { Badge, Collapse } from '@rinzai/zen';
15
+
16
+ const TAGS = ['Groceries', 'Rent', 'Transport', 'Dining out', 'Subscriptions', 'Travel', 'Gifts', 'Health', 'Books'];
17
+
18
+ /** Shows as many tags as fit on one line, and the rest behind "+N". Resize the window to see it adapt. */
19
+ export default function Default() {
20
+ return (
21
+ <Collapse items={TAGS} className="w-full max-w-md gap-1">
22
+ {(tag) => <Badge variant="secondary">{tag}</Badge>}
23
+ </Collapse>
24
+ );
25
+ }
26
+ ```
27
+
28
+ ## API reference
29
+
30
+ ### Collapse
31
+
32
+ A row showing as many items as fit on one line, and the rest behind a "+N"
33
+ button that opens them in a popover. It measures the items as rendered (any
34
+ content, font or style), and fits again whenever the row resizes. Its width
35
+ is the room it's given, not its items': size the row with className (e.g.
36
+ w-full, max-w-md, gap-1).
37
+
38
+ | Prop | Type | Default | Description |
39
+ | --- | --- | --- | --- |
40
+ | `children` (required) | `(item: string, index: number, itemData?: TData \| undefined) => ReactNode` | | Renders one item (in the row, or in the popover when it doesn't fit). |
41
+ | `items` (required) | `string[]` | | |
42
+ | `badgeClassName` | `string` | | Classes for the "+N" badge. |
43
+ | `badgeVariant` | `"default" \| "outline" \| "secondary" \| null` | `secondary` | |
44
+ | `className` | `string` | | Classes for the row: its width, and the gap between items. |
45
+ | `data` | `TData[]` | | Extra data per item, passed to `children` with it. |
46
+ | `moreLabel` | `string` | | Text after the count on the "+N" button, e.g. "tags". |
@@ -0,0 +1,145 @@
1
+ # Color Picker
2
+
3
+ Pick a colour by eye or by hex: a saturation and brightness area, hue and opacity, and your own swatches. With hexToOklch to give a colour to the theme engine.
4
+
5
+ ```tsx
6
+ import { ColorPicker, ColorPanel, hexToOklch, oklchToHex } from '@rinzai/zen';
7
+ ```
8
+
9
+ ## Examples
10
+
11
+ ### Default
12
+
13
+ ```tsx
14
+ import { ColorPicker, Field } from '@rinzai/zen';
15
+ import { useState } from 'react';
16
+
17
+ export default function Default() {
18
+ const [color, setColor] = useState('#7c5cff');
19
+ return (
20
+ <Field label="Title colour" className="w-64">
21
+ <ColorPicker value={color} onValueChange={setColor} />
22
+ </Field>
23
+ );
24
+ }
25
+ ```
26
+
27
+ ### Swatches
28
+
29
+ ```tsx
30
+ import { ColorPicker, Field } from '@rinzai/zen';
31
+ import { useState } from 'react';
32
+
33
+ const SWATCHES = [
34
+ { label: 'Violet', value: '#7c5cff' },
35
+ { label: 'Teal', value: '#2bb3a3' },
36
+ { label: 'Amber', value: '#f0a83a' },
37
+ { label: 'Rose', value: '#ef5a7a' },
38
+ { label: 'White', value: '#ffffff' },
39
+ { label: 'Black', value: '#000000' },
40
+ ];
41
+
42
+ /** With `alpha`, an opacity slider, and values like "#7c5cff99". Swatches are yours, such as your brand's colours. */
43
+ export default function Swatches() {
44
+ const [color, setColor] = useState('#2bb3a3cc');
45
+ return (
46
+ <Field label="Card background" className="w-64">
47
+ <ColorPicker value={color} onValueChange={setColor} alpha swatches={SWATCHES} />
48
+ </Field>
49
+ );
50
+ }
51
+ ```
52
+
53
+ ### Inline
54
+
55
+ ```tsx
56
+ import { Card, ColorPanel } from '@rinzai/zen';
57
+ import { useState } from 'react';
58
+
59
+ /** ColorPanel is the picker's panel on its own, for a sidebar or an inspector. `onValueCommit` fires once a change is made, the moment to save. */
60
+ export default function Inline() {
61
+ const [color, setColor] = useState('#ef5a7a');
62
+ const [saved, setSaved] = useState(color);
63
+ return (
64
+ <Card className="flex flex-col gap-3 p-4!">
65
+ <ColorPanel value={color} onValueChange={setColor} onValueCommit={setSaved} />
66
+ <p className="text-muted-foreground mt-0! text-xs">
67
+ Saved: <span className="text-foreground font-mono">{saved}</span>
68
+ </p>
69
+ </Card>
70
+ );
71
+ }
72
+ ```
73
+
74
+ ### Accent
75
+
76
+ ```tsx
77
+ import { applyTheme, Badge, Button, ColorPicker, customize, DEFAULT_THEME, Field, hexToOklch } from '@rinzai/zen';
78
+ import { useEffect, useRef, useState } from 'react';
79
+
80
+ /** `hexToOklch` gives a picked colour's hue to the theme engine, which keeps it readable in dark and light. */
81
+ export default function Accent() {
82
+ const [color, setColor] = useState('#2bb3a3');
83
+ const preview = useRef<HTMLDivElement>(null);
84
+ useEffect(() => {
85
+ const oklch = hexToOklch(color);
86
+ const root = document.documentElement;
87
+ const apply = () =>
88
+ oklch &&
89
+ preview.current &&
90
+ applyTheme(customize(DEFAULT_THEME, { hue: Math.round(oklch.h) }), {
91
+ appearance: root.classList.contains('light') ? 'light' : 'dark',
92
+ target: preview.current,
93
+ });
94
+ apply();
95
+ const watch = new MutationObserver(apply);
96
+ watch.observe(root, { attributes: true, attributeFilter: ['class'] });
97
+ return () => watch.disconnect();
98
+ }, [color]);
99
+ return (
100
+ <div className="flex flex-wrap items-end gap-6">
101
+ <Field label="Accent" className="w-56">
102
+ <ColorPicker value={color} onValueChange={setColor} />
103
+ </Field>
104
+ <div ref={preview} className="flex items-center gap-3">
105
+ <Button>Save</Button>
106
+ <Badge>New</Badge>
107
+ </div>
108
+ </div>
109
+ );
110
+ }
111
+ ```
112
+
113
+ ## API reference
114
+
115
+ ### ColorPicker
116
+
117
+ A field showing a colour and its hex that opens a ColorPanel: drag or use
118
+ the arrow keys in the area, slide the hue (and opacity, with `alpha`), type
119
+ a hex or pick a swatch. Works in a Field like any input.
120
+
121
+ | Prop | Type | Default | Description |
122
+ | --- | --- | --- | --- |
123
+ | `alpha` | `boolean` | | Offer opacity; values may then be "#rrggbbaa". |
124
+ | `defaultValue` | `string` | `#000000` | The first colour, when the picker keeps its own. Black by default. |
125
+ | `name` | `string` | | Form field name: the hex is submitted with the form. |
126
+ | `onValueChange` | `((value: string) => void)` | | Every change, while dragging too. |
127
+ | `onValueCommit` | `((value: string) => void)` | | Once a change is made: let go of the area or a slider, a key, a hex entered, a swatch picked. |
128
+ | `size` | `"default" \| "lg" \| "sm"` | `default` | Height, matching Button's: sm (32px), default (40px) or lg (44px). |
129
+ | `swatches` | `ColorSwatch[]` | | Colours to pick in one press, such as your theme's. |
130
+ | `value` | `string` | | "#rrggbb", or "#rrggbbaa" with `alpha`, when you keep it. |
131
+
132
+ ### ColorPanel
133
+
134
+ Everything ColorPicker opens: a saturation and brightness area, hue and
135
+ alpha sliders, the hex, and your swatches. Use it inline, or in a popup of
136
+ your own. Values are hex: "#rrggbb", or "#rrggbbaa" with `alpha`.
137
+
138
+ | Prop | Type | Default | Description |
139
+ | --- | --- | --- | --- |
140
+ | `alpha` | `boolean` | `false` | Offer opacity; values may then be "#rrggbbaa". |
141
+ | `defaultValue` | `string` | `#000000` | The first colour, when the picker keeps its own. Black by default. |
142
+ | `onValueChange` | `((value: string) => void)` | | Every change, while dragging too. |
143
+ | `onValueCommit` | `((value: string) => void)` | | Once a change is made: let go of the area or a slider, a key, a hex entered, a swatch picked. |
144
+ | `swatches` | `ColorSwatch[]` | | Colours to pick in one press, such as your theme's. |
145
+ | `value` | `string` | | "#rrggbb", or "#rrggbbaa" with `alpha`, when you keep it. |