@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.
Files changed (172) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/README.md +16 -1
  3. package/dist/carousel/index.d.ts.map +1 -1
  4. package/dist/carousel/index.js +5 -5
  5. package/dist/docs/README.md +205 -0
  6. package/dist/docs/accordion.md +122 -0
  7. package/dist/docs/alert.md +69 -0
  8. package/dist/docs/animated-number.md +105 -0
  9. package/dist/docs/avatar.md +91 -0
  10. package/dist/docs/backdrop.md +94 -0
  11. package/dist/docs/badge.md +37 -0
  12. package/dist/docs/breadcrumbs.md +44 -0
  13. package/dist/docs/button-group.md +76 -0
  14. package/dist/docs/button.md +131 -0
  15. package/dist/docs/calendar-heatmap.md +73 -0
  16. package/dist/docs/calendar.md +101 -0
  17. package/dist/docs/canvas.md +584 -0
  18. package/dist/docs/card.md +180 -0
  19. package/dist/docs/carousel.md +162 -0
  20. package/dist/docs/chart.md +325 -0
  21. package/dist/docs/checkbox.md +88 -0
  22. package/dist/docs/chip.md +52 -0
  23. package/dist/docs/code-block.md +116 -0
  24. package/dist/docs/code-input.md +52 -0
  25. package/dist/docs/collapse.md +46 -0
  26. package/dist/docs/color-picker.md +145 -0
  27. package/dist/docs/combobox.md +208 -0
  28. package/dist/docs/command-palette.md +65 -0
  29. package/dist/docs/date-picker.md +167 -0
  30. package/dist/docs/dialog.md +271 -0
  31. package/dist/docs/disclosure.md +88 -0
  32. package/dist/docs/editable-cell.md +90 -0
  33. package/dist/docs/empty-state.md +46 -0
  34. package/dist/docs/field.md +82 -0
  35. package/dist/docs/file-drop.md +44 -0
  36. package/dist/docs/filter-bar.md +66 -0
  37. package/dist/docs/header.md +51 -0
  38. package/dist/docs/hover-card.md +110 -0
  39. package/dist/docs/icon-picker.md +65 -0
  40. package/dist/docs/input-group.md +89 -0
  41. package/dist/docs/input.md +33 -0
  42. package/dist/docs/inset.md +57 -0
  43. package/dist/docs/kbd.md +34 -0
  44. package/dist/docs/keypad.md +51 -0
  45. package/dist/docs/load-more.md +66 -0
  46. package/dist/docs/locale-provider.md +99 -0
  47. package/dist/docs/marquee.md +86 -0
  48. package/dist/docs/menu.md +403 -0
  49. package/dist/docs/meter.md +102 -0
  50. package/dist/docs/money-input.md +169 -0
  51. package/dist/docs/month-picker.md +80 -0
  52. package/dist/docs/navigation-menu.md +157 -0
  53. package/dist/docs/number-field.md +129 -0
  54. package/dist/docs/page-header.md +44 -0
  55. package/dist/docs/page-transition.md +93 -0
  56. package/dist/docs/pagination.md +42 -0
  57. package/dist/docs/pills.md +85 -0
  58. package/dist/docs/player-bar.md +158 -0
  59. package/dist/docs/popover.md +200 -0
  60. package/dist/docs/progress-ring.md +70 -0
  61. package/dist/docs/radio-cards.md +82 -0
  62. package/dist/docs/radio-group.md +86 -0
  63. package/dist/docs/range-slider.md +64 -0
  64. package/dist/docs/repeat-picker.md +44 -0
  65. package/dist/docs/resizable.md +107 -0
  66. package/dist/docs/seek-bar.md +68 -0
  67. package/dist/docs/segmented.md +89 -0
  68. package/dist/docs/select.md +148 -0
  69. package/dist/docs/separator.md +76 -0
  70. package/dist/docs/side-nav.md +75 -0
  71. package/dist/docs/skeleton.md +89 -0
  72. package/dist/docs/slider.md +87 -0
  73. package/dist/docs/sortable.md +182 -0
  74. package/dist/docs/sparkline.md +56 -0
  75. package/dist/docs/spinner.md +69 -0
  76. package/dist/docs/status-pill.md +81 -0
  77. package/dist/docs/stepper.md +59 -0
  78. package/dist/docs/tab-bar.md +78 -0
  79. package/dist/docs/table-of-contents.md +43 -0
  80. package/dist/docs/table.md +714 -0
  81. package/dist/docs/tabs.md +140 -0
  82. package/dist/docs/tag-input.md +45 -0
  83. package/dist/docs/textarea.md +25 -0
  84. package/dist/docs/theme-toggle.md +85 -0
  85. package/dist/docs/theming.md +193 -0
  86. package/dist/docs/time-field.md +71 -0
  87. package/dist/docs/timeline.md +50 -0
  88. package/dist/docs/toast.md +53 -0
  89. package/dist/docs/toggle-group.md +83 -0
  90. package/dist/docs/toggle.md +38 -0
  91. package/dist/docs/toolbar.md +97 -0
  92. package/dist/docs/tooltip.md +60 -0
  93. package/dist/docs/tree.md +295 -0
  94. package/dist/docs/trend.md +50 -0
  95. package/dist/icons/arrow-path.d.ts +3 -0
  96. package/dist/icons/arrow-path.d.ts.map +1 -0
  97. package/dist/icons/arrows-right-left.d.ts +3 -0
  98. package/dist/icons/arrows-right-left.d.ts.map +1 -0
  99. package/dist/icons/backward.d.ts +3 -0
  100. package/dist/icons/backward.d.ts.map +1 -0
  101. package/dist/icons/forward.d.ts +3 -0
  102. package/dist/icons/forward.d.ts.map +1 -0
  103. package/dist/icons/heart-solid.d.ts +3 -0
  104. package/dist/icons/heart-solid.d.ts.map +1 -0
  105. package/dist/icons/heart.d.ts +3 -0
  106. package/dist/icons/heart.d.ts.map +1 -0
  107. package/dist/icons/musical-note.d.ts +3 -0
  108. package/dist/icons/musical-note.d.ts.map +1 -0
  109. package/dist/icons/pause.d.ts +3 -0
  110. package/dist/icons/pause.d.ts.map +1 -0
  111. package/dist/icons/play.d.ts +3 -0
  112. package/dist/icons/play.d.ts.map +1 -0
  113. package/dist/icons/queue-list.d.ts +3 -0
  114. package/dist/icons/queue-list.d.ts.map +1 -0
  115. package/dist/icons/speaker-wave.d.ts +3 -0
  116. package/dist/icons/speaker-wave.d.ts.map +1 -0
  117. package/dist/icons/speaker-x-mark.d.ts +3 -0
  118. package/dist/icons/speaker-x-mark.d.ts.map +1 -0
  119. package/dist/index.d.ts +8 -2
  120. package/dist/index.d.ts.map +1 -1
  121. package/dist/lib.js +80 -74
  122. package/dist/marquee/index.d.ts +15 -0
  123. package/dist/marquee/index.d.ts.map +1 -0
  124. package/dist/marquee/index.js +90 -0
  125. package/dist/menu/index.d.ts.map +1 -1
  126. package/dist/menu/index.js +7 -6
  127. package/dist/number-field/index.d.ts +18 -2
  128. package/dist/number-field/index.d.ts.map +1 -1
  129. package/dist/number-field/index.js +65 -51
  130. package/dist/player-bar/index.d.ts +11 -0
  131. package/dist/player-bar/index.d.ts.map +1 -0
  132. package/dist/player-bar/index.js +17 -0
  133. package/dist/radio-cards/index.d.ts +2 -1
  134. package/dist/radio-cards/index.d.ts.map +1 -1
  135. package/dist/radio-cards/index.js +3 -3
  136. package/dist/seek-bar/index.d.ts +24 -0
  137. package/dist/seek-bar/index.d.ts.map +1 -0
  138. package/dist/seek-bar/index.js +120 -0
  139. package/dist/segmented/index.d.ts +3 -2
  140. package/dist/segmented/index.d.ts.map +1 -1
  141. package/dist/segmented/index.js +19 -18
  142. package/dist/slider/index.d.ts +2 -1
  143. package/dist/slider/index.d.ts.map +1 -1
  144. package/dist/tab-bar/index.d.ts +4 -2
  145. package/dist/tab-bar/index.d.ts.map +1 -1
  146. package/dist/tab-bar/index.js +24 -19
  147. package/dist/textarea/index.d.ts +6 -2
  148. package/dist/textarea/index.d.ts.map +1 -1
  149. package/dist/textarea/index.js +2 -1
  150. package/dist/theme.css +40 -0
  151. package/dist/utils/duration.d.ts +3 -0
  152. package/dist/utils/duration.d.ts.map +1 -0
  153. package/dist/utils/duration.js +7 -0
  154. package/dist/utils/messages.d.ts +2 -0
  155. package/dist/utils/messages.d.ts.map +1 -1
  156. package/dist/utils/messages.js +3 -1
  157. package/dist/utils/slot.d.ts +5 -2
  158. package/dist/utils/slot.d.ts.map +1 -1
  159. package/dist/utils/slot.js +16 -11
  160. package/dist/utils/useAnchoredPopup.d.ts.map +1 -1
  161. package/dist/utils/useAnchoredPopup.js +3 -2
  162. package/dist/utils/useHeightVar.d.ts +7 -0
  163. package/dist/utils/useHeightVar.d.ts.map +1 -0
  164. package/dist/utils/useHeightVar.js +17 -0
  165. package/dist/utils/useMediaPlayer.d.ts +38 -0
  166. package/dist/utils/useMediaPlayer.d.ts.map +1 -0
  167. package/dist/utils/useMediaPlayer.js +91 -0
  168. package/dist/utils/useMediaSession.d.ts +24 -0
  169. package/dist/utils/useMediaSession.d.ts.map +1 -0
  170. package/dist/utils/useMediaSession.js +76 -0
  171. package/dist/zen.css +1 -1
  172. package/package.json +4 -2
@@ -0,0 +1,208 @@
1
+ # Combobox
2
+
3
+ Search and pick one or several items of any shape, composed from parts: field, panel, search, a virtualized list, empty and create rows.
4
+
5
+ ```tsx
6
+ import { Combobox, ComboboxTrigger, ComboboxSearch, ComboboxList, ComboboxItem, ComboboxEmpty, ComboboxCreate } from '@rinzai/zen';
7
+ ```
8
+
9
+ ## Examples
10
+
11
+ ### Accounts
12
+
13
+ ```tsx
14
+ import {
15
+ Combobox,
16
+ ComboboxEmpty,
17
+ ComboboxItem,
18
+ ComboboxList,
19
+ ComboboxPopup,
20
+ ComboboxSearch,
21
+ ComboboxTrigger,
22
+ } from '@rinzai/zen';
23
+ import { useState } from 'react';
24
+
25
+ interface Account {
26
+ id: string;
27
+ name: string;
28
+ number: string;
29
+ }
30
+
31
+ const ACCOUNTS: Account[] = [
32
+ { id: 'chk', name: 'Everyday checking', number: '•• 4821' },
33
+ { id: 'sav', name: 'Rainy-day savings', number: '•• 0937' },
34
+ { id: 'crd', name: 'Travel credit card', number: '•• 1150' },
35
+ { id: 'brk', name: 'Brokerage', number: '•• 7302' },
36
+ ];
37
+
38
+ /** Any shape of item (itemKey, itemText), your own rows, your own empty message. ↑↓ and Enter work in the search. */
39
+ export default function Accounts() {
40
+ const [account, setAccount] = useState<string | null>('chk');
41
+ return (
42
+ <Combobox
43
+ items={ACCOUNTS}
44
+ itemKey={(a) => a.id}
45
+ itemText={(a) => a.name}
46
+ value={account}
47
+ onValueChange={setAccount}
48
+ >
49
+ <ComboboxTrigger aria-label="Account" placeholder="Pick an account" className="w-72" />
50
+ <ComboboxPopup>
51
+ <ComboboxSearch placeholder="Search accounts" />
52
+ <ComboboxList<Account>
53
+ renderItem={(a) => (
54
+ <ComboboxItem item={a}>
55
+ <span className="flex justify-between gap-3">
56
+ {a.name}
57
+ <span className="text-muted-foreground tabular-nums">{a.number}</span>
58
+ </span>
59
+ </ComboboxItem>
60
+ )}
61
+ >
62
+ <ComboboxEmpty>No account by that name</ComboboxEmpty>
63
+ </ComboboxList>
64
+ </ComboboxPopup>
65
+ </Combobox>
66
+ );
67
+ }
68
+ ```
69
+
70
+ ### Tags
71
+
72
+ ```tsx
73
+ import { Combobox, ComboboxCreate, ComboboxList, ComboboxPopup, ComboboxSearch, ComboboxTrigger } from '@rinzai/zen';
74
+ import { useState } from 'react';
75
+
76
+ const asText = (tag: string) => tag;
77
+
78
+ /** Several at once (chips, removable), and typing a new name offers to add it: ComboboxCreate (Enter works too). */
79
+ export default function Tags() {
80
+ const [tags, setTags] = useState(['Essential', 'Shared', 'Work', 'Treat', 'Annual']);
81
+ const [chosen, setChosen] = useState(['Essential', 'Shared']);
82
+ return (
83
+ <Combobox multiple items={tags} itemKey={asText} itemText={asText} value={chosen} onValueChange={setChosen}>
84
+ <ComboboxTrigger aria-label="Tags" placeholder="No tags" className="w-80" />
85
+ <ComboboxPopup>
86
+ <ComboboxSearch />
87
+ <ComboboxList>
88
+ <ComboboxCreate
89
+ onCreate={(tag) => {
90
+ setTags((all) => [...all, tag]);
91
+ setChosen((c) => [...c, tag]);
92
+ }}
93
+ />
94
+ </ComboboxList>
95
+ </ComboboxPopup>
96
+ </Combobox>
97
+ );
98
+ }
99
+ ```
100
+
101
+ ### Many items
102
+
103
+ ```tsx
104
+ import { Combobox, ComboboxEmpty, ComboboxList, ComboboxPopup, ComboboxSearch, ComboboxTrigger } from '@rinzai/zen';
105
+ import { useState } from 'react';
106
+
107
+ const ACCOUNTS = Array.from({ length: 2000 }, (_, i) => ({
108
+ key: String(i + 1),
109
+ text: `Account ${String(i + 1).padStart(4, '0')}`,
110
+ }));
111
+
112
+ /** 2,000 items: the list renders only the rows in view, so it opens and scrolls instantly. Type to filter. */
113
+ export default function ManyItems() {
114
+ const [account, setAccount] = useState<string | null>('1');
115
+ return (
116
+ <Combobox items={ACCOUNTS} value={account} onValueChange={setAccount}>
117
+ <ComboboxTrigger aria-label="Account" className="w-64" />
118
+ <ComboboxPopup>
119
+ <ComboboxSearch />
120
+ <ComboboxList>
121
+ <ComboboxEmpty />
122
+ </ComboboxList>
123
+ </ComboboxPopup>
124
+ </Combobox>
125
+ );
126
+ }
127
+ ```
128
+
129
+ ## API reference
130
+
131
+ ### Combobox
132
+
133
+ Pick one or several items from a list you can search: a field that opens a
134
+ panel with a search box and the matching items. Compose it from
135
+ ComboboxTrigger, ComboboxPopup, ComboboxSearch, ComboboxList (virtualized,
136
+ for long lists), ComboboxItem, ComboboxEmpty and ComboboxCreate. Keyboard:
137
+ Enter, Space or ↓ on the field opens it, typing searches, ↑↓ move, Enter
138
+ chooses, Escape closes. Items can be any shape: say how to read a key and
139
+ text from one with `itemKey` and `itemText`.
140
+
141
+ | Prop | Type | Default | Description |
142
+ | --- | --- | --- | --- |
143
+ | `items` (required) | `readonly T[]` | | |
144
+ | `defaultValue` | `string \| readonly string[] \| null` | | |
145
+ | `filter` | `((item: T, query: string) => boolean)` | | Whether an item matches the search (default: its text contains it, any case). |
146
+ | `itemKey` | `((item: T) => string)` | | How to read an item's unique key (default: its `key`). |
147
+ | `itemText` | `((item: T) => string)` | | How to read an item's text, shown and searched (default: its `text`). |
148
+ | `multiple` | `boolean` | | |
149
+ | `name` | `string` | | Form field name: the chosen key is submitted with the form (with `multiple`, each key). |
150
+ | `onValueChange` | `((key: string) => void) \| ((keys: string[]) => void)` | | |
151
+ | `selectedItems` | `readonly T[]` | | Chosen items that aren't in `items` (e.g. just created), so the field can still show them. |
152
+ | `value` | `string \| readonly string[] \| null` | | |
153
+
154
+ ### ComboboxTrigger
155
+
156
+ The field: shows the choice (text, or chips when `multiple`) or the
157
+ placeholder, and opens the panel. Pass children to show the value your own way.
158
+
159
+ | Prop | Type | Default | Description |
160
+ | --- | --- | --- | --- |
161
+ | `children` | `ReactNode` | | Your own display of the value (default: its text, or chips). |
162
+ | `disabled` | `boolean` | | |
163
+ | `placeholder` | `string` | | Shown while nothing is chosen. |
164
+ | `size` | `"default" \| "lg" \| "sm"` | `default` | Height, matching Button's: sm (32px), default (40px) or lg (44px). |
165
+
166
+ ### ComboboxSearch
167
+
168
+ The search box: focused when the panel opens; ↑↓ move through the matches and Enter chooses.
169
+
170
+ | Prop | Type | Default | Description |
171
+ | --- | --- | --- | --- |
172
+ | `aria-label` | `string` | | |
173
+ | `placeholder` | `string` | | |
174
+
175
+ ### ComboboxList
176
+
177
+ The matching items, rendered only while in view (so thousands stay fast).
178
+ Rows are `itemHeight` px tall; `renderItem` draws each (default: a
179
+ ComboboxItem). Put ComboboxEmpty or ComboboxCreate inside for no matches.
180
+
181
+ | Prop | Type | Default | Description |
182
+ | --- | --- | --- | --- |
183
+ | `itemHeight` | `number` | `35` | Height of every row, in px (default 35). |
184
+ | `renderItem` | `((item: T) => ReactNode)` | | |
185
+
186
+ ### ComboboxItem
187
+
188
+ One item in a ComboboxList: a check when chosen, then its text (or your own content).
189
+
190
+ | Prop | Type | Default | Description |
191
+ | --- | --- | --- | --- |
192
+ | `item` (required) | `T` | | |
193
+ | `className` | `string` | | |
194
+
195
+ ### ComboboxEmpty
196
+
197
+ Shown when the search matches nothing.
198
+
199
+ No props of its own; it accepts the props of the element it renders.
200
+
201
+ ### ComboboxCreate
202
+
203
+ Offered when the search matches nothing: adds what was typed (Enter works
204
+ too). `children` renders the row from the text (default "Add …").
205
+
206
+ | Prop | Type | Default | Description |
207
+ | --- | --- | --- | --- |
208
+ | `onCreate` (required) | `(text: string) => void` | | |
@@ -0,0 +1,65 @@
1
+ # Command Palette
2
+
3
+ Search everything and act on it from the keyboard: a field over a list that narrows as you type, opened with ⌘K.
4
+
5
+ ```tsx
6
+ import { CommandPalette, useCommandPaletteShortcut } from '@rinzai/zen';
7
+ ```
8
+
9
+ ## Examples
10
+
11
+ ### Default
12
+
13
+ ```tsx
14
+ import { Button, CommandPalette, Kbd, useCommandPaletteShortcut, type CommandItem } from '@rinzai/zen';
15
+ import { useState } from 'react';
16
+ // Icons from Heroicons (https://heroicons.com): ChartBar (chart-bar), ListBullet (list-bullet), Settings (cog-6-tooth).
17
+
18
+ /** Pages and actions in one search. Open it with the button, or ⌘K / Ctrl+K. */
19
+ export default function Default() {
20
+ const [open, setOpen] = useState(false);
21
+ const [last, setLast] = useState<string>();
22
+ useCommandPaletteShortcut(() => setOpen(true));
23
+ const go = (label: string) => () => setLast(label);
24
+ const items: CommandItem[] = [
25
+ { label: 'Overview', group: 'Pages', icon: <ChartBar />, onSelect: go('Overview') },
26
+ { label: 'Entries', group: 'Pages', icon: <ListBullet />, keywords: ['transactions'], onSelect: go('Entries') },
27
+ { label: 'Settings', group: 'Pages', icon: <Settings />, keywords: ['preferences'], onSelect: go('Settings') },
28
+ { label: 'Add entry', group: 'Actions', hint: 'N', onSelect: go('Add entry') },
29
+ { label: 'Import a statement', group: 'Actions', keywords: ['csv', 'bank'], onSelect: go('Import') },
30
+ { label: 'Export to CSV', group: 'Actions', onSelect: go('Export') },
31
+ ];
32
+ return (
33
+ <div className="flex flex-col items-center gap-3">
34
+ <Button variant="outline" onClick={() => setOpen(true)}>
35
+ Search…
36
+ <span className="ml-2 flex gap-0.5">
37
+ <Kbd>⌘</Kbd>
38
+ <Kbd>K</Kbd>
39
+ </span>
40
+ </Button>
41
+ {last && <p className="text-muted-foreground mt-0! text-sm">Ran: {last}</p>}
42
+ <CommandPalette open={open} onOpenChange={setOpen} items={items} placeholder="Search pages and actions…" />
43
+ </div>
44
+ );
45
+ }
46
+ ```
47
+
48
+ ## API reference
49
+
50
+ ### CommandPalette
51
+
52
+ Search everything and act on it (pages, commands, records): a field over a
53
+ list that narrows as you type. Arrow keys move, Enter runs the item and
54
+ closes. Open it from a button, or anywhere with ⌘K / Ctrl+K
55
+ (useCommandPaletteShortcut).
56
+
57
+ | Prop | Type | Default | Description |
58
+ | --- | --- | --- | --- |
59
+ | `items` (required) | `CommandItem[]` | | |
60
+ | `onOpenChange` (required) | `(open: boolean) => void` | | |
61
+ | `open` (required) | `boolean` | | |
62
+ | `className` | `string` | | |
63
+ | `empty` | `ReactNode` | | Shown when nothing matches. |
64
+ | `label` | `string` | | Names the palette and its list, for screen readers. |
65
+ | `placeholder` | `string` | | |
@@ -0,0 +1,167 @@
1
+ # Date Picker
2
+
3
+ A field that opens a calendar: one date, or a range previewed as you point. Dates are "YYYY-MM-DD" strings, so they never shift with time zones.
4
+
5
+ ```tsx
6
+ import { DatePicker, DateRangePicker } from '@rinzai/zen';
7
+ ```
8
+
9
+ ## Examples
10
+
11
+ ### Default
12
+
13
+ ```tsx
14
+ import { DatePicker } from '@rinzai/zen';
15
+ import { useState } from 'react';
16
+
17
+ /** Opens on the chosen date, ready for the keyboard; picking a day closes it. With onClear, it can be emptied. */
18
+ export default function Default() {
19
+ const [date, setDate] = useState<string | null>('2026-09-30');
20
+ return (
21
+ <DatePicker
22
+ aria-label="Due date"
23
+ value={date}
24
+ onValueChange={setDate}
25
+ clearable
26
+ locale="en-IN"
27
+ className="w-56"
28
+ />
29
+ );
30
+ }
31
+ ```
32
+
33
+ ### Range
34
+
35
+ ```tsx
36
+ import { type DateRange, DateRangePicker } from '@rinzai/zen';
37
+ import { useState } from 'react';
38
+
39
+ /** A trip: the start, then the end, across two months. Closes once both are picked. */
40
+ export default function Range() {
41
+ const [trip, setTrip] = useState<DateRange | null>(null);
42
+ return (
43
+ <DateRangePicker
44
+ aria-label="Trip dates"
45
+ value={trip}
46
+ onValueChange={setTrip}
47
+ clearable
48
+ months={2}
49
+ locale="en-US"
50
+ className="w-64"
51
+ />
52
+ );
53
+ }
54
+ ```
55
+
56
+ ### Presets
57
+
58
+ ```tsx
59
+ import { commonRanges, type DateRange, DateRangePicker, today } from '@rinzai/zen';
60
+ import { useState } from 'react';
61
+
62
+ /** The ranges people pick most, one tap each, beside the calendar. */
63
+ export default function Presets() {
64
+ const r = commonRanges(today());
65
+ const [range, setRange] = useState<DateRange | null>(r.thisMonth);
66
+ return (
67
+ <DateRangePicker
68
+ aria-label="Entries between"
69
+ value={range}
70
+ onValueChange={setRange}
71
+ locale="en-US"
72
+ className="w-64"
73
+ presets={[
74
+ { label: 'This week', range: r.thisWeek },
75
+ { label: 'Last 7 days', range: r.last7Days },
76
+ { label: 'Last 30 days', range: r.last30Days },
77
+ { label: 'This month', range: r.thisMonth },
78
+ { label: 'Last month', range: r.lastMonth },
79
+ { label: 'This year', range: r.thisYear },
80
+ ]}
81
+ />
82
+ );
83
+ }
84
+ ```
85
+
86
+ ### In field
87
+
88
+ ```tsx
89
+ import { addDays, DatePicker, Field, today } from '@rinzai/zen';
90
+ import { useState } from 'react';
91
+
92
+ /** In a Field: the label, hint and error are wired to the picker. Only dates from today on. */
93
+ export default function InField() {
94
+ const [date, setDate] = useState<string | null>(null);
95
+ const [touched, setTouched] = useState(false);
96
+ return (
97
+ <Field
98
+ label="Start date"
99
+ hint="When the plan begins."
100
+ error={touched && !date ? 'Pick a start date.' : undefined}
101
+ className="w-64"
102
+ >
103
+ <DatePicker
104
+ value={date}
105
+ onValueChange={setDate}
106
+ onBlur={() => setTouched(true)}
107
+ locale="en-US"
108
+ min={today()}
109
+ max={addDays(today(), 365)}
110
+ />
111
+ </Field>
112
+ );
113
+ }
114
+ ```
115
+
116
+ ## API reference
117
+
118
+ ### DatePicker
119
+
120
+ Date field: shows the date ("Sep 30, 2026" for the locale) and opens a
121
+ Calendar to pick one, focused on the chosen date so the keyboard works at
122
+ once. Dates are "YYYY-MM-DD" strings. When `clearable`, the popup also offers
123
+ `clearLabel` to empty it. With `name`, it's submitted with a form.
124
+
125
+ | Prop | Type | Default | Description |
126
+ | --- | --- | --- | --- |
127
+ | `className` | `string` | | Classes for the trigger (e.g. a width, instead of the default full width). |
128
+ | `clearable` | `boolean` | `false` | Offers a clear option in the popup (shown when a value is set); clearing reports null. |
129
+ | `clearLabel` | `string` | | |
130
+ | `defaultValue` | `string \| null` | `null` | The first date, when the picker keeps its own. |
131
+ | `isDisabled` | `((date: string) => boolean)` | | Dates that can't be picked, e.g. weekends or booked days. |
132
+ | `locale` | `string` | | Decides the date format, month and weekday names, and the first day of the week, e.g. "en-IN". Defaults to the LocaleProvider's. |
133
+ | `max` | `string` | | Latest date that can be picked. |
134
+ | `min` | `string` | | Earliest date that can be picked ("YYYY-MM-DD"). |
135
+ | `name` | `string` | | Form field name: the value is submitted with the form ("YYYY-MM-DD", a range as "start/end"). |
136
+ | `onValueChange` | `((date: string \| null) => void)` | | |
137
+ | `placeholder` | `string` | | |
138
+ | `size` | `"default" \| "lg" \| "sm"` | | Height, matching Button's: sm (32px), default (40px) or lg (44px). |
139
+ | `value` | `string \| null` | | "YYYY-MM-DD", or null for none, when you keep it. |
140
+ | `weekStartsOn` | `number` | | 0 for Sunday … 6 for Saturday. Defaults to the locale's. |
141
+
142
+ ### DateRangePicker
143
+
144
+ Date range field: pick a start and then an end in a Calendar (the days
145
+ between are previewed while pointing); it closes once the range is complete.
146
+ Shows the range the locale's way, e.g. "Sep 3 – 10, 2026". With `presets`,
147
+ common ranges ("Last 30 days") sit beside the calendar, one tap each (see
148
+ commonRanges).
149
+
150
+ | Prop | Type | Default | Description |
151
+ | --- | --- | --- | --- |
152
+ | `className` | `string` | | Classes for the trigger (e.g. a width, instead of the default full width). |
153
+ | `clearable` | `boolean` | `false` | Offers a clear option in the popup (shown when a value is set); clearing reports null. |
154
+ | `clearLabel` | `string` | | |
155
+ | `defaultValue` | `DateRange \| null` | `null` | The first range, when the picker keeps its own. |
156
+ | `isDisabled` | `((date: string) => boolean)` | | Dates that can't be picked, e.g. weekends or booked days. |
157
+ | `locale` | `string` | | Decides the date format, month and weekday names, and the first day of the week, e.g. "en-IN". Defaults to the LocaleProvider's. |
158
+ | `max` | `string` | | Latest date that can be picked. |
159
+ | `min` | `string` | | Earliest date that can be picked ("YYYY-MM-DD"). |
160
+ | `months` | `number` | `1` | Months shown side by side (2 suits wide screens). |
161
+ | `name` | `string` | | Form field name: the value is submitted with the form ("YYYY-MM-DD", a range as "start/end"). |
162
+ | `onValueChange` | `((range: DateRange \| null) => void)` | | |
163
+ | `placeholder` | `string` | | |
164
+ | `presets` | `DateRangePreset[]` | | Ranges picked in one tap, beside the calendar. |
165
+ | `size` | `"default" \| "lg" \| "sm"` | | Height, matching Button's: sm (32px), default (40px) or lg (44px). |
166
+ | `value` | `DateRange \| null` | | The range when you keep it; `end` is null while only the start is picked. |
167
+ | `weekStartsOn` | `number` | | 0 for Sunday … 6 for Saturday. Defaults to the locale's. |