@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,51 @@
|
|
|
1
|
+
# Header
|
|
2
|
+
|
|
3
|
+
A sticky glass bar across the top of the page.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Header } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Header, Pill, PillIndicator, Pills, ThemeToggle } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
export default function Default() {
|
|
17
|
+
return (
|
|
18
|
+
<div className="w-full overflow-hidden rounded-xl">
|
|
19
|
+
<Header className="static">
|
|
20
|
+
<span className="text-lg font-semibold tracking-tight">Zen</span>
|
|
21
|
+
<nav aria-label="Main">
|
|
22
|
+
<Pills>
|
|
23
|
+
<PillIndicator />
|
|
24
|
+
<Pill href="#docs" active>
|
|
25
|
+
Docs
|
|
26
|
+
</Pill>
|
|
27
|
+
<Pill href="#components">Components</Pill>
|
|
28
|
+
</Pills>
|
|
29
|
+
</nav>
|
|
30
|
+
<ThemeToggle />
|
|
31
|
+
</Header>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## API reference
|
|
38
|
+
|
|
39
|
+
### Header
|
|
40
|
+
|
|
41
|
+
A sticky bar across the top of the page: the page colour, translucent and
|
|
42
|
+
heavily blurred, over a hairline. Clears the notch on phones, where it's
|
|
43
|
+
solid instead (a live blur over scrolling content stutters there).
|
|
44
|
+
|
|
45
|
+
Its first stop for the keyboard is a "Skip to content" link, hidden until
|
|
46
|
+
focused, that moves focus to the page's <main> past the bar's links (WCAG
|
|
47
|
+
2.4.1). Change its text with `skipLabel`, or pass null to leave it out.
|
|
48
|
+
|
|
49
|
+
| Prop | Type | Default | Description |
|
|
50
|
+
| --- | --- | --- | --- |
|
|
51
|
+
| `skipLabel` | `string \| null` | | The skip link's text; null for no skip link (e.g. when the page has its own). |
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Hover Card
|
|
2
|
+
|
|
3
|
+
A preview of what a link points to, like a person or an account: a glass card once the pointer rests on the link, or while it has keyboard focus.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { HoverCard, HoverCardTrigger, HoverCardContent } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Avatar, HoverCard, HoverCardContent, HoverCardTrigger } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
/** Rest the pointer on the name, or tab to it: a preview of who it is. */
|
|
17
|
+
export default function Default() {
|
|
18
|
+
return (
|
|
19
|
+
<p className="mt-0! text-sm">
|
|
20
|
+
Paid to{' '}
|
|
21
|
+
<HoverCard>
|
|
22
|
+
<HoverCardTrigger href="#priya">Priya Sharma</HoverCardTrigger>
|
|
23
|
+
<HoverCardContent className="flex gap-3">
|
|
24
|
+
<Avatar name="Priya Sharma" />
|
|
25
|
+
<div className="flex flex-col gap-1">
|
|
26
|
+
<span className="font-medium">Priya Sharma</span>
|
|
27
|
+
<span className="text-muted-foreground text-xs">Flatmate · shares rent and bills</span>
|
|
28
|
+
<span className="text-muted-foreground mt-1 text-xs tabular-nums">
|
|
29
|
+
12 payments this year, $4,210 in all
|
|
30
|
+
</span>
|
|
31
|
+
</div>
|
|
32
|
+
</HoverCardContent>
|
|
33
|
+
</HoverCard>{' '}
|
|
34
|
+
on 3 October.
|
|
35
|
+
</p>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Trend
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { HoverCard, HoverCardContent, HoverCardTrigger, Sparkline } from '@rinzai/zen';
|
|
44
|
+
|
|
45
|
+
const MERCHANTS = [
|
|
46
|
+
{ name: 'Corner Market', spent: [120, 96, 140, 118, 152, 131], total: '$757' },
|
|
47
|
+
{ name: 'City Transit', spent: [64, 70, 58, 66, 61, 72], total: '$391' },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
/** More than a name: each merchant's spending over six months, a glance away. */
|
|
51
|
+
export default function Trend() {
|
|
52
|
+
return (
|
|
53
|
+
<ul className="flex flex-col gap-2 text-sm">
|
|
54
|
+
{MERCHANTS.map((m) => (
|
|
55
|
+
<li key={m.name}>
|
|
56
|
+
<HoverCard>
|
|
57
|
+
<HoverCardTrigger href={`#${m.name}`}>{m.name}</HoverCardTrigger>
|
|
58
|
+
<HoverCardContent className="flex w-56 flex-col gap-2">
|
|
59
|
+
<div className="flex items-baseline justify-between">
|
|
60
|
+
<span className="font-medium">{m.name}</span>
|
|
61
|
+
<span className="text-muted-foreground text-xs tabular-nums">{m.total}</span>
|
|
62
|
+
</div>
|
|
63
|
+
<Sparkline
|
|
64
|
+
values={m.spent}
|
|
65
|
+
area
|
|
66
|
+
className="h-10 w-full"
|
|
67
|
+
aria-label={`${m.name}, last six months`}
|
|
68
|
+
/>
|
|
69
|
+
<span className="text-muted-foreground text-xs">Last six months</span>
|
|
70
|
+
</HoverCardContent>
|
|
71
|
+
</HoverCard>
|
|
72
|
+
</li>
|
|
73
|
+
))}
|
|
74
|
+
</ul>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## API reference
|
|
80
|
+
|
|
81
|
+
### HoverCard
|
|
82
|
+
|
|
83
|
+
A preview of what a link points to, e.g. a person or an account: a glass
|
|
84
|
+
card shown after the pointer rests on the link (`openDelay`), or while it
|
|
85
|
+
has keyboard focus, and kept while the pointer or focus is on the card.
|
|
86
|
+
Escape hides it. What it shows is extra, out of the tab order: everything in
|
|
87
|
+
it should be reachable by following the link too. Put a HoverCardTrigger and
|
|
88
|
+
a HoverCardContent inside (it can go in a paragraph).
|
|
89
|
+
|
|
90
|
+
| Prop | Type | Default | Description |
|
|
91
|
+
| --- | --- | --- | --- |
|
|
92
|
+
| `closeDelay` | `number` | `200` | How long it stays after the pointer leaves, in ms, so the pointer can reach it. Default 200. |
|
|
93
|
+
| `openDelay` | `number` | `500` | How long the pointer rests on the trigger before the card shows, in ms. Default 500. |
|
|
94
|
+
|
|
95
|
+
### HoverCardTrigger
|
|
96
|
+
|
|
97
|
+
What the card previews: a link by default (give it `href`). With `asChild`, your own element (e.g. your router's link).
|
|
98
|
+
|
|
99
|
+
| Prop | Type | Default | Description |
|
|
100
|
+
| --- | --- | --- | --- |
|
|
101
|
+
| `asChild` | `boolean` | | Put the trigger's props on your own element instead of rendering a link. |
|
|
102
|
+
|
|
103
|
+
### HoverCardContent
|
|
104
|
+
|
|
105
|
+
The card: glass, below the trigger (above it if there's no room), lined up with its start by default.
|
|
106
|
+
|
|
107
|
+
| Prop | Type | Default | Description |
|
|
108
|
+
| --- | --- | --- | --- |
|
|
109
|
+
| `align` | `"center" \| "end" \| "start"` | | Lined up with the trigger's start (default) or end edge, or centred on it. |
|
|
110
|
+
| `offset` | `number` | | Gap from the trigger, in px. |
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Icon Picker
|
|
2
|
+
|
|
3
|
+
An icon and a colour for something, like a spending category, from a grid in a popover.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { IconPicker } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Field, IconPicker, Input } from '@rinzai/zen';
|
|
15
|
+
import { useState } from 'react';
|
|
16
|
+
|
|
17
|
+
const ICONS = [
|
|
18
|
+
{ value: 'groceries', label: 'Groceries', icon: '🛒' },
|
|
19
|
+
{ value: 'dining', label: 'Dining', icon: '🍽️' },
|
|
20
|
+
{ value: 'transport', label: 'Transport', icon: '🚆' },
|
|
21
|
+
{ value: 'home', label: 'Home', icon: '🏠' },
|
|
22
|
+
{ value: 'bills', label: 'Bills', icon: '💡' },
|
|
23
|
+
{ value: 'gifts', label: 'Gifts', icon: '🎁' },
|
|
24
|
+
{ value: 'health', label: 'Health', icon: '💊' },
|
|
25
|
+
{ value: 'fun', label: 'Fun', icon: '🎬' },
|
|
26
|
+
{ value: 'travel', label: 'Travel', icon: '✈️' },
|
|
27
|
+
{ value: 'books', label: 'Books', icon: '📚' },
|
|
28
|
+
{ value: 'pets', label: 'Pets', icon: '🐾' },
|
|
29
|
+
{ value: 'work', label: 'Work', icon: '💼' },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/** A new category: its icon and colour beside its name. */
|
|
33
|
+
export default function Default() {
|
|
34
|
+
const [look, setLook] = useState<{ icon: string; color?: string }>({ icon: 'groceries', color: 'teal' });
|
|
35
|
+
return (
|
|
36
|
+
<div className="flex w-full max-w-sm items-end gap-3">
|
|
37
|
+
<IconPicker label="Category icon" icons={ICONS} value={look} onValueChange={setLook} />
|
|
38
|
+
<Field label="Name" className="flex-1">
|
|
39
|
+
<Input defaultValue="Groceries" />
|
|
40
|
+
</Field>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## API reference
|
|
47
|
+
|
|
48
|
+
### IconPicker
|
|
49
|
+
|
|
50
|
+
Pick an icon and a colour for something, e.g. a spending category: a button
|
|
51
|
+
showing the choice opens a grid of icons and a row of colours. Icons are
|
|
52
|
+
yours (emoji work); colours default to the chart palette. Both are radio
|
|
53
|
+
groups, so arrow keys move through them.
|
|
54
|
+
|
|
55
|
+
| Prop | Type | Default | Description |
|
|
56
|
+
| --- | --- | --- | --- |
|
|
57
|
+
| `icons` (required) | `{ value: string; label: string; icon: ReactNode; }[]` | | |
|
|
58
|
+
| `label` (required) | `string` | | What it's the icon of, e.g. "Category icon"; the button also says the current choice. |
|
|
59
|
+
| `asChild` | `boolean` | | Put the trigger's props on your own button element instead of rendering one. |
|
|
60
|
+
| `className` | `string` | | |
|
|
61
|
+
| `colors` | `IconColor[]` | `[ { value: 'blue', color: 'var(--chart-1)' }, { value: 'orange', color: 'var(--chart-2)' }, { value: 'teal', color: 'var(--chart-3)' }, { value: 'amber', color: 'var(--chart-4)' }, { value: 'pink', color: 'var(--chart-5)' }, { value: 'green', color: 'var(--chart-6)' }, { value: 'violet', color: 'var(--chart-7)' }, { value: 'red', color: 'var(--chart-8)' }, ]` | Default: ICON_COLORS, the chart palette. |
|
|
62
|
+
| `defaultValue` | `{ icon: string; color?: string; }` | `{ icon: '' }` | The first choice, when the picker keeps its own. |
|
|
63
|
+
| `name` | `string` | | Form field name: the icon is submitted under it, the colour under `${name}-color`. |
|
|
64
|
+
| `onValueChange` | `((value: { icon: string; color?: string; }) => void)` | | |
|
|
65
|
+
| `value` | `{ icon: string; color?: string; }` | | The choice, when you keep it. |
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Input Group
|
|
2
|
+
|
|
3
|
+
A field with slots: put symbols, icons, units, buttons or menus before and after the input.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { InputGroup, InputGroupInput, InputGroupAddon } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { InputGroup, InputGroupAddon, InputGroupInput } from '@rinzai/zen';
|
|
15
|
+
// Icons from Heroicons (https://heroicons.com): Search (magnifying-glass).
|
|
16
|
+
|
|
17
|
+
export default function Default() {
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex w-80 flex-col gap-3">
|
|
20
|
+
<InputGroup>
|
|
21
|
+
<InputGroupAddon>
|
|
22
|
+
<Search />
|
|
23
|
+
</InputGroupAddon>
|
|
24
|
+
<InputGroupInput placeholder="Search entries" />
|
|
25
|
+
</InputGroup>
|
|
26
|
+
<InputGroup>
|
|
27
|
+
<InputGroupAddon>https://</InputGroupAddon>
|
|
28
|
+
<InputGroupInput placeholder="zen.rinzai" />
|
|
29
|
+
<InputGroupAddon>.dev</InputGroupAddon>
|
|
30
|
+
</InputGroup>
|
|
31
|
+
<InputGroup>
|
|
32
|
+
<InputGroupInput placeholder="72" inputMode="numeric" className="tabular-nums" />
|
|
33
|
+
<InputGroupAddon>kg</InputGroupAddon>
|
|
34
|
+
</InputGroup>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### With button
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { Button, InputGroup, InputGroupAddon, InputGroupInput } from '@rinzai/zen';
|
|
44
|
+
import { useState } from 'react';
|
|
45
|
+
|
|
46
|
+
/** Controls in addons keep their own clicks; the text around them focuses the input. */
|
|
47
|
+
export default function WithButton() {
|
|
48
|
+
const [query, setQuery] = useState('rent');
|
|
49
|
+
return (
|
|
50
|
+
<InputGroup className="w-80 pr-1">
|
|
51
|
+
<InputGroupAddon>Filter</InputGroupAddon>
|
|
52
|
+
<InputGroupInput value={query} onChange={(e) => setQuery(e.target.value)} aria-label="Filter" />
|
|
53
|
+
{query && (
|
|
54
|
+
<InputGroupAddon>
|
|
55
|
+
<Button variant="ghost" size="sm" className="h-7" onClick={() => setQuery('')}>
|
|
56
|
+
Clear
|
|
57
|
+
</Button>
|
|
58
|
+
</InputGroupAddon>
|
|
59
|
+
)}
|
|
60
|
+
</InputGroup>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## API reference
|
|
66
|
+
|
|
67
|
+
### InputGroup
|
|
68
|
+
|
|
69
|
+
A field with slots: compose an InputGroupInput with InputGroupAddons before
|
|
70
|
+
or after it (a currency symbol, an icon, a unit, a button, a menu trigger).
|
|
71
|
+
The whole group looks like one field and glows while anything in it has
|
|
72
|
+
focus; clicking an addon's text focuses the input.
|
|
73
|
+
|
|
74
|
+
| Prop | Type | Default | Description |
|
|
75
|
+
| --- | --- | --- | --- |
|
|
76
|
+
| `invalid` | `boolean` | | Show the field in its error state (e.g. alongside aria-invalid on the input). |
|
|
77
|
+
| `size` | `"default" \| "lg" \| "sm"` | `default` | Height, matching Button's: sm (32px), default (40px) or lg (44px). |
|
|
78
|
+
|
|
79
|
+
### InputGroupInput
|
|
80
|
+
|
|
81
|
+
The input inside an InputGroup: borderless, filling the space between the addons.
|
|
82
|
+
|
|
83
|
+
No props of its own; it accepts the props of the element it renders.
|
|
84
|
+
|
|
85
|
+
### InputGroupAddon
|
|
86
|
+
|
|
87
|
+
Content before or after the input: text, an icon, a button or a menu trigger.
|
|
88
|
+
|
|
89
|
+
No props of its own; it accepts the props of the element it renders.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Input
|
|
2
|
+
|
|
3
|
+
Text fields in faint glass that glow on focus.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Input } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### States
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Input } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
export default function States() {
|
|
17
|
+
return (
|
|
18
|
+
<div className="flex w-72 flex-col gap-3">
|
|
19
|
+
<Input aria-label="Empty" placeholder="Placeholder" />
|
|
20
|
+
<Input aria-label="Filled" defaultValue="With a value" />
|
|
21
|
+
<Input aria-label="Disabled" defaultValue="Disabled" disabled />
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## API reference
|
|
28
|
+
|
|
29
|
+
### Input
|
|
30
|
+
|
|
31
|
+
| Prop | Type | Default | Description |
|
|
32
|
+
| --- | --- | --- | --- |
|
|
33
|
+
| `size` | `"default" \| "lg" \| "sm"` | | Height, matching Button's: sm (32px), default (40px) or lg (44px). |
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Inset
|
|
2
|
+
|
|
3
|
+
A faint box set into a card: a tile for one figure, or a bordered panel for a result, tinted for good or bad news.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Inset } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Card, CardDescription, CardHeader, CardTitle, Inset } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
/** Tiles for single figures inside a card, and a tinted panel for a result. */
|
|
17
|
+
export default function Default() {
|
|
18
|
+
return (
|
|
19
|
+
<Card className="flex w-full max-w-sm flex-col gap-4">
|
|
20
|
+
<CardHeader className="mb-0">
|
|
21
|
+
<div className="min-w-0">
|
|
22
|
+
<CardTitle>Car loan</CardTitle>
|
|
23
|
+
<CardDescription>$32,000 at 8.5% · 5 years</CardDescription>
|
|
24
|
+
</div>
|
|
25
|
+
</CardHeader>
|
|
26
|
+
<dl className="grid grid-cols-2 gap-3 text-sm">
|
|
27
|
+
<Inset className="flex flex-col gap-0.5">
|
|
28
|
+
<dt className="text-muted-foreground text-xs">Next payment</dt>
|
|
29
|
+
<dd className="font-medium tabular-nums">$656</dd>
|
|
30
|
+
</Inset>
|
|
31
|
+
<Inset tone="positive" className="flex flex-col gap-0.5">
|
|
32
|
+
<dt className="text-muted-foreground text-xs">Debt-free</dt>
|
|
33
|
+
<dd className="font-medium">Mar 2031</dd>
|
|
34
|
+
</Inset>
|
|
35
|
+
</dl>
|
|
36
|
+
<Inset bordered tone="positive" className="text-sm" role="status">
|
|
37
|
+
Paying $200 more a month saves <strong>$2,140</strong> in interest.
|
|
38
|
+
</Inset>
|
|
39
|
+
</Card>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## API reference
|
|
45
|
+
|
|
46
|
+
### Inset
|
|
47
|
+
|
|
48
|
+
A faint box set into a card or dialog: a small tile for one figure (e.g.
|
|
49
|
+
"Next EMI"), or with `bordered`, a panel for a result or a note. `tone`
|
|
50
|
+
tints it for good or bad news. With `asChild`, your own element (a <p>, a
|
|
51
|
+
status region) gets its look.
|
|
52
|
+
|
|
53
|
+
| Prop | Type | Default | Description |
|
|
54
|
+
| --- | --- | --- | --- |
|
|
55
|
+
| `asChild` | `boolean` | | Put the look on your single child element instead of a <div>. |
|
|
56
|
+
| `bordered` | `boolean` | `false` | A bordered panel instead of a small tile. |
|
|
57
|
+
| `tone` | `"default" \| "negative" \| "positive"` | `default` | |
|
package/dist/docs/kbd.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Kbd
|
|
2
|
+
|
|
3
|
+
Keys on the keyboard, for shortcuts in hints and tooltips.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Kbd } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Kbd } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
/** Shortcuts in a sentence: each key its own Kbd. */
|
|
17
|
+
export default function Default() {
|
|
18
|
+
return (
|
|
19
|
+
<p className="text-muted-foreground mt-0! flex flex-wrap items-center gap-1 text-sm">
|
|
20
|
+
Search with <Kbd>⌘</Kbd>
|
|
21
|
+
<Kbd>K</Kbd> on a Mac, <Kbd>Ctrl</Kbd>
|
|
22
|
+
<Kbd>K</Kbd> elsewhere; close with <Kbd>Esc</Kbd>.
|
|
23
|
+
</p>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## API reference
|
|
29
|
+
|
|
30
|
+
### Kbd
|
|
31
|
+
|
|
32
|
+
A key on the keyboard, for shortcuts in hints and tooltips: <Kbd>⌘</Kbd><Kbd>K</Kbd>. Sized from the text around it.
|
|
33
|
+
|
|
34
|
+
No props of its own; it accepts the props of the element it renders.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Keypad
|
|
2
|
+
|
|
3
|
+
A number pad for typing an amount on a phone.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Keypad } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Button, Keypad } from '@rinzai/zen';
|
|
15
|
+
import { useState } from 'react';
|
|
16
|
+
|
|
17
|
+
/** Entering an amount on a phone: the figure above, the pad below. */
|
|
18
|
+
export default function Default() {
|
|
19
|
+
const [typed, setTyped] = useState('');
|
|
20
|
+
const amount = Number(typed || 0);
|
|
21
|
+
return (
|
|
22
|
+
<div className="flex w-full max-w-72 flex-col gap-4">
|
|
23
|
+
<p className="mt-0! text-center text-4xl font-semibold tracking-tight tabular-nums" aria-live="polite">
|
|
24
|
+
${typed || '0'}
|
|
25
|
+
</p>
|
|
26
|
+
<Keypad value={typed} onValueChange={setTyped} />
|
|
27
|
+
<Button disabled={amount <= 0} onClick={() => setTyped('')}>
|
|
28
|
+
Add ${amount.toFixed(2)}
|
|
29
|
+
</Button>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## API reference
|
|
36
|
+
|
|
37
|
+
### Keypad
|
|
38
|
+
|
|
39
|
+
A number pad for typing an amount on a phone, where the system keyboard
|
|
40
|
+
covers half the screen. The value is the text typed ("12.5"), so a
|
|
41
|
+
trailing point or zero stays until you convert it: no leading zeros, one
|
|
42
|
+
point, at most `decimals` digits after it.
|
|
43
|
+
|
|
44
|
+
| Prop | Type | Default | Description |
|
|
45
|
+
| --- | --- | --- | --- |
|
|
46
|
+
| `decimals` | `number` | `2` | Digits allowed after the point (default 2; 0 hides the point). |
|
|
47
|
+
| `defaultValue` | `string` | | What's typed at first, when the keypad keeps its own. |
|
|
48
|
+
| `deleteLabel` | `string` | | The delete key's name for screen readers. |
|
|
49
|
+
| `maxLength` | `number` | `12` | |
|
|
50
|
+
| `onValueChange` | `((value: string) => void)` | | |
|
|
51
|
+
| `value` | `string` | | What's been typed, e.g. "12.5", when you keep it. |
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Load More
|
|
2
|
+
|
|
3
|
+
The end of a long list that has more: loads the next page when it scrolls into view, or on a press.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { LoadMore } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { LoadMore } from '@rinzai/zen';
|
|
15
|
+
import { useState } from 'react';
|
|
16
|
+
|
|
17
|
+
const PAGE = 12;
|
|
18
|
+
const TOTAL = 60;
|
|
19
|
+
const entry = (i: number) => ({ id: i, label: `Entry ${i + 1}`, amount: `−$${((i * 37) % 180) + 6}` });
|
|
20
|
+
|
|
21
|
+
/** Scroll to the end and the next page loads; there's a button for it too. */
|
|
22
|
+
export default function Default() {
|
|
23
|
+
const [entries, setEntries] = useState(() => Array.from({ length: PAGE }, (_, i) => entry(i)));
|
|
24
|
+
const [loading, setLoading] = useState(false);
|
|
25
|
+
const more = () => {
|
|
26
|
+
setLoading(true);
|
|
27
|
+
// Stands in for a request.
|
|
28
|
+
setTimeout(() => {
|
|
29
|
+
setEntries((es) => [...es, ...Array.from({ length: PAGE }, (_, i) => entry(es.length + i))]);
|
|
30
|
+
setLoading(false);
|
|
31
|
+
}, 700);
|
|
32
|
+
};
|
|
33
|
+
return (
|
|
34
|
+
<div className="glass glow-edge h-80 w-full max-w-sm overflow-y-auto rounded-xl">
|
|
35
|
+
<ul className="rise-list divide-tint/[0.06] divide-y">
|
|
36
|
+
{entries.map((e) => (
|
|
37
|
+
<li key={e.id} className="flex justify-between px-4 py-2.5 text-sm">
|
|
38
|
+
<span>{e.label}</span>
|
|
39
|
+
<span className="tabular-nums">{e.amount}</span>
|
|
40
|
+
</li>
|
|
41
|
+
))}
|
|
42
|
+
</ul>
|
|
43
|
+
<LoadMore onLoadMore={more} loading={loading} hasMore={entries.length < TOTAL} />
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## API reference
|
|
50
|
+
|
|
51
|
+
### LoadMore
|
|
52
|
+
|
|
53
|
+
The end of a long list that has more: a "Load more" button, which also
|
|
54
|
+
presses itself when it scrolls into view (`auto`, default). While `loading`
|
|
55
|
+
it shows a spinner and won't ask again; once there's nothing left
|
|
56
|
+
(`hasMore` false) it's gone. Put it after the list, or in a table's last row.
|
|
57
|
+
|
|
58
|
+
| Prop | Type | Default | Description |
|
|
59
|
+
| --- | --- | --- | --- |
|
|
60
|
+
| `hasMore` (required) | `boolean` | | |
|
|
61
|
+
| `onLoadMore` (required) | `() => void` | | |
|
|
62
|
+
| `auto` | `boolean` | `true` | Load when it scrolls into view, not only on press (default true). |
|
|
63
|
+
| `className` | `string` | | |
|
|
64
|
+
| `label` | `string` | | |
|
|
65
|
+
| `loading` | `boolean` | `false` | While true, it shows a spinner and won't ask for more. |
|
|
66
|
+
| `loadingLabel` | `string` | | |
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Locale Provider
|
|
2
|
+
|
|
3
|
+
The language for everything inside: how dates, numbers and money read, and every word components show or announce. Without it, "en-US" and English.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { LocaleProvider, useLocale, useMessages, ENGLISH } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### French
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { DatePicker, Field, LocaleProvider, MoneyInput, RepeatPicker } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
const FRENCH = {
|
|
17
|
+
pickDate: 'Choisir une date',
|
|
18
|
+
chooseDate: 'Choisir la date',
|
|
19
|
+
clear: 'Effacer',
|
|
20
|
+
previousMonth: 'Mois précédent',
|
|
21
|
+
nextMonth: 'Mois suivant',
|
|
22
|
+
chooseMonthAndYear: (month: string) => `${month}, choisir le mois et l’année`,
|
|
23
|
+
enterAmount: 'Saisissez un montant.',
|
|
24
|
+
repeats: 'Répétition',
|
|
25
|
+
every: 'Tous les',
|
|
26
|
+
next: 'Prochaines :',
|
|
27
|
+
repeatUnit: (unit: 'day' | 'week' | 'month' | 'year', count: number) =>
|
|
28
|
+
({ day: 'jour', week: 'semaine', month: 'mois', year: 'an' })[unit] +
|
|
29
|
+
(count > 1 && unit !== 'month' ? 's' : ''),
|
|
30
|
+
repeatEvery: (every: number, unit: string) => (every === 1 ? `Chaque ${unit}` : `Tous les ${every} ${unit}`),
|
|
31
|
+
repeatOn: (rule: string, when: string) => `${rule} le ${when}`,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* `locale` sets how dates, numbers and money read, and the week's first day;
|
|
36
|
+
* `messages` gives the words. Anything you leave out stays English.
|
|
37
|
+
*/
|
|
38
|
+
export default function French() {
|
|
39
|
+
return (
|
|
40
|
+
<LocaleProvider locale="fr-FR" messages={FRENCH}>
|
|
41
|
+
<div className="flex w-80 flex-col gap-4">
|
|
42
|
+
<Field label="Date">
|
|
43
|
+
<DatePicker />
|
|
44
|
+
</Field>
|
|
45
|
+
<Field label="Montant">
|
|
46
|
+
<MoneyInput currency="EUR" defaultValue={128050} />
|
|
47
|
+
</Field>
|
|
48
|
+
<RepeatPicker defaultValue={{ every: 2, unit: 'week' }} start="2026-10-12" />
|
|
49
|
+
</div>
|
|
50
|
+
</LocaleProvider>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Right to left
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { Calendar, LocaleProvider, Pagination, Slider, Tab, TabList, Tabs } from '@rinzai/zen';
|
|
59
|
+
import { useState } from 'react';
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Right to left: set `dir="rtl"` on the page (or any part of it). Layout,
|
|
63
|
+
* arrow keys and arrows follow; LocaleProvider gives the locale and words.
|
|
64
|
+
*/
|
|
65
|
+
export default function RightToLeft() {
|
|
66
|
+
const [page, setPage] = useState(2);
|
|
67
|
+
return (
|
|
68
|
+
<div dir="rtl" className="flex w-80 flex-col gap-5">
|
|
69
|
+
<LocaleProvider locale="ar-EG" messages={{ previousPage: 'الصفحة السابقة', nextPage: 'الصفحة التالية' }}>
|
|
70
|
+
<Tabs defaultValue="month">
|
|
71
|
+
<TabList>
|
|
72
|
+
<Tab value="month">الشهر</Tab>
|
|
73
|
+
<Tab value="year">السنة</Tab>
|
|
74
|
+
</TabList>
|
|
75
|
+
</Tabs>
|
|
76
|
+
<Calendar defaultValue="2026-10-12" />
|
|
77
|
+
<Slider aria-label="الميزانية" defaultValue={30} />
|
|
78
|
+
<Pagination page={page} count={5} onPageChange={setPage} />
|
|
79
|
+
</LocaleProvider>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## API reference
|
|
86
|
+
|
|
87
|
+
### LocaleProvider
|
|
88
|
+
|
|
89
|
+
The language for everything inside: `locale` formats dates, numbers and
|
|
90
|
+
money and picks month and weekday names and the week's first day (a
|
|
91
|
+
component's own `locale` prop still wins), and `messages` replaces any of the
|
|
92
|
+
words components show or announce (see ENGLISH for them all; the rest stay
|
|
93
|
+
English). Without it, components use "en-US" and English. Nest one to change
|
|
94
|
+
part of the page.
|
|
95
|
+
|
|
96
|
+
| Prop | Type | Default | Description |
|
|
97
|
+
| --- | --- | --- | --- |
|
|
98
|
+
| `locale` | `string` | | e.g. "en-IN", "fr-FR". Defaults to the enclosing provider's, or "en-US". |
|
|
99
|
+
| `messages` | `Partial<{ clear: string; cancel: string; close: string; dismiss: string; delete: string; remove: (item: string) => string; noResults: string; search: string; expand: string; collapse: string; minimum: string; ... 109 more ...; timeOf: (time: string, total: string) => string; }>` | | Any of the words to replace; the others are kept. |
|