@rinzai/zen 0.14.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 +7 -1
- 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/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,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. |
|