@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,86 @@
|
|
|
1
|
+
# Radio Group
|
|
2
|
+
|
|
3
|
+
Choose one of a list, each option with a label and a description; native radios, so arrow keys work.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { RadioGroup, Radio } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Radio, RadioGroup } from '@rinzai/zen';
|
|
15
|
+
import { useState } from 'react';
|
|
16
|
+
|
|
17
|
+
/** Arrow keys move between options and pick them, as for any radio group. */
|
|
18
|
+
export default function Default() {
|
|
19
|
+
const [repay, setRepay] = useState<'emi' | 'tenure'>('emi');
|
|
20
|
+
return (
|
|
21
|
+
<RadioGroup label="After a prepayment" value={repay} onValueChange={setRepay}>
|
|
22
|
+
<Radio value="emi" description="Keep the end date; pay less each month.">
|
|
23
|
+
Lower the EMI
|
|
24
|
+
</Radio>
|
|
25
|
+
<Radio value="tenure" description="Keep the EMI; finish sooner and save more interest.">
|
|
26
|
+
Shorten the tenure
|
|
27
|
+
</Radio>
|
|
28
|
+
</RadioGroup>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Horizontal
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { Radio, RadioGroup } from '@rinzai/zen';
|
|
37
|
+
import { useState } from 'react';
|
|
38
|
+
|
|
39
|
+
/** In a row, with one option disabled. */
|
|
40
|
+
export default function Horizontal() {
|
|
41
|
+
const [every, setEvery] = useState<string | null>(null);
|
|
42
|
+
return (
|
|
43
|
+
<RadioGroup label="Repeat" value={every} onValueChange={setEvery} orientation="horizontal">
|
|
44
|
+
<Radio value="week">Weekly</Radio>
|
|
45
|
+
<Radio value="month">Monthly</Radio>
|
|
46
|
+
<Radio value="year">Yearly</Radio>
|
|
47
|
+
<Radio value="custom" disabled>
|
|
48
|
+
Custom
|
|
49
|
+
</Radio>
|
|
50
|
+
</RadioGroup>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## API reference
|
|
56
|
+
|
|
57
|
+
### RadioGroup
|
|
58
|
+
|
|
59
|
+
Choose one of a list: native radios underneath, so arrow keys move and pick
|
|
60
|
+
within the group, and forms work as usual. Fill it with Radio. For a few
|
|
61
|
+
short options side by side use Segmented; for options that show themselves,
|
|
62
|
+
RadioCards.
|
|
63
|
+
|
|
64
|
+
| Prop | Type | Default | Description |
|
|
65
|
+
| --- | --- | --- | --- |
|
|
66
|
+
| `children` (required) | `ReactNode` | | The Radios. |
|
|
67
|
+
| `className` | `string` | | Classes for the options' container. |
|
|
68
|
+
| `defaultValue` | `string \| null` | `null` | The first chosen option, when the group keeps its own. |
|
|
69
|
+
| `disabled` | `boolean` | `false` | |
|
|
70
|
+
| `label` | `ReactNode` | | Shown above the options, naming the group. Without it, give the group an aria-label. |
|
|
71
|
+
| `name` | `string` | | Form field name; defaults to a generated one. |
|
|
72
|
+
| `onValueChange` | `((value: V) => void)` | | |
|
|
73
|
+
| `orientation` | `"horizontal" \| "vertical"` | `vertical` | Options in a column (default) or wrapping in a row. |
|
|
74
|
+
| `value` | `string \| null` | | The chosen option when you keep it, or null for none yet. |
|
|
75
|
+
|
|
76
|
+
### Radio
|
|
77
|
+
|
|
78
|
+
One option: a round radio that fills with a glowing dot when chosen, its label, and an optional description.
|
|
79
|
+
|
|
80
|
+
| Prop | Type | Default | Description |
|
|
81
|
+
| --- | --- | --- | --- |
|
|
82
|
+
| `children` (required) | `ReactNode` | | The label. |
|
|
83
|
+
| `value` (required) | `string` | | |
|
|
84
|
+
| `className` | `string` | | |
|
|
85
|
+
| `description` | `ReactNode` | | A muted line under the label. |
|
|
86
|
+
| `disabled` | `boolean` | | Can't be chosen (and the arrow keys skip it). |
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Range Slider
|
|
2
|
+
|
|
3
|
+
Two thumbs on one track, for a span like an amount from $50 to $500.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { RangeSlider } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { RangeSlider } from '@rinzai/zen';
|
|
15
|
+
import { useState } from 'react';
|
|
16
|
+
|
|
17
|
+
/** Entries between two amounts. */
|
|
18
|
+
export default function Default() {
|
|
19
|
+
const [span, setSpan] = useState<[number, number]>([50, 500]);
|
|
20
|
+
return (
|
|
21
|
+
<div className="flex w-full max-w-sm flex-col gap-3">
|
|
22
|
+
<div className="flex justify-between text-sm">
|
|
23
|
+
<span className="font-medium">Amount</span>
|
|
24
|
+
<span className="text-muted-foreground tabular-nums">
|
|
25
|
+
${span[0]} – ${span[1]}
|
|
26
|
+
</span>
|
|
27
|
+
</div>
|
|
28
|
+
<RangeSlider
|
|
29
|
+
label="Amount"
|
|
30
|
+
min={0}
|
|
31
|
+
max={1000}
|
|
32
|
+
step={10}
|
|
33
|
+
value={span}
|
|
34
|
+
onValueChange={setSpan}
|
|
35
|
+
valueText={(v) => `$${v}`}
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## API reference
|
|
43
|
+
|
|
44
|
+
### RangeSlider
|
|
45
|
+
|
|
46
|
+
Two thumbs on one track, for a span such as an amount from $50 to $500:
|
|
47
|
+
two native range inputs (keyboard, forms and screen readers work as on
|
|
48
|
+
Slider), the span between them filled. The thumbs can meet but not cross.
|
|
49
|
+
|
|
50
|
+
| Prop | Type | Default | Description |
|
|
51
|
+
| --- | --- | --- | --- |
|
|
52
|
+
| `label` (required) | `string` | | Accessible name of the pair, e.g. "Amount". |
|
|
53
|
+
| `className` | `string` | | |
|
|
54
|
+
| `defaultValue` | `readonly [number, number]` | | The first span, when the slider keeps its own (default the whole range). |
|
|
55
|
+
| `disabled` | `boolean` | | |
|
|
56
|
+
| `max` | `number` | `100` | |
|
|
57
|
+
| `min` | `number` | `0` | |
|
|
58
|
+
| `name` | `string` | | Form field name: low and high are submitted under it, in that order. |
|
|
59
|
+
| `onValueChange` | `((value: [number, number]) => void)` | | While dragging (every step). |
|
|
60
|
+
| `onValueCommit` | `((value: [number, number]) => void)` | | Once the user lets go: the moment to save or filter. |
|
|
61
|
+
| `step` | `number` | `1` | |
|
|
62
|
+
| `thumbLabels` | `readonly [string, string]` | | Each thumb's name (default "Minimum", "Maximum"). |
|
|
63
|
+
| `value` | `readonly [number, number]` | | The span, low then high, when you keep it. |
|
|
64
|
+
| `valueText` | `((value: number) => string)` | | Spoken value of a thumb, e.g. (v) => `$${v}`. |
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Repeat Picker
|
|
2
|
+
|
|
3
|
+
How often something recurs, like a bill, said in words, with the next dates it falls on.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { RepeatPicker } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { type Repeat, RepeatPicker } from '@rinzai/zen';
|
|
15
|
+
import { useState } from 'react';
|
|
16
|
+
|
|
17
|
+
/** Rent: monthly on the 1st, from November. */
|
|
18
|
+
export default function Default() {
|
|
19
|
+
const [repeat, setRepeat] = useState<Repeat>({ every: 1, unit: 'month', day: 1 });
|
|
20
|
+
return (
|
|
21
|
+
<RepeatPicker value={repeat} onValueChange={setRepeat} locale="en-US" start="2026-11-01" className="max-w-sm" />
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## API reference
|
|
27
|
+
|
|
28
|
+
### RepeatPicker
|
|
29
|
+
|
|
30
|
+
How often something recurs, e.g. a bill: every n days, weeks, months or
|
|
31
|
+
years, on chosen weekdays or a day of the month. Says what it means in
|
|
32
|
+
words, and with a `start` date, the next few dates it falls on.
|
|
33
|
+
|
|
34
|
+
| Prop | Type | Default | Description |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| `className` | `string` | | |
|
|
37
|
+
| `defaultValue` | `Repeat` | `{ every: 1, unit: 'month' }` | The first rule, when the picker keeps its own. Every month by default. |
|
|
38
|
+
| `label` | `string` | | The group's name for screen readers (default "Repeats"). |
|
|
39
|
+
| `locale` | `string` | | For weekday and date names, and the week's first day. Defaults to the LocaleProvider's. |
|
|
40
|
+
| `name` | `string` | | Form field name: the rule is submitted as JSON. |
|
|
41
|
+
| `onValueChange` | `((value: Repeat) => void)` | | |
|
|
42
|
+
| `start` | `string` | | When it starts: its weekday and day of the month are the defaults, and the next dates are shown. |
|
|
43
|
+
| `value` | `Repeat` | | The rule, when you keep it. |
|
|
44
|
+
| `weekStartsOn` | `number` | | 0 for Sunday … 6. Defaults to the locale's. |
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Resizable
|
|
2
|
+
|
|
3
|
+
Panels with handles between them to drag, or to move with the arrow keys, within limits you set; groups nest, and the layout is reported to keep.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
/** A sidebar beside the main view, and a detail pane under it: drag a line, or focus it and use the arrow keys. */
|
|
17
|
+
export default function Default() {
|
|
18
|
+
return (
|
|
19
|
+
<ResizablePanelGroup className="border-tint/10 h-72 w-full max-w-2xl rounded-xl border">
|
|
20
|
+
<ResizablePanel defaultSize={30} minSize={20} maxSize={50}>
|
|
21
|
+
<div className="flex h-full flex-col gap-1 p-4">
|
|
22
|
+
<span className="text-muted-foreground mb-2 text-xs font-medium tracking-wide uppercase">
|
|
23
|
+
Accounts
|
|
24
|
+
</span>
|
|
25
|
+
<span className="text-sm">Everyday</span>
|
|
26
|
+
<span className="text-sm">Savings</span>
|
|
27
|
+
<span className="text-sm">Credit card</span>
|
|
28
|
+
</div>
|
|
29
|
+
</ResizablePanel>
|
|
30
|
+
<ResizableHandle aria-label="Resize accounts" />
|
|
31
|
+
<ResizablePanel>
|
|
32
|
+
<ResizablePanelGroup orientation="vertical">
|
|
33
|
+
<ResizablePanel defaultSize={65} minSize={30}>
|
|
34
|
+
<div className="flex h-full items-center justify-center text-sm">Transactions</div>
|
|
35
|
+
</ResizablePanel>
|
|
36
|
+
<ResizableHandle aria-label="Resize details" />
|
|
37
|
+
<ResizablePanel minSize={20}>
|
|
38
|
+
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">
|
|
39
|
+
Details
|
|
40
|
+
</div>
|
|
41
|
+
</ResizablePanel>
|
|
42
|
+
</ResizablePanelGroup>
|
|
43
|
+
</ResizablePanel>
|
|
44
|
+
</ResizablePanelGroup>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Stacked
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@rinzai/zen';
|
|
53
|
+
|
|
54
|
+
/** Three panels stacked, each with a smallest size: drag either line, or focus one and use ↑↓. */
|
|
55
|
+
export default function Stacked() {
|
|
56
|
+
return (
|
|
57
|
+
<ResizablePanelGroup orientation="vertical" className="border-tint/10 h-80 w-full max-w-md rounded-xl border">
|
|
58
|
+
<ResizablePanel defaultSize={25} minSize={15}>
|
|
59
|
+
<div className="flex h-full flex-col justify-center gap-0.5 px-4">
|
|
60
|
+
<span className="text-muted-foreground text-xs">Balance</span>
|
|
61
|
+
<span className="text-lg font-semibold tabular-nums">$12,480</span>
|
|
62
|
+
</div>
|
|
63
|
+
</ResizablePanel>
|
|
64
|
+
<ResizableHandle aria-label="Resize balance" />
|
|
65
|
+
<ResizablePanel defaultSize={45} minSize={20}>
|
|
66
|
+
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">This month</div>
|
|
67
|
+
</ResizablePanel>
|
|
68
|
+
<ResizableHandle aria-label="Resize this month" />
|
|
69
|
+
<ResizablePanel minSize={15}>
|
|
70
|
+
<div className="text-muted-foreground flex h-full items-center justify-center text-sm">Upcoming</div>
|
|
71
|
+
</ResizablePanel>
|
|
72
|
+
</ResizablePanelGroup>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## API reference
|
|
78
|
+
|
|
79
|
+
### ResizablePanelGroup
|
|
80
|
+
|
|
81
|
+
Panels side by side (or stacked, `orientation="vertical"`) with handles
|
|
82
|
+
between them to drag, or to focus and move with the arrow keys. Sizes are
|
|
83
|
+
percentages of the group; `onLayout` reports them all after each change,
|
|
84
|
+
e.g. to keep for next time as the panels' `defaultSize`. Groups nest.
|
|
85
|
+
|
|
86
|
+
| Prop | Type | Default | Description |
|
|
87
|
+
| --- | --- | --- | --- |
|
|
88
|
+
| `onLayout` | `((sizes: number[]) => void)` | | Every panel's size, in percent and in order, after each change. |
|
|
89
|
+
| `orientation` | `"horizontal" \| "vertical"` | `horizontal` | Side by side (default), or stacked. |
|
|
90
|
+
|
|
91
|
+
### ResizablePanel
|
|
92
|
+
|
|
93
|
+
One panel: `defaultSize` is a percentage of the group (those without one share what's left); `minSize` and `maxSize` bound it.
|
|
94
|
+
|
|
95
|
+
| Prop | Type | Default | Description |
|
|
96
|
+
| --- | --- | --- | --- |
|
|
97
|
+
| `defaultSize` | `number` | | Its share of the group, in percent. |
|
|
98
|
+
| `maxSize` | `number` | `100` | |
|
|
99
|
+
| `minSize` | `number` | `0` | |
|
|
100
|
+
|
|
101
|
+
### ResizableHandle
|
|
102
|
+
|
|
103
|
+
The boundary between two panels: a hairline that lights up while it's
|
|
104
|
+
dragged, hovered or focused, with a wider grip than it looks. Arrow keys move
|
|
105
|
+
it, Home and End take the panel before it to its smallest and largest.
|
|
106
|
+
|
|
107
|
+
No props of its own; it accepts the props of the element it renders.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Seek Bar
|
|
2
|
+
|
|
3
|
+
Where playback is in a track, and the way to move it: played and loaded on one track, between the time played and the length.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { SeekBar, formatDuration } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Button, SeekBar, useMediaPlayer } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
import { useDemoTrack } from '../../player-bar/examples/demo-track';
|
|
17
|
+
// Icons from Heroicons (https://heroicons.com): Pause (pause), Play (play).
|
|
18
|
+
|
|
19
|
+
/** Filled to the time played, shaded on to what's loaded. Drag it: the track plays on, and seeks when you let go. */
|
|
20
|
+
export default function Default() {
|
|
21
|
+
const src = useDemoTrack();
|
|
22
|
+
const [audioRef, player] = useMediaPlayer();
|
|
23
|
+
return (
|
|
24
|
+
<div className="flex w-96 items-center gap-3">
|
|
25
|
+
<audio ref={audioRef} src={src} preload="metadata" />
|
|
26
|
+
<Button
|
|
27
|
+
size="icon-sm"
|
|
28
|
+
className="rounded-full"
|
|
29
|
+
aria-label={player.playing ? 'Pause' : 'Play'}
|
|
30
|
+
disabled={!src}
|
|
31
|
+
onClick={() => player.toggle()}
|
|
32
|
+
>
|
|
33
|
+
{player.playing ? <Pause className="size-4" /> : <Play className="size-4" />}
|
|
34
|
+
</Button>
|
|
35
|
+
<SeekBar
|
|
36
|
+
className="flex-1"
|
|
37
|
+
currentTime={player.currentTime}
|
|
38
|
+
playing={player.playing && !player.waiting}
|
|
39
|
+
playbackRate={player.playbackRate}
|
|
40
|
+
duration={player.duration}
|
|
41
|
+
buffered={player.buffered}
|
|
42
|
+
onSeek={player.seek}
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## API reference
|
|
50
|
+
|
|
51
|
+
### SeekBar
|
|
52
|
+
|
|
53
|
+
Where playback is in a track, and the way to move it: filled up to the
|
|
54
|
+
current time, shaded on to what's loaded, between the time played and the
|
|
55
|
+
length. While playing it glides, run by the browser's compositor between
|
|
56
|
+
the player's time updates. While dragging, the time follows the thumb and
|
|
57
|
+
the track keeps playing; letting go seeks. The arrow keys seek 5 seconds.
|
|
58
|
+
|
|
59
|
+
| Prop | Type | Default | Description |
|
|
60
|
+
| --- | --- | --- | --- |
|
|
61
|
+
| `currentTime` (required) | `number` | | Seconds played. |
|
|
62
|
+
| `duration` (required) | `number` | | Seconds long; 0 while unknown (the bar is disabled). |
|
|
63
|
+
| `onSeek` (required) | `(time: number) => void` | | When the user lets go, or steps with the keys: the time to play from. |
|
|
64
|
+
| `buffered` | `number` | `0` | Seconds loaded, shaded on the track past the time played. |
|
|
65
|
+
| `playbackRate` | `number` | `1` | How fast it moves on while playing (default 1). |
|
|
66
|
+
| `playing` | `boolean` | `false` | While the time is running (playing, not waiting for data): the bar glides on between time updates. |
|
|
67
|
+
| `thumbColor` | `string` | | CSS colour for the thumb (default: the accent). |
|
|
68
|
+
| `valueText` | `((value: number) => string)` | | Spoken value, e.g. (v) => `Hue ${v} degrees`. |
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Segmented
|
|
2
|
+
|
|
3
|
+
A pill row for a few mutually exclusive options.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Segmented, SegmentedItem } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Segmented, SegmentedItem } from '@rinzai/zen';
|
|
15
|
+
import { useState } from 'react';
|
|
16
|
+
|
|
17
|
+
export default function Default() {
|
|
18
|
+
const [glow, setGlow] = useState<'off' | 'soft' | 'bright'>('soft');
|
|
19
|
+
return (
|
|
20
|
+
<Segmented label="Glow" value={glow} onValueChange={setGlow}>
|
|
21
|
+
<SegmentedItem value="off">Off</SegmentedItem>
|
|
22
|
+
<SegmentedItem value="soft">Soft</SegmentedItem>
|
|
23
|
+
<SegmentedItem value="bright">Bright</SegmentedItem>
|
|
24
|
+
</Segmented>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### With icons
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { Segmented, SegmentedItem } from '@rinzai/zen';
|
|
33
|
+
import { useState } from 'react';
|
|
34
|
+
// Icons from Heroicons (https://heroicons.com): ChartBarIcon (chart-bar), ListBullet (list-bullet), Squares (squares-2x2).
|
|
35
|
+
|
|
36
|
+
/** SegmentedItem holds anything (here an icon and text), and an option can be disabled. */
|
|
37
|
+
export default function WithIcons() {
|
|
38
|
+
const [view, setView] = useState('list');
|
|
39
|
+
return (
|
|
40
|
+
<Segmented label="View" value={view} onValueChange={setView}>
|
|
41
|
+
<SegmentedItem value="list">
|
|
42
|
+
<span className="flex items-center gap-1.5">
|
|
43
|
+
<ListBullet className="size-4" /> List
|
|
44
|
+
</span>
|
|
45
|
+
</SegmentedItem>
|
|
46
|
+
<SegmentedItem value="grid">
|
|
47
|
+
<span className="flex items-center gap-1.5">
|
|
48
|
+
<Squares className="size-4" /> Grid
|
|
49
|
+
</span>
|
|
50
|
+
</SegmentedItem>
|
|
51
|
+
<SegmentedItem value="chart" disabled>
|
|
52
|
+
<span className="flex items-center gap-1.5">
|
|
53
|
+
<ChartBarIcon className="size-4" /> Chart
|
|
54
|
+
</span>
|
|
55
|
+
</SegmentedItem>
|
|
56
|
+
</Segmented>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## API reference
|
|
62
|
+
|
|
63
|
+
### Segmented
|
|
64
|
+
|
|
65
|
+
A small set of mutually exclusive options, shown as a pill row. Native radio
|
|
66
|
+
inputs underneath, so arrow keys and forms work as for any radio group. The
|
|
67
|
+
selection slides between options, like Pills. Fill it with
|
|
68
|
+
SegmentedItem (any content: text, icons, a disabled option).
|
|
69
|
+
|
|
70
|
+
| Prop | Type | Default | Description |
|
|
71
|
+
| --- | --- | --- | --- |
|
|
72
|
+
| `children` (required) | `ReactNode` | | The SegmentedItems. |
|
|
73
|
+
| `className` | `string` | | |
|
|
74
|
+
| `defaultValue` | `string` | | The first chosen option, when the control keeps its own. |
|
|
75
|
+
| `label` | `ReactNode` | | Shown above the options, naming the group. Without it, give the group an aria-label or aria-labelledby. |
|
|
76
|
+
| `name` | `string` | | Form field name; defaults to a generated one. |
|
|
77
|
+
| `onValueChange` | `((value: V) => void)` | | |
|
|
78
|
+
| `value` | `string` | | The chosen option, when you keep it. |
|
|
79
|
+
|
|
80
|
+
### SegmentedItem
|
|
81
|
+
|
|
82
|
+
One option of a Segmented: a native radio covering whatever you put inside.
|
|
83
|
+
|
|
84
|
+
| Prop | Type | Default | Description |
|
|
85
|
+
| --- | --- | --- | --- |
|
|
86
|
+
| `children` (required) | `ReactNode` | | What the option shows: text, an icon, both. |
|
|
87
|
+
| `value` (required) | `string` | | |
|
|
88
|
+
| `className` | `string` | | |
|
|
89
|
+
| `disabled` | `boolean` | | Skipped by the arrow keys and can't be chosen. |
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Select
|
|
2
|
+
|
|
3
|
+
Pick one value from a short list, fully keyboard-driven. For long or searchable lists, use Combobox.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Select, SelectItem, SelectGroup, SelectSeparator } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Select, SelectItem } from '@rinzai/zen';
|
|
15
|
+
import { useState } from 'react';
|
|
16
|
+
|
|
17
|
+
const CATEGORIES = ['Groceries', 'Rent', 'Transport', 'Dining out', 'Subscriptions', 'Health', 'Travel'];
|
|
18
|
+
|
|
19
|
+
/** Keyboard: arrows, Home/End, type a letter to jump, Enter to choose, Escape to close. */
|
|
20
|
+
export default function Default() {
|
|
21
|
+
const [category, setCategory] = useState<string | null>('rent');
|
|
22
|
+
return (
|
|
23
|
+
<Select aria-label="Category" value={category} onValueChange={setCategory} className="w-56">
|
|
24
|
+
{CATEGORIES.map((label) => (
|
|
25
|
+
<SelectItem key={label} value={label.toLowerCase().replace(/\s+/g, '-')}>
|
|
26
|
+
{label}
|
|
27
|
+
</SelectItem>
|
|
28
|
+
))}
|
|
29
|
+
</Select>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Custom options
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { currencySymbol, Select, SelectItem } from '@rinzai/zen';
|
|
38
|
+
import { useState } from 'react';
|
|
39
|
+
|
|
40
|
+
const CURRENCIES = [
|
|
41
|
+
{ value: 'INR', label: 'Indian rupee' },
|
|
42
|
+
{ value: 'USD', label: 'US dollar' },
|
|
43
|
+
{ value: 'EUR', label: 'Euro' },
|
|
44
|
+
{ value: 'JPY', label: 'Japanese yen', disabled: true },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
/** An item can show anything; its `label` is what the field shows and typing matches. Disabled items are skipped by the keyboard. */
|
|
48
|
+
export default function CustomOptions() {
|
|
49
|
+
const [currency, setCurrency] = useState<string | null>(null);
|
|
50
|
+
return (
|
|
51
|
+
<Select
|
|
52
|
+
aria-label="Currency"
|
|
53
|
+
placeholder="Choose a currency"
|
|
54
|
+
value={currency}
|
|
55
|
+
onValueChange={setCurrency}
|
|
56
|
+
className="w-64"
|
|
57
|
+
>
|
|
58
|
+
{CURRENCIES.map((c) => (
|
|
59
|
+
<SelectItem key={c.value} value={c.value} label={c.label} disabled={c.disabled}>
|
|
60
|
+
<span className="flex items-center justify-between gap-3">
|
|
61
|
+
{c.label}
|
|
62
|
+
<span className="text-muted-foreground tabular-nums">
|
|
63
|
+
{c.value} {currencySymbol(c.value, 'en')}
|
|
64
|
+
</span>
|
|
65
|
+
</span>
|
|
66
|
+
</SelectItem>
|
|
67
|
+
))}
|
|
68
|
+
</Select>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Groups
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
import { Select, SelectGroup, SelectItem, SelectSeparator } from '@rinzai/zen';
|
|
77
|
+
import { useState } from 'react';
|
|
78
|
+
|
|
79
|
+
/** SelectItem children, organised with SelectGroup and SelectSeparator; the keyboard skips the disabled one. */
|
|
80
|
+
export default function Groups() {
|
|
81
|
+
const [category, setCategory] = useState<string | null>('groceries');
|
|
82
|
+
return (
|
|
83
|
+
<Select aria-label="Category" value={category} onValueChange={setCategory} className="w-64">
|
|
84
|
+
<SelectGroup label="Essentials">
|
|
85
|
+
<SelectItem value="rent">Rent</SelectItem>
|
|
86
|
+
<SelectItem value="groceries">Groceries</SelectItem>
|
|
87
|
+
<SelectItem value="transport">Transport</SelectItem>
|
|
88
|
+
</SelectGroup>
|
|
89
|
+
<SelectSeparator />
|
|
90
|
+
<SelectGroup label="Lifestyle">
|
|
91
|
+
<SelectItem value="dining">Dining out</SelectItem>
|
|
92
|
+
<SelectItem value="travel" disabled>
|
|
93
|
+
Travel (over budget)
|
|
94
|
+
</SelectItem>
|
|
95
|
+
</SelectGroup>
|
|
96
|
+
</Select>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## API reference
|
|
102
|
+
|
|
103
|
+
### Select
|
|
104
|
+
|
|
105
|
+
Pick one value from a short list: a field-styled button that opens a list
|
|
106
|
+
below it. Keyboard: arrows, Home/End, type to jump, Enter to choose, Escape
|
|
107
|
+
to close. Fill it with SelectItem, organised with SelectGroup and
|
|
108
|
+
SelectSeparator. For long or searchable lists, use Combobox.
|
|
109
|
+
|
|
110
|
+
| Prop | Type | Default | Description |
|
|
111
|
+
| --- | --- | --- | --- |
|
|
112
|
+
| `children` (required) | `ReactNode` | | SelectItem, SelectGroup and SelectSeparator. |
|
|
113
|
+
| `className` | `string` | | Classes for the trigger (e.g. a width, instead of the default full width). |
|
|
114
|
+
| `defaultValue` | `string \| null` | `null` | The first value, when the select keeps its own. |
|
|
115
|
+
| `disabled` | `boolean` | `false` | |
|
|
116
|
+
| `id` | `string` | | |
|
|
117
|
+
| `listClassName` | `string` | | Classes for the popup list. |
|
|
118
|
+
| `name` | `string` | | Form field name: adds a hidden input with the value. |
|
|
119
|
+
| `onValueChange` | `((value: V) => void)` | | |
|
|
120
|
+
| `placeholder` | `string` | | Shown while nothing is chosen. |
|
|
121
|
+
| `size` | `"default" \| "lg" \| "sm"` | `default` | Height, matching Button's: sm (32px), default (40px) or lg (44px). |
|
|
122
|
+
| `value` | `string \| null` | | The chosen value, when you keep it; null for none. |
|
|
123
|
+
|
|
124
|
+
### SelectItem
|
|
125
|
+
|
|
126
|
+
One choice in a Select. Its `label` (or its text, when that's all it holds)
|
|
127
|
+
shows in the field once chosen and is matched when typing to jump.
|
|
128
|
+
|
|
129
|
+
| Prop | Type | Default | Description |
|
|
130
|
+
| --- | --- | --- | --- |
|
|
131
|
+
| `value` (required) | `string` | | |
|
|
132
|
+
| `className` | `string` | | |
|
|
133
|
+
| `disabled` | `boolean` | | |
|
|
134
|
+
| `label` | `string` | | Text for the field and for typing to jump (defaults to the item's text). |
|
|
135
|
+
|
|
136
|
+
### SelectGroup
|
|
137
|
+
|
|
138
|
+
A labelled group of SelectItems.
|
|
139
|
+
|
|
140
|
+
| Prop | Type | Default | Description |
|
|
141
|
+
| --- | --- | --- | --- |
|
|
142
|
+
| `label` (required) | `ReactNode` | | |
|
|
143
|
+
|
|
144
|
+
### SelectSeparator
|
|
145
|
+
|
|
146
|
+
A hairline between items or groups.
|
|
147
|
+
|
|
148
|
+
No props of its own; it accepts the props of the element it renders.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Separator
|
|
2
|
+
|
|
3
|
+
A hairline between groups of content, across or upright.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Separator } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Separator } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
/** Across between sections, and upright between items in a row. */
|
|
17
|
+
export default function Default() {
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex w-full max-w-xs flex-col gap-3 text-sm">
|
|
20
|
+
<div>
|
|
21
|
+
<p className="mt-0! font-medium">September 2026</p>
|
|
22
|
+
<p className="text-muted-foreground mt-0!">Budgets, entries and goals</p>
|
|
23
|
+
</div>
|
|
24
|
+
<Separator />
|
|
25
|
+
<div className="text-muted-foreground flex h-5 items-center gap-3">
|
|
26
|
+
<span>Spent $2,742</span>
|
|
27
|
+
<Separator orientation="vertical" />
|
|
28
|
+
<span>Left $1,258</span>
|
|
29
|
+
<Separator orientation="vertical" />
|
|
30
|
+
<span>12 entries</span>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Stats
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { Card, Separator } from '@rinzai/zen';
|
|
41
|
+
|
|
42
|
+
/** Upright between figures in a card: it takes the row's height, so it spans each label and value. */
|
|
43
|
+
export default function Stats() {
|
|
44
|
+
return (
|
|
45
|
+
<Card className="flex w-full max-w-md items-stretch justify-between gap-4 py-4">
|
|
46
|
+
{[
|
|
47
|
+
['Income', '$6,200'],
|
|
48
|
+
['Spent', '$4,180'],
|
|
49
|
+
['Saved', '$2,020'],
|
|
50
|
+
].map(([label, value], i) => (
|
|
51
|
+
<div key={label} className="contents">
|
|
52
|
+
{i > 0 && <Separator orientation="vertical" decorative />}
|
|
53
|
+
<div className="flex flex-1 flex-col gap-1">
|
|
54
|
+
<span className="text-muted-foreground text-xs">{label}</span>
|
|
55
|
+
<span className="text-lg font-semibold tabular-nums">{value}</span>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
))}
|
|
59
|
+
</Card>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## API reference
|
|
65
|
+
|
|
66
|
+
### Separator
|
|
67
|
+
|
|
68
|
+
A hairline between groups of content, across (the default) or upright
|
|
69
|
+
(`orientation="vertical"`, e.g. between toolbar buttons; it takes its
|
|
70
|
+
container's height). Screen readers hear it only when it divides something
|
|
71
|
+
meaningful: pass `decorative` for one that's just visual.
|
|
72
|
+
|
|
73
|
+
| Prop | Type | Default | Description |
|
|
74
|
+
| --- | --- | --- | --- |
|
|
75
|
+
| `decorative` | `boolean` | | Only visual: hidden from screen readers. |
|
|
76
|
+
| `orientation` | `"horizontal" \| "vertical"` | `horizontal` | |
|