@rinzai/zen 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +8 -2
- 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/number-field/index.d.ts +18 -2
- package/dist/number-field/index.d.ts.map +1 -1
- package/dist/number-field/index.js +65 -51
- 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,83 @@
|
|
|
1
|
+
# Toggle Group
|
|
2
|
+
|
|
3
|
+
Buttons that stay pressed, in a row: any number of them, like filters, or at most one. One stop in the tab order; the arrow keys go through them.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { ToggleGroup, ToggleGroupItem } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { ToggleGroup, ToggleGroupItem } from '@rinzai/zen';
|
|
15
|
+
import { useState } from 'react';
|
|
16
|
+
|
|
17
|
+
/** Filters: any number on at once. */
|
|
18
|
+
export default function Default() {
|
|
19
|
+
const [kinds, setKinds] = useState<readonly string[]>(['expense', 'income']);
|
|
20
|
+
return (
|
|
21
|
+
<div className="flex flex-col items-center gap-3">
|
|
22
|
+
<ToggleGroup multiple aria-label="Show" value={kinds} onValueChange={setKinds}>
|
|
23
|
+
<ToggleGroupItem value="expense">Expenses</ToggleGroupItem>
|
|
24
|
+
<ToggleGroupItem value="income">Income</ToggleGroupItem>
|
|
25
|
+
<ToggleGroupItem value="transfer">Transfers</ToggleGroupItem>
|
|
26
|
+
</ToggleGroup>
|
|
27
|
+
<p className="text-muted-foreground mt-0! text-sm">
|
|
28
|
+
{kinds.length ? `Showing ${kinds.length} of 3 kinds` : 'Showing everything'}
|
|
29
|
+
</p>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Single
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { ToggleGroup, ToggleGroupItem } from '@rinzai/zen';
|
|
39
|
+
// Icons from Heroicons (https://heroicons.com): ChartBar (chart-bar), ListBullet (list-bullet), Squares (squares-2x2).
|
|
40
|
+
|
|
41
|
+
/** At most one: pressing the pressed one releases it. Icon-only items carry an aria-label. */
|
|
42
|
+
export default function Single() {
|
|
43
|
+
return (
|
|
44
|
+
<ToggleGroup aria-label="Group by" defaultValue="list">
|
|
45
|
+
<ToggleGroupItem value="list" aria-label="As a list" className="px-2">
|
|
46
|
+
<ListBullet className="size-4" />
|
|
47
|
+
</ToggleGroupItem>
|
|
48
|
+
<ToggleGroupItem value="cards" aria-label="As cards" className="px-2">
|
|
49
|
+
<Squares className="size-4" />
|
|
50
|
+
</ToggleGroupItem>
|
|
51
|
+
<ToggleGroupItem value="chart" aria-label="As a chart" className="px-2">
|
|
52
|
+
<ChartBar className="size-4" />
|
|
53
|
+
</ToggleGroupItem>
|
|
54
|
+
</ToggleGroup>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## API reference
|
|
60
|
+
|
|
61
|
+
### ToggleGroup
|
|
62
|
+
|
|
63
|
+
Buttons that stay pressed, in a row: any number of them (`multiple`, e.g.
|
|
64
|
+
filters), or at most one, which can be released again. For exactly one
|
|
65
|
+
always chosen, use Segmented. ←→ move between them, Home and End jump to
|
|
66
|
+
the ends; Space or Enter presses.
|
|
67
|
+
|
|
68
|
+
| Prop | Type | Default | Description |
|
|
69
|
+
| --- | --- | --- | --- |
|
|
70
|
+
| `children` | `ReactNode` | | The ToggleGroupItems. |
|
|
71
|
+
| `defaultValue` | `string \| readonly string[] \| null` | | |
|
|
72
|
+
| `multiple` | `boolean` | | At most one pressed (the default); pressing another releases it. Any number pressed. |
|
|
73
|
+
| `name` | `string` | | Form field name: each pressed item's value is submitted under it. |
|
|
74
|
+
| `onValueChange` | `((value: string \| null) => void) \| ((value: readonly string[]) => void)` | | |
|
|
75
|
+
| `value` | `string \| readonly string[] \| null` | | The pressed item's value, or null for none, when you keep it. |
|
|
76
|
+
|
|
77
|
+
### ToggleGroupItem
|
|
78
|
+
|
|
79
|
+
One button of a ToggleGroup: text, an icon (give it an aria-label), or both.
|
|
80
|
+
|
|
81
|
+
| Prop | Type | Default | Description |
|
|
82
|
+
| --- | --- | --- | --- |
|
|
83
|
+
| `value` (required) | `string` | | |
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Toggle
|
|
2
|
+
|
|
3
|
+
An on/off switch.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Toggle } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Toggle } from '@rinzai/zen';
|
|
15
|
+
import { useState } from 'react';
|
|
16
|
+
|
|
17
|
+
export default function Default() {
|
|
18
|
+
const [on, setOn] = useState(true);
|
|
19
|
+
return (
|
|
20
|
+
<label className="flex w-64 items-center justify-between">
|
|
21
|
+
Repeats every month
|
|
22
|
+
<Toggle checked={on} onCheckedChange={setOn} />
|
|
23
|
+
</label>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## API reference
|
|
29
|
+
|
|
30
|
+
### Toggle
|
|
31
|
+
|
|
32
|
+
On/off switch. A native checkbox with the switch role underneath, so forms
|
|
33
|
+
and screen readers work as they do for any checkbox. Space and Enter both
|
|
34
|
+
flip it (Enter doesn't submit a surrounding form), as for a switch button.
|
|
35
|
+
|
|
36
|
+
| Prop | Type | Default | Description |
|
|
37
|
+
| --- | --- | --- | --- |
|
|
38
|
+
| `onCheckedChange` | `((checked: boolean) => void)` | | |
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Toolbar
|
|
2
|
+
|
|
3
|
+
A row of controls for one thing, like a table’s: one stop for Tab, and the arrow keys through its buttons, toggles and fields.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Toolbar, ToolbarSeparator } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Button, Toolbar, ToolbarSeparator, ToggleGroup, ToggleGroupItem } from '@rinzai/zen';
|
|
15
|
+
// Icons from Heroicons (https://heroicons.com): ChartBar (chart-bar), ListBullet (list-bullet), Settings (cog-6-tooth), Table (table-cells).
|
|
16
|
+
|
|
17
|
+
/** A table's controls: one stop for Tab, and ←→ through them all, the toggle group's items too. */
|
|
18
|
+
export default function Default() {
|
|
19
|
+
return (
|
|
20
|
+
<Toolbar aria-label="Entries">
|
|
21
|
+
<ToggleGroup aria-label="View" defaultValue="table">
|
|
22
|
+
<ToggleGroupItem value="table" aria-label="Table" className="px-2">
|
|
23
|
+
<Table className="size-4" />
|
|
24
|
+
</ToggleGroupItem>
|
|
25
|
+
<ToggleGroupItem value="list" aria-label="List" className="px-2">
|
|
26
|
+
<ListBullet className="size-4" />
|
|
27
|
+
</ToggleGroupItem>
|
|
28
|
+
<ToggleGroupItem value="chart" aria-label="Chart" className="px-2">
|
|
29
|
+
<ChartBar className="size-4" />
|
|
30
|
+
</ToggleGroupItem>
|
|
31
|
+
</ToggleGroup>
|
|
32
|
+
<ToolbarSeparator />
|
|
33
|
+
<Button variant="ghost" size="sm">
|
|
34
|
+
Filter
|
|
35
|
+
</Button>
|
|
36
|
+
<Button variant="ghost" size="sm">
|
|
37
|
+
Sort
|
|
38
|
+
</Button>
|
|
39
|
+
<ToolbarSeparator />
|
|
40
|
+
<Button variant="ghost" size="icon-sm" aria-label="Table settings">
|
|
41
|
+
<Settings className="size-4" />
|
|
42
|
+
</Button>
|
|
43
|
+
</Toolbar>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Vertical
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { Button, Toolbar, ToolbarSeparator, ToggleGroup, ToggleGroupItem } from '@rinzai/zen';
|
|
52
|
+
// Icons from Heroicons (https://heroicons.com): ChartBar (chart-bar), MinusMicro (minus), PlusMicro (plus), Squares (squares-2x2).
|
|
53
|
+
|
|
54
|
+
/** Upright beside what it controls, e.g. a chart: ↑↓ move through it, and its toggle group stacks too. */
|
|
55
|
+
export default function Vertical() {
|
|
56
|
+
return (
|
|
57
|
+
<Toolbar aria-label="Chart" orientation="vertical">
|
|
58
|
+
<Button variant="ghost" size="icon-sm" aria-label="Zoom in">
|
|
59
|
+
<PlusMicro />
|
|
60
|
+
</Button>
|
|
61
|
+
<Button variant="ghost" size="icon-sm" aria-label="Zoom out">
|
|
62
|
+
<MinusMicro />
|
|
63
|
+
</Button>
|
|
64
|
+
<ToolbarSeparator />
|
|
65
|
+
<ToggleGroup aria-label="Chart type" defaultValue="bars">
|
|
66
|
+
<ToggleGroupItem value="bars" aria-label="Bars" className="px-2">
|
|
67
|
+
<ChartBar className="size-4" />
|
|
68
|
+
</ToggleGroupItem>
|
|
69
|
+
<ToggleGroupItem value="tiles" aria-label="Tiles" className="px-2">
|
|
70
|
+
<Squares className="size-4" />
|
|
71
|
+
</ToggleGroupItem>
|
|
72
|
+
</ToggleGroup>
|
|
73
|
+
</Toolbar>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## API reference
|
|
79
|
+
|
|
80
|
+
### Toolbar
|
|
81
|
+
|
|
82
|
+
A row of controls for one thing, e.g. a table's or an editor's: buttons,
|
|
83
|
+
ToggleGroups, Selects, a search field. Tab enters it once; ←→ (↑↓ when
|
|
84
|
+
`orientation="vertical"`) move between its controls, Home and End jump to
|
|
85
|
+
the ends. Divide it with ToolbarSeparator. Give it an aria-label.
|
|
86
|
+
|
|
87
|
+
| Prop | Type | Default | Description |
|
|
88
|
+
| --- | --- | --- | --- |
|
|
89
|
+
| `orientation` | `"horizontal" \| "vertical"` | `horizontal` | |
|
|
90
|
+
|
|
91
|
+
### ToolbarSeparator
|
|
92
|
+
|
|
93
|
+
A hairline between groups of a Toolbar's controls, across its direction.
|
|
94
|
+
|
|
95
|
+
| Prop | Type | Default | Description |
|
|
96
|
+
| --- | --- | --- | --- |
|
|
97
|
+
| `decorative` | `boolean` | | Only visual: hidden from screen readers. |
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Tooltip
|
|
2
|
+
|
|
3
|
+
A short glass hint on hover or keyboard focus, describing its trigger to screen readers.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Tooltip, TooltipTrigger, TooltipContent } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Badge, Tooltip, TooltipContent, TooltipTrigger } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
/** Shows on hover and on keyboard focus; the trigger is described by it for screen readers. */
|
|
17
|
+
export default function Default() {
|
|
18
|
+
return (
|
|
19
|
+
<Tooltip>
|
|
20
|
+
<TooltipTrigger className="rounded-md">
|
|
21
|
+
<Badge variant="secondary">Beta</Badge>
|
|
22
|
+
</TooltipTrigger>
|
|
23
|
+
<TooltipContent>
|
|
24
|
+
<div className="p-3 text-sm">
|
|
25
|
+
<p className="font-medium">Beta feature</p>
|
|
26
|
+
<p className="text-muted-foreground mt-0.5!">This feature is still in preview and may change.</p>
|
|
27
|
+
</div>
|
|
28
|
+
</TooltipContent>
|
|
29
|
+
</Tooltip>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## API reference
|
|
35
|
+
|
|
36
|
+
### Tooltip
|
|
37
|
+
|
|
38
|
+
A short hint shown while the pointer is over its trigger or the trigger has
|
|
39
|
+
keyboard focus; Escape hides it. The trigger is described by it for screen
|
|
40
|
+
readers. Put a TooltipTrigger and a TooltipContent inside. For anything to
|
|
41
|
+
click or fill in, use Popover.
|
|
42
|
+
|
|
43
|
+
No props of its own; it accepts the props of the element it renders.
|
|
44
|
+
|
|
45
|
+
### TooltipTrigger
|
|
46
|
+
|
|
47
|
+
What the tooltip describes: a button by default. With `asChild`, your own focusable element instead.
|
|
48
|
+
|
|
49
|
+
| Prop | Type | Default | Description |
|
|
50
|
+
| --- | --- | --- | --- |
|
|
51
|
+
| `asChild` | `boolean` | | Put the trigger's props on your own focusable element instead of rendering a button. |
|
|
52
|
+
|
|
53
|
+
### TooltipContent
|
|
54
|
+
|
|
55
|
+
The hint: glass, below the trigger (above it if there's no room), centred on it by default.
|
|
56
|
+
|
|
57
|
+
| Prop | Type | Default | Description |
|
|
58
|
+
| --- | --- | --- | --- |
|
|
59
|
+
| `align` | `"center" \| "end" \| "start"` | `center` | Centred on the trigger (default), or lined up with its start or end edge. |
|
|
60
|
+
| `offset` | `number` | `5` | Gap from the trigger, in px. |
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# Tree Table
|
|
2
|
+
|
|
3
|
+
Rows that open into child rows, animated, built from useTree and the Table parts. With virtual rows and editable cells, enough for a budget planner.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { TreeRow, TreeCell, TreeLabel, TreeToggle } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Basic
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import {
|
|
15
|
+
Table,
|
|
16
|
+
TableBody,
|
|
17
|
+
TableContainer,
|
|
18
|
+
TableHead,
|
|
19
|
+
TableHeader,
|
|
20
|
+
TableRow,
|
|
21
|
+
TreeCell,
|
|
22
|
+
TreeLabel,
|
|
23
|
+
TreeRow,
|
|
24
|
+
useTree,
|
|
25
|
+
} from '@rinzai/zen';
|
|
26
|
+
|
|
27
|
+
interface Node {
|
|
28
|
+
name: string;
|
|
29
|
+
size: string;
|
|
30
|
+
children?: Node[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const FILES: Node[] = [
|
|
34
|
+
{
|
|
35
|
+
name: 'packages',
|
|
36
|
+
size: '412 kB',
|
|
37
|
+
children: [
|
|
38
|
+
{
|
|
39
|
+
name: 'button',
|
|
40
|
+
size: '6 kB',
|
|
41
|
+
children: [
|
|
42
|
+
{ name: 'index.tsx', size: '2 kB' },
|
|
43
|
+
{ name: 'index.test.tsx', size: '4 kB' },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
{ name: 'table', size: '14 kB', children: [{ name: 'index.tsx', size: '9 kB' }] },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{ name: 'docs', size: '88 kB', children: [{ name: 'app', size: '80 kB' }] },
|
|
50
|
+
{ name: 'package.json', size: '3 kB' },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
/** Nested data in, visible rows out: useTree handles opening, closing and the animation. Start collapsed with defaultExpanded={false}. */
|
|
54
|
+
export default function Basic() {
|
|
55
|
+
const tree = useTree({ items: FILES, getKey: (n) => n.name + n.size, getChildren: (n) => n.children });
|
|
56
|
+
return (
|
|
57
|
+
<TableContainer className="w-full max-w-md">
|
|
58
|
+
<Table {...tree.tableProps}>
|
|
59
|
+
<TableHeader>
|
|
60
|
+
<TableRow>
|
|
61
|
+
<TableHead>Name</TableHead>
|
|
62
|
+
<TableHead numeric>Size</TableHead>
|
|
63
|
+
</TableRow>
|
|
64
|
+
</TableHeader>
|
|
65
|
+
<TableBody>
|
|
66
|
+
{tree.rows.map((row) => (
|
|
67
|
+
<TreeRow key={row.key} row={row} tree={tree}>
|
|
68
|
+
<TreeCell className={row.hasChildren ? 'font-medium' : undefined}>
|
|
69
|
+
<TreeLabel row={row} tree={tree}>
|
|
70
|
+
{row.item.name}
|
|
71
|
+
</TreeLabel>
|
|
72
|
+
</TreeCell>
|
|
73
|
+
<TreeCell numeric className="text-muted-foreground">
|
|
74
|
+
{row.item.size}
|
|
75
|
+
</TreeCell>
|
|
76
|
+
</TreeRow>
|
|
77
|
+
))}
|
|
78
|
+
</TableBody>
|
|
79
|
+
</Table>
|
|
80
|
+
</TableContainer>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Planner
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import {
|
|
89
|
+
EditableCell,
|
|
90
|
+
formatMoney,
|
|
91
|
+
MoneyInput,
|
|
92
|
+
Table,
|
|
93
|
+
TableBody,
|
|
94
|
+
TableContainer,
|
|
95
|
+
TableFooter,
|
|
96
|
+
TableFooterCell,
|
|
97
|
+
TableHead,
|
|
98
|
+
TableHeader,
|
|
99
|
+
TableRow,
|
|
100
|
+
TableSpacerRow,
|
|
101
|
+
TreeCell,
|
|
102
|
+
TreeLabel,
|
|
103
|
+
TreeRow,
|
|
104
|
+
useTree,
|
|
105
|
+
useVirtualList,
|
|
106
|
+
} from '@rinzai/zen';
|
|
107
|
+
import { useRef, useState } from 'react';
|
|
108
|
+
|
|
109
|
+
const MONTHS = ['Oct', 'Nov', 'Dec', 'Jan', 'Feb', 'Mar'];
|
|
110
|
+
const ROW_HEIGHT = 36;
|
|
111
|
+
|
|
112
|
+
interface Line {
|
|
113
|
+
key: string;
|
|
114
|
+
label: string;
|
|
115
|
+
/** Amount per month, in paise; groups add up their items. */
|
|
116
|
+
amounts?: number[];
|
|
117
|
+
items?: Line[];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const item = (label: string, rupees: number[]): Line => ({
|
|
121
|
+
key: label.toLowerCase().replace(/\W+/g, '-'),
|
|
122
|
+
label,
|
|
123
|
+
amounts: rupees.map((r) => r * 100),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const PLAN: Line[] = [
|
|
127
|
+
{
|
|
128
|
+
key: 'home',
|
|
129
|
+
label: 'Home',
|
|
130
|
+
items: [
|
|
131
|
+
item('Rent', [18500, 18500, 18500, 18500, 18500, 18500]),
|
|
132
|
+
item('Electricity', [1400, 1250, 1600, 1900, 1700, 1300]),
|
|
133
|
+
item('Internet', [799, 799, 799, 799, 799, 799]),
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
key: 'food',
|
|
138
|
+
label: 'Food',
|
|
139
|
+
items: [
|
|
140
|
+
item('Groceries', [9000, 9500, 12000, 9000, 9000, 9500]),
|
|
141
|
+
item('Dining out', [3000, 2500, 6000, 2000, 2500, 3000]),
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
key: 'transport',
|
|
146
|
+
label: 'Transport',
|
|
147
|
+
items: [
|
|
148
|
+
item('Metro card', [1200, 1200, 1200, 1200, 1200, 1200]),
|
|
149
|
+
item('Cabs', [1500, 1800, 2500, 1500, 1200, 1600]),
|
|
150
|
+
],
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
const total = (line: Line, month: number): number =>
|
|
155
|
+
line.items ? line.items.reduce((sum, i) => sum + total(i, month), 0) : (line.amounts?.[month] ?? 0);
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* A budget grid like a planner: categories open into items, months run across,
|
|
159
|
+
* and the header, first column and totals stay put while it scrolls either way.
|
|
160
|
+
* Click an amount to change it. Rows are virtualized, so long plans stay fast.
|
|
161
|
+
*/
|
|
162
|
+
export default function Planner() {
|
|
163
|
+
const [plan, setPlan] = useState(PLAN);
|
|
164
|
+
const tree = useTree({ items: plan, getKey: (l) => l.key, getChildren: (l) => l.items, rowHeight: ROW_HEIGHT });
|
|
165
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
166
|
+
const rows = useVirtualList({ count: tree.rows.length, itemHeight: ROW_HEIGHT, scrollRef, overscan: 12 });
|
|
167
|
+
const inr = (paise: number) => formatMoney(paise, 'INR', 'en-IN', { showDecimals: false });
|
|
168
|
+
|
|
169
|
+
const setAmount = (key: string, month: number, paise: number | null) =>
|
|
170
|
+
setPlan((p) =>
|
|
171
|
+
p.map((group) => ({
|
|
172
|
+
...group,
|
|
173
|
+
items: group.items?.map((i) =>
|
|
174
|
+
i.key === key ? { ...i, amounts: i.amounts!.map((a, m) => (m === month ? (paise ?? 0) : a)) } : i,
|
|
175
|
+
),
|
|
176
|
+
})),
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<TableContainer ref={scrollRef} label="Budget plan" className="max-h-80 w-full">
|
|
181
|
+
<Table {...tree.tableProps} className="w-max min-w-full">
|
|
182
|
+
<TableHeader>
|
|
183
|
+
<TableRow>
|
|
184
|
+
<TableHead sticky="start" className="min-w-44">
|
|
185
|
+
Budget
|
|
186
|
+
</TableHead>
|
|
187
|
+
{MONTHS.map((m) => (
|
|
188
|
+
<TableHead key={m} numeric className="min-w-28">
|
|
189
|
+
{m}
|
|
190
|
+
</TableHead>
|
|
191
|
+
))}
|
|
192
|
+
</TableRow>
|
|
193
|
+
</TableHeader>
|
|
194
|
+
<TableBody>
|
|
195
|
+
<TableSpacerRow height={rows.paddingTop} colSpan={MONTHS.length + 1} />
|
|
196
|
+
{rows.items.map(({ index }) => {
|
|
197
|
+
const row = tree.rows[index];
|
|
198
|
+
const line = row.item;
|
|
199
|
+
return (
|
|
200
|
+
<TreeRow key={row.key} row={row} tree={tree}>
|
|
201
|
+
<TreeCell sticky="start" className={row.hasChildren ? 'font-medium' : undefined}>
|
|
202
|
+
<TreeLabel row={row} tree={tree}>
|
|
203
|
+
<span className="truncate">{line.label}</span>
|
|
204
|
+
</TreeLabel>
|
|
205
|
+
</TreeCell>
|
|
206
|
+
{MONTHS.map((month, m) => (
|
|
207
|
+
<TreeCell key={month} numeric>
|
|
208
|
+
{row.hasChildren ? (
|
|
209
|
+
<span className="text-muted-foreground px-1.5">{inr(total(line, m))}</span>
|
|
210
|
+
) : (
|
|
211
|
+
<EditableCell
|
|
212
|
+
label={`${line.label}, ${month}: ${inr(total(line, m))}`}
|
|
213
|
+
editor={(close) => (
|
|
214
|
+
<MoneyInput
|
|
215
|
+
compact
|
|
216
|
+
autoFocus
|
|
217
|
+
allowEmpty
|
|
218
|
+
aria-label={`${line.label}, ${month}`}
|
|
219
|
+
value={total(line, m)}
|
|
220
|
+
currency="INR"
|
|
221
|
+
locale="en-IN"
|
|
222
|
+
className="w-28"
|
|
223
|
+
onValueChange={(v) => {
|
|
224
|
+
setAmount(line.key, m, v);
|
|
225
|
+
close();
|
|
226
|
+
}}
|
|
227
|
+
onCancel={close}
|
|
228
|
+
/>
|
|
229
|
+
)}
|
|
230
|
+
>
|
|
231
|
+
{inr(total(line, m))}
|
|
232
|
+
</EditableCell>
|
|
233
|
+
)}
|
|
234
|
+
</TreeCell>
|
|
235
|
+
))}
|
|
236
|
+
</TreeRow>
|
|
237
|
+
);
|
|
238
|
+
})}
|
|
239
|
+
<TableSpacerRow height={rows.paddingBottom} colSpan={MONTHS.length + 1} />
|
|
240
|
+
</TableBody>
|
|
241
|
+
<TableFooter>
|
|
242
|
+
<TableRow>
|
|
243
|
+
<TableFooterCell sticky="start">Total</TableFooterCell>
|
|
244
|
+
{MONTHS.map((month, m) => (
|
|
245
|
+
<TableFooterCell key={month} numeric>
|
|
246
|
+
{inr(plan.reduce((sum, g) => sum + total(g, m), 0))}
|
|
247
|
+
</TableFooterCell>
|
|
248
|
+
))}
|
|
249
|
+
</TableRow>
|
|
250
|
+
</TableFooter>
|
|
251
|
+
</Table>
|
|
252
|
+
</TableContainer>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## API reference
|
|
258
|
+
|
|
259
|
+
### TreeRow
|
|
260
|
+
|
|
261
|
+
A table row wired to useTree: tree semantics, the keyboard and the open/close animation.
|
|
262
|
+
|
|
263
|
+
| Prop | Type | Default | Description |
|
|
264
|
+
| --- | --- | --- | --- |
|
|
265
|
+
| `row` (required) | `TreeRowData<T>` | | |
|
|
266
|
+
| `tree` (required) | `{ rows: TreeRowData<T>[]; toggle: (key: string) => void; isExpanded: (key: string) => boolean; expand: (key: string) => void; collapse: (key: string) => void; expandAll: () => void; ... 4 more ...; rowHeight: number; }` | | |
|
|
267
|
+
|
|
268
|
+
### TreeCell
|
|
269
|
+
|
|
270
|
+
A cell in a TreeRow: its content sits in a box whose height animates as groups open and close.
|
|
271
|
+
|
|
272
|
+
| Prop | Type | Default | Description |
|
|
273
|
+
| --- | --- | --- | --- |
|
|
274
|
+
| `numeric` | `boolean` | | Aligned to the end, tabular digits. |
|
|
275
|
+
| `sticky` | `"start"` | | Stay in view when the table scrolls sideways. |
|
|
276
|
+
|
|
277
|
+
### TreeLabel
|
|
278
|
+
|
|
279
|
+
The first column's content: indented by depth, with the row's toggle (or a spacer, so labels line up).
|
|
280
|
+
|
|
281
|
+
| Prop | Type | Default | Description |
|
|
282
|
+
| --- | --- | --- | --- |
|
|
283
|
+
| `row` (required) | `TreeRowData<T>` | | |
|
|
284
|
+
| `tree` (required) | `{ rows: TreeRowData<T>[]; toggle: (key: string) => void; isExpanded: (key: string) => boolean; expand: (key: string) => void; collapse: (key: string) => void; expandAll: () => void; ... 4 more ...; rowHeight: number; }` | | |
|
|
285
|
+
| `className` | `string` | | |
|
|
286
|
+
| `indent` | `number` | `20` | Indent per level, in px. |
|
|
287
|
+
|
|
288
|
+
### TreeToggle
|
|
289
|
+
|
|
290
|
+
Chevron button that opens and closes a row: points right when closed, down when open.
|
|
291
|
+
|
|
292
|
+
| Prop | Type | Default | Description |
|
|
293
|
+
| --- | --- | --- | --- |
|
|
294
|
+
| `expanded` (required) | `boolean` | | |
|
|
295
|
+
| `onToggle` (required) | `() => void` | | |
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Trend
|
|
2
|
+
|
|
3
|
+
A change, like +12% with an arrow, coloured by whether it is good news.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Trend } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Stat, StatRow, Trend } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
/** Spending going up is bad news (`good="down"`); savings going up is good. */
|
|
17
|
+
export default function Default() {
|
|
18
|
+
return (
|
|
19
|
+
<StatRow className="w-full max-w-xl">
|
|
20
|
+
<Stat
|
|
21
|
+
label="Spent"
|
|
22
|
+
value="$1,845"
|
|
23
|
+
hint={
|
|
24
|
+
<Trend value={0.12} good="down">
|
|
25
|
+
vs last month
|
|
26
|
+
</Trend>
|
|
27
|
+
}
|
|
28
|
+
/>
|
|
29
|
+
<Stat label="Saved" value="$640" hint={<Trend value={0.084}>vs last month</Trend>} />
|
|
30
|
+
<Stat label="Bills" value="$1,210" hint={<Trend value={0}>vs last month</Trend>} />
|
|
31
|
+
</StatRow>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## API reference
|
|
37
|
+
|
|
38
|
+
### Trend
|
|
39
|
+
|
|
40
|
+
A change, e.g. "+12%" with an arrow, coloured by whether it's good news:
|
|
41
|
+
savings going up is, spending going up isn't (`good="down"`). `value` is
|
|
42
|
+
the change as a fraction (0.12 for 12%); children follow it, muted, e.g.
|
|
43
|
+
"vs last month".
|
|
44
|
+
|
|
45
|
+
| Prop | Type | Default | Description |
|
|
46
|
+
| --- | --- | --- | --- |
|
|
47
|
+
| `value` (required) | `number` | | The change as a fraction: 0.12 is 12% up, -0.05 is 5% down. |
|
|
48
|
+
| `format` | `((value: number) => string)` | | How the change reads, e.g. as money instead of a percent. |
|
|
49
|
+
| `good` | `"down" \| "up"` | `up` | Which way is good news (default `up`); spending would be `down`. |
|
|
50
|
+
| `locale` | `string` | | For the default percent format. Defaults to the LocaleProvider's. |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arrow-path.d.ts","sourceRoot":"","sources":["../../packages/icons/arrow-path.tsx"],"names":[],"mappings":"AAEA,OAAO,SAAS,MAAM,cAAc,CAAC;AAErC,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,KAAK,EAAE,SAAS,+BAoBjD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arrows-right-left.d.ts","sourceRoot":"","sources":["../../packages/icons/arrows-right-left.tsx"],"names":[],"mappings":"AAEA,OAAO,SAAS,MAAM,cAAc,CAAC;AAErC,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,KAAK,EAAE,SAAS,+BAoBvD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backward.d.ts","sourceRoot":"","sources":["../../packages/icons/backward.tsx"],"names":[],"mappings":"AAEA,OAAO,SAAS,MAAM,cAAc,CAAC;AAErC,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,KAAK,EAAE,SAAS,+BAchD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forward.d.ts","sourceRoot":"","sources":["../../packages/icons/forward.tsx"],"names":[],"mappings":"AAEA,OAAO,SAAS,MAAM,cAAc,CAAC;AAErC,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,KAAK,EAAE,SAAS,+BAc/C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heart-solid.d.ts","sourceRoot":"","sources":["../../packages/icons/heart-solid.tsx"],"names":[],"mappings":"AAEA,OAAO,SAAS,MAAM,cAAc,CAAC;AAErC,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,KAAK,EAAE,SAAS,+BAclD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heart.d.ts","sourceRoot":"","sources":["../../packages/icons/heart.tsx"],"names":[],"mappings":"AAEA,OAAO,SAAS,MAAM,cAAc,CAAC;AAErC,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,KAAK,EAAE,SAAS,+BAoB7C"}
|