@rinzai/zen 0.14.0 → 0.16.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 +153 -0
- package/README.md +16 -1
- package/dist/animated-number/index.js +14 -14
- package/dist/breadcrumbs/index.js +14 -14
- package/dist/calendar/index.js +29 -29
- package/dist/calendar-heatmap/index.js +12 -12
- package/dist/canvas/index.js +19 -19
- package/dist/carousel/index.d.ts.map +1 -1
- package/dist/carousel/index.js +29 -29
- package/dist/chart/index.js +7 -7
- package/dist/code-block/index.js +5 -5
- package/dist/collapse/index.js +6 -6
- package/dist/color-picker/index.js +10 -10
- package/dist/combobox/index.js +61 -61
- package/dist/command-palette/index.js +5 -5
- package/dist/confirm-dialog/index.js +4 -4
- package/dist/date-picker/index.js +15 -15
- package/dist/dialog/index.d.ts.map +1 -1
- package/dist/dialog/index.js +45 -65
- 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 +456 -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 +97 -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 +244 -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 +102 -0
- package/dist/docs/tree.md +295 -0
- package/dist/docs/trend.md +50 -0
- package/dist/filter-bar/index.js +6 -6
- package/dist/header/index.js +6 -6
- package/dist/icon-picker/index.js +5 -5
- 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/keypad/index.js +5 -5
- package/dist/lib.js +86 -80
- package/dist/load-more/index.js +5 -5
- 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 +7 -1
- package/dist/menu/index.d.ts.map +1 -1
- package/dist/menu/index.js +222 -178
- package/dist/money-input/index.js +54 -54
- package/dist/month-picker/index.js +11 -11
- package/dist/number-field/index.js +5 -5
- package/dist/pagination/index.js +12 -12
- 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/range-slider/index.js +11 -11
- package/dist/repeat-picker/index.js +5 -5
- package/dist/resizable/index.js +22 -22
- package/dist/seek-bar/index.d.ts +26 -0
- package/dist/seek-bar/index.d.ts.map +1 -0
- package/dist/seek-bar/index.js +121 -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/select/index.js +21 -21
- package/dist/slider/index.d.ts +2 -1
- package/dist/slider/index.d.ts.map +1 -1
- package/dist/sortable/index.d.ts +13 -1
- package/dist/sortable/index.d.ts.map +1 -1
- package/dist/sortable/index.js +74 -55
- package/dist/sortable/shared.js +14 -14
- package/dist/sortable/tree.js +17 -17
- package/dist/sparkline/index.js +4 -4
- package/dist/stepper/index.js +13 -13
- 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/table/index.js +26 -26
- package/dist/table/selection.js +9 -9
- package/dist/table-of-contents/index.js +11 -11
- package/dist/tag-input/index.js +5 -5
- 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-toggle/index.js +4 -4
- package/dist/theme.css +143 -0
- package/dist/time-field/index.js +21 -21
- package/dist/toast/index.d.ts.map +1 -1
- package/dist/toast/index.js +41 -41
- package/dist/tooltip/index.d.ts +9 -3
- package/dist/tooltip/index.d.ts.map +1 -1
- package/dist/tooltip/index.js +19 -16
- package/dist/tree/index.js +34 -34
- package/dist/trend/index.js +5 -5
- 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 +3 -0
- package/dist/utils/messages.d.ts.map +1 -1
- package/dist/utils/messages.js +4 -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/swipe.d.ts +7 -0
- package/dist/utils/swipe.d.ts.map +1 -1
- package/dist/utils/swipe.js +45 -25
- package/dist/utils/useAnchoredPopup.d.ts +2 -1
- package/dist/utils/useAnchoredPopup.d.ts.map +1 -1
- package/dist/utils/useAnchoredPopup.js +10 -5
- 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/utils/useVirtualList.d.ts +6 -1
- package/dist/utils/useVirtualList.d.ts.map +1 -1
- package/dist/utils/useVirtualList.js +12 -12
- package/dist/zen.css +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Pagination
|
|
2
|
+
|
|
3
|
+
The pages a long list is split into, as buttons or links for your router.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Pagination } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Pagination } from '@rinzai/zen';
|
|
15
|
+
import { useState } from 'react';
|
|
16
|
+
|
|
17
|
+
/** Pages as buttons. With a router, give `href` and render each with its Link through `renderLink`. */
|
|
18
|
+
export default function Default() {
|
|
19
|
+
const [page, setPage] = useState(6);
|
|
20
|
+
return <Pagination page={page} count={12} onPageChange={setPage} />;
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## API reference
|
|
25
|
+
|
|
26
|
+
### Pagination
|
|
27
|
+
|
|
28
|
+
The pages a long list is split into: previous and next, the first and last
|
|
29
|
+
pages, and those around the current one (`page`, from 1). Pages are buttons
|
|
30
|
+
calling `onPageChange`, or with `href`, links (render them with your router's
|
|
31
|
+
Link through `renderLink`).
|
|
32
|
+
|
|
33
|
+
| Prop | Type | Default | Description |
|
|
34
|
+
| --- | --- | --- | --- |
|
|
35
|
+
| `count` (required) | `number` | | How many pages there are. With fewer than 2, nothing is shown. |
|
|
36
|
+
| `page` (required) | `number` | | The current page, from 1. |
|
|
37
|
+
| `aria-label` | `string` | | |
|
|
38
|
+
| `className` | `string` | | |
|
|
39
|
+
| `href` | `((page: number) => string)` | | Pages as links: the URL of each. |
|
|
40
|
+
| `onPageChange` | `((page: number) => void)` | | |
|
|
41
|
+
| `renderLink` | `((props: DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, page: number) => ReactNode)` | `(props) => <a {...props} />` | Renders a page link, e.g. with your router's Link. |
|
|
42
|
+
| `siblings` | `number` | `1` | Pages shown either side of the current one. |
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Pills
|
|
2
|
+
|
|
3
|
+
A glass track with a glowing pill that slides to the current page. In a nav, it is site navigation; works with any router.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Pills, Pill, PillIndicator } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Navigation
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Pill, PillIndicator, Pills } from '@rinzai/zen';
|
|
15
|
+
import { useState } from 'react';
|
|
16
|
+
|
|
17
|
+
const PAGES = ['Month', 'Planner', 'Loans', 'Goals', 'Trends'];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Site navigation: Pills in a <nav>. With a router, pass its link as the child:
|
|
21
|
+
* <Pill asChild><NavLink to="/goals">Goals</NavLink></Pill>.
|
|
22
|
+
*/
|
|
23
|
+
export default function Navigation() {
|
|
24
|
+
const [current, setCurrent] = useState('Month');
|
|
25
|
+
return (
|
|
26
|
+
<nav aria-label="Main">
|
|
27
|
+
<Pills>
|
|
28
|
+
<PillIndicator />
|
|
29
|
+
{PAGES.map((page) => (
|
|
30
|
+
<Pill
|
|
31
|
+
key={page}
|
|
32
|
+
href={`#${page.toLowerCase()}`}
|
|
33
|
+
active={page === current}
|
|
34
|
+
onClick={(e) => {
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
setCurrent(page);
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
{page}
|
|
40
|
+
</Pill>
|
|
41
|
+
))}
|
|
42
|
+
</Pills>
|
|
43
|
+
</nav>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## API reference
|
|
49
|
+
|
|
50
|
+
### Pills
|
|
51
|
+
|
|
52
|
+
A glass track of pills, with an optional glowing pill that slides to the
|
|
53
|
+
active one: whichever has aria-current="page" (router links such as React
|
|
54
|
+
Router's NavLink set it themselves) or aria-selected="true". It adds no role
|
|
55
|
+
of its own; what you wrap it in says what it is. Site navigation:
|
|
56
|
+
|
|
57
|
+
<nav aria-label="Main">
|
|
58
|
+
<Pills>
|
|
59
|
+
<PillIndicator />
|
|
60
|
+
<Pill href="/month" active>Month</Pill>
|
|
61
|
+
<Pill asChild><NavLink to="/goals">Goals</NavLink></Pill>
|
|
62
|
+
</Pills>
|
|
63
|
+
</nav>
|
|
64
|
+
|
|
65
|
+
For tabs that switch panels in place, use Tabs with `<TabList variant="pills">`.
|
|
66
|
+
|
|
67
|
+
No props of its own; it accepts the props of the element it renders.
|
|
68
|
+
|
|
69
|
+
### Pill
|
|
70
|
+
|
|
71
|
+
One pill: a link. `active` marks the current page. With `asChild`, the
|
|
72
|
+
styles go onto your own element instead (a router's Link or NavLink).
|
|
73
|
+
|
|
74
|
+
| Prop | Type | Default | Description |
|
|
75
|
+
| --- | --- | --- | --- |
|
|
76
|
+
| `active` | `boolean` | | This pill is the current page (aria-current="page"). |
|
|
77
|
+
| `asChild` | `boolean` | | Style the single child element (e.g. a router link) instead of rendering an <a>. |
|
|
78
|
+
|
|
79
|
+
### PillIndicator
|
|
80
|
+
|
|
81
|
+
The glowing pill under the active item. Place it inside Pills; hidden while none is active.
|
|
82
|
+
|
|
83
|
+
| Prop | Type | Default | Description |
|
|
84
|
+
| --- | --- | --- | --- |
|
|
85
|
+
| `className` | `string` | | |
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Player Bar
|
|
2
|
+
|
|
3
|
+
What's playing, along the bottom of the screen, sitting on a TabBar on phones. useMediaPlayer drives your own <audio> or <video>; useMediaSession puts it on the lock screen and the media keys.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { PlayerBar, useMediaPlayer, useMediaSession } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Button, PlayerBar, SeekBar, Slider, useMediaPlayer, useMediaSession } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
import { useDemoTrack } from './demo-track';
|
|
17
|
+
// Icons from Heroicons (https://heroicons.com): Backward (backward), Pause (pause), Play (play), SpeakerWave (speaker-wave), SpeakerXMark (speaker-x-mark).
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Your own <audio>, driven by useMediaPlayer: the cover and title, the controls
|
|
21
|
+
* over a SeekBar, and the volume. useMediaSession puts it on the lock screen
|
|
22
|
+
* and the media keys. Fixed to the bottom of the screen; here it sits in a frame.
|
|
23
|
+
*/
|
|
24
|
+
export default function Default() {
|
|
25
|
+
const src = useDemoTrack();
|
|
26
|
+
const [audioRef, player] = useMediaPlayer();
|
|
27
|
+
useMediaSession(player, { title: 'Glass Aurora', artist: 'Zen' });
|
|
28
|
+
return (
|
|
29
|
+
<div className="border-tint/10 relative h-48 w-full [transform:translateZ(0)] overflow-hidden rounded-2xl border">
|
|
30
|
+
<audio ref={audioRef} src={src} preload="metadata" />
|
|
31
|
+
<PlayerBar aria-label="Now playing" className="absolute! justify-between rounded-b-[15px]">
|
|
32
|
+
<div className="flex min-w-0 flex-1 items-center gap-3">
|
|
33
|
+
<div className="from-primary size-11 shrink-0 rounded-lg bg-linear-135 to-fuchsia-400" />
|
|
34
|
+
<div className="min-w-0">
|
|
35
|
+
<p className="truncate text-sm font-medium">Glass Aurora</p>
|
|
36
|
+
<p className="text-muted-foreground truncate text-xs">Zen</p>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
<div className="flex flex-[2] flex-col items-center gap-1">
|
|
40
|
+
<div className="flex items-center gap-2">
|
|
41
|
+
<Button variant="ghost" size="icon-sm" aria-label="Restart" onClick={() => player.seek(0)}>
|
|
42
|
+
<Backward className="size-4" />
|
|
43
|
+
</Button>
|
|
44
|
+
<Button
|
|
45
|
+
size="icon"
|
|
46
|
+
className="rounded-full"
|
|
47
|
+
aria-label={player.playing ? 'Pause' : 'Play'}
|
|
48
|
+
disabled={!src}
|
|
49
|
+
onClick={() => player.toggle()}
|
|
50
|
+
>
|
|
51
|
+
{player.playing ? <Pause className="size-5" /> : <Play className="size-5" />}
|
|
52
|
+
</Button>
|
|
53
|
+
</div>
|
|
54
|
+
<SeekBar
|
|
55
|
+
className="w-full"
|
|
56
|
+
currentTime={player.currentTime}
|
|
57
|
+
playing={player.playing && !player.waiting}
|
|
58
|
+
playbackRate={player.playbackRate}
|
|
59
|
+
duration={player.duration}
|
|
60
|
+
buffered={player.buffered}
|
|
61
|
+
onSeek={player.seek}
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
<div className="hidden flex-1 items-center justify-end gap-2 sm:flex">
|
|
65
|
+
<Button
|
|
66
|
+
variant="ghost"
|
|
67
|
+
size="icon-sm"
|
|
68
|
+
aria-label={player.muted ? 'Unmute' : 'Mute'}
|
|
69
|
+
onClick={() => player.setMuted(!player.muted)}
|
|
70
|
+
>
|
|
71
|
+
{player.muted ? <SpeakerXMark className="size-4" /> : <SpeakerWave className="size-4" />}
|
|
72
|
+
</Button>
|
|
73
|
+
<Slider
|
|
74
|
+
aria-label="Volume"
|
|
75
|
+
className="w-24"
|
|
76
|
+
value={player.muted ? 0 : Math.round(player.volume * 100)}
|
|
77
|
+
onValueChange={(v) => {
|
|
78
|
+
player.setVolume(v / 100);
|
|
79
|
+
if (v > 0 && player.muted) player.setMuted(false);
|
|
80
|
+
}}
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
</PlayerBar>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### On tab bar
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
import { Button, PlayerBar, TabBar, TabBarItem, useMediaPlayer } from '@rinzai/zen';
|
|
93
|
+
|
|
94
|
+
import { useDemoTrack } from './demo-track';
|
|
95
|
+
// Icons from Heroicons (https://heroicons.com): Pause (pause), Play (play), Search (magnifying-glass), Squares (squares-2x2), ListBullet (list-bullet).
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* On a phone, it sits on the TabBar beside it, and the page pads its end by
|
|
99
|
+
* both bars' heights (--zen-tab-bar-height and --zen-player-bar-height).
|
|
100
|
+
*/
|
|
101
|
+
export default function OnTabBar() {
|
|
102
|
+
const src = useDemoTrack();
|
|
103
|
+
const [audioRef, player] = useMediaPlayer();
|
|
104
|
+
const progress = player.duration ? (player.currentTime / player.duration) * 100 : 0;
|
|
105
|
+
return (
|
|
106
|
+
<div className="border-tint/10 relative h-96 w-80 [transform:translateZ(0)] overflow-hidden rounded-2xl border">
|
|
107
|
+
<audio ref={audioRef} src={src} preload="metadata" />
|
|
108
|
+
<div className="h-full overflow-y-auto p-4 pb-[calc(var(--zen-tab-bar-height,0px)+var(--zen-player-bar-height,0px))]">
|
|
109
|
+
<p className="text-muted-foreground text-sm">Home</p>
|
|
110
|
+
</div>
|
|
111
|
+
<PlayerBar aria-label="Now playing" className="absolute! gap-3 px-3 pt-2 pb-2">
|
|
112
|
+
<div className="from-primary size-9 shrink-0 rounded-md bg-linear-135 to-fuchsia-400" />
|
|
113
|
+
<div className="min-w-0 flex-1">
|
|
114
|
+
<p className="truncate text-sm font-medium">Glass Aurora</p>
|
|
115
|
+
<p className="text-muted-foreground truncate text-xs">Zen</p>
|
|
116
|
+
</div>
|
|
117
|
+
<Button
|
|
118
|
+
variant="ghost"
|
|
119
|
+
size="icon-sm"
|
|
120
|
+
aria-label={player.playing ? 'Pause' : 'Play'}
|
|
121
|
+
disabled={!src}
|
|
122
|
+
onClick={() => player.toggle()}
|
|
123
|
+
>
|
|
124
|
+
{player.playing ? <Pause className="size-5" /> : <Play className="size-5" />}
|
|
125
|
+
</Button>
|
|
126
|
+
<span
|
|
127
|
+
aria-hidden
|
|
128
|
+
className="bg-primary absolute start-0 bottom-0 h-0.5 shadow-[0_0_8px_oklch(var(--glow)/calc(0.8*var(--glow-k)))]"
|
|
129
|
+
style={{ width: `${progress}%` }}
|
|
130
|
+
/>
|
|
131
|
+
</PlayerBar>
|
|
132
|
+
<TabBar aria-label="Main" hideFrom={false} className="absolute! rounded-b-[15px]">
|
|
133
|
+
<TabBarItem href="#home" icon={<Squares />} active onClick={(e) => e.preventDefault()}>
|
|
134
|
+
Home
|
|
135
|
+
</TabBarItem>
|
|
136
|
+
<TabBarItem href="#search" icon={<Search />} onClick={(e) => e.preventDefault()}>
|
|
137
|
+
Search
|
|
138
|
+
</TabBarItem>
|
|
139
|
+
<TabBarItem href="#library" icon={<ListBullet />} onClick={(e) => e.preventDefault()}>
|
|
140
|
+
Library
|
|
141
|
+
</TabBarItem>
|
|
142
|
+
</TabBar>
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## API reference
|
|
149
|
+
|
|
150
|
+
### PlayerBar
|
|
151
|
+
|
|
152
|
+
What's playing, along the bottom of the screen: a glass bar for the cover,
|
|
153
|
+
the title, the controls and a SeekBar, laid out as you like. It sits on a
|
|
154
|
+
TabBar beside it (in the same parent), and clears the home indicator where
|
|
155
|
+
there's none. Its height is kept in `--zen-player-bar-height` on that
|
|
156
|
+
parent, for the page to pad its end by. Name it with an aria-label ("Now playing"). Drive it with useMediaPlayer.
|
|
157
|
+
|
|
158
|
+
No props of its own; it accepts the props of the element it renders.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Popover
|
|
2
|
+
|
|
3
|
+
A glass panel that opens from a button, on the native Popover API: small forms, cards and settings. For hints use Tooltip; for actions, Menu.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Popover, PopoverTrigger, PopoverContent, PopoverClose } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Profile card
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Badge, Button, Popover, PopoverContent, PopoverTrigger } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
/** Your own trigger: here an avatar button. */
|
|
17
|
+
export default function ProfileCard() {
|
|
18
|
+
return (
|
|
19
|
+
<Popover>
|
|
20
|
+
<PopoverTrigger
|
|
21
|
+
aria-label="Alex Johnson"
|
|
22
|
+
className="bg-primary text-primary-foreground flex size-9 items-center justify-center rounded-full text-sm font-bold"
|
|
23
|
+
>
|
|
24
|
+
AJ
|
|
25
|
+
</PopoverTrigger>
|
|
26
|
+
<PopoverContent aria-label="Alex Johnson">
|
|
27
|
+
<div className="flex w-64 flex-col gap-3 p-4">
|
|
28
|
+
<div className="flex items-center gap-3">
|
|
29
|
+
<div className="bg-primary text-primary-foreground flex size-10 items-center justify-center rounded-full text-sm font-bold">
|
|
30
|
+
AJ
|
|
31
|
+
</div>
|
|
32
|
+
<div className="flex flex-col">
|
|
33
|
+
<span className="text-sm font-semibold">Alex Johnson</span>
|
|
34
|
+
<span className="text-muted-foreground text-xs">alex@example.com</span>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<div className="flex gap-2">
|
|
38
|
+
<Badge>Admin</Badge>
|
|
39
|
+
<Badge variant="outline">Pro</Badge>
|
|
40
|
+
</div>
|
|
41
|
+
<div className="border-tint/10 flex flex-col gap-1 border-t pt-3">
|
|
42
|
+
<Button variant="ghost" size="sm" className="justify-start">
|
|
43
|
+
View profile
|
|
44
|
+
</Button>
|
|
45
|
+
<Button variant="ghost" size="sm" className="justify-start">
|
|
46
|
+
Settings
|
|
47
|
+
</Button>
|
|
48
|
+
<Button variant="ghost" size="sm" className="text-destructive justify-start">
|
|
49
|
+
Sign out
|
|
50
|
+
</Button>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</PopoverContent>
|
|
54
|
+
</Popover>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Feedback form
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { Button, Input, Popover, PopoverClose, PopoverContent, PopoverTrigger, Textarea } from '@rinzai/zen';
|
|
63
|
+
|
|
64
|
+
export default function FeedbackForm() {
|
|
65
|
+
return (
|
|
66
|
+
<Popover>
|
|
67
|
+
<PopoverTrigger asChild>
|
|
68
|
+
<Button variant="outline">Give feedback</Button>
|
|
69
|
+
</PopoverTrigger>
|
|
70
|
+
<PopoverContent aria-labelledby="feedback-title">
|
|
71
|
+
<div className="flex w-72 flex-col gap-3 p-4">
|
|
72
|
+
<p id="feedback-title" className="text-sm font-semibold">
|
|
73
|
+
Send feedback
|
|
74
|
+
</p>
|
|
75
|
+
<Input placeholder="Subject" />
|
|
76
|
+
<Textarea placeholder="Describe your feedback…" className="resize-none" />
|
|
77
|
+
<div className="flex justify-end gap-2">
|
|
78
|
+
<PopoverClose variant="outline" size="sm">
|
|
79
|
+
Cancel
|
|
80
|
+
</PopoverClose>
|
|
81
|
+
<Button size="sm">Submit</Button>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</PopoverContent>
|
|
85
|
+
</Popover>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Settings
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { Button, Popover, PopoverContent, PopoverTrigger, Toggle } from '@rinzai/zen';
|
|
94
|
+
import { useState } from 'react';
|
|
95
|
+
|
|
96
|
+
export default function Settings() {
|
|
97
|
+
const [settings, setSettings] = useState({ notifications: true, sounds: false, autoSave: true });
|
|
98
|
+
const rows = [
|
|
99
|
+
{ key: 'notifications', label: 'Notifications' },
|
|
100
|
+
{ key: 'sounds', label: 'Sounds' },
|
|
101
|
+
{ key: 'autoSave', label: 'Auto-save' },
|
|
102
|
+
] as const;
|
|
103
|
+
return (
|
|
104
|
+
<Popover>
|
|
105
|
+
<PopoverTrigger asChild>
|
|
106
|
+
<Button variant="outline">Preferences</Button>
|
|
107
|
+
</PopoverTrigger>
|
|
108
|
+
<PopoverContent aria-label="Preferences">
|
|
109
|
+
<div className="flex w-60 flex-col gap-3 p-4">
|
|
110
|
+
<p className="text-sm font-semibold">Preferences</p>
|
|
111
|
+
{rows.map(({ key, label }) => (
|
|
112
|
+
<label key={key} className="flex items-center justify-between font-normal">
|
|
113
|
+
{label}
|
|
114
|
+
<Toggle
|
|
115
|
+
checked={settings[key]}
|
|
116
|
+
onCheckedChange={(on) => setSettings((s) => ({ ...s, [key]: on }))}
|
|
117
|
+
/>
|
|
118
|
+
</label>
|
|
119
|
+
))}
|
|
120
|
+
</div>
|
|
121
|
+
</PopoverContent>
|
|
122
|
+
</Popover>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Controlled
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
import { Button, Popover, PopoverClose, PopoverContent, PopoverTrigger } from '@rinzai/zen';
|
|
131
|
+
import { useState } from 'react';
|
|
132
|
+
|
|
133
|
+
/** Follow your own state with `open`; `onOpenChange` hears every open and close (outside clicks, Escape…). */
|
|
134
|
+
export default function Controlled() {
|
|
135
|
+
const [open, setOpen] = useState(false);
|
|
136
|
+
return (
|
|
137
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
138
|
+
<PopoverTrigger asChild>
|
|
139
|
+
<Button>{open ? 'Hide' : 'Show'} popover</Button>
|
|
140
|
+
</PopoverTrigger>
|
|
141
|
+
<PopoverContent aria-label="Controlled">
|
|
142
|
+
<div className="flex flex-col gap-2 p-3 text-sm">
|
|
143
|
+
<p className="font-medium">Manually controlled</p>
|
|
144
|
+
<PopoverClose size="sm" variant="secondary">
|
|
145
|
+
Close
|
|
146
|
+
</PopoverClose>
|
|
147
|
+
</div>
|
|
148
|
+
</PopoverContent>
|
|
149
|
+
</Popover>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## API reference
|
|
155
|
+
|
|
156
|
+
### Popover
|
|
157
|
+
|
|
158
|
+
A panel that opens from a button: a small form, a profile card, settings. A
|
|
159
|
+
native popover, so outside clicks and Escape close it and focus returns to
|
|
160
|
+
the button. Put a PopoverTrigger and a PopoverContent inside. Opens and closes
|
|
161
|
+
on its own, or follows `open` (with `onOpenChange` to hear the user close it).
|
|
162
|
+
For a hint on hover or focus, use Tooltip; for a list of actions, Menu.
|
|
163
|
+
|
|
164
|
+
| Prop | Type | Default | Description |
|
|
165
|
+
| --- | --- | --- | --- |
|
|
166
|
+
| `defaultOpen` | `boolean` | `false` | Open when it first appears. |
|
|
167
|
+
| `onOpenChange` | `((open: boolean) => void)` | | Every open and close: the trigger, outside clicks, Escape, PopoverClose. |
|
|
168
|
+
| `open` | `boolean` | | Open or close it from your state; leave it out to let the trigger do it. |
|
|
169
|
+
|
|
170
|
+
### PopoverTrigger
|
|
171
|
+
|
|
172
|
+
The button that opens and closes the popover. With `asChild`, your own button (e.g. a Button) instead.
|
|
173
|
+
|
|
174
|
+
| Prop | Type | Default | Description |
|
|
175
|
+
| --- | --- | --- | --- |
|
|
176
|
+
| `asChild` | `boolean` | | Put the trigger's props on your own button element instead of rendering one. |
|
|
177
|
+
|
|
178
|
+
### PopoverContent
|
|
179
|
+
|
|
180
|
+
The panel: glass, below the trigger (above it if there's no room), centred on
|
|
181
|
+
it by default. A dialog to assistive tech; give it `aria-label` or a heading
|
|
182
|
+
it's labelled by when it holds a form.
|
|
183
|
+
|
|
184
|
+
| Prop | Type | Default | Description |
|
|
185
|
+
| --- | --- | --- | --- |
|
|
186
|
+
| `align` | `"center" \| "end" \| "start"` | `center` | Centred on the trigger (default), or lined up with its start or end edge. |
|
|
187
|
+
| `offset` | `number` | `5` | Gap from the trigger, in px. |
|
|
188
|
+
|
|
189
|
+
### PopoverClose
|
|
190
|
+
|
|
191
|
+
A button that closes its popover (after its own onClick, unless that calls
|
|
192
|
+
preventDefault). Takes every Button prop: variant, size, asChild…
|
|
193
|
+
|
|
194
|
+
| Prop | Type | Default | Description |
|
|
195
|
+
| --- | --- | --- | --- |
|
|
196
|
+
| `asChild` | `boolean` | | Put the button's styles on the single child element (e.g. a link) instead of a <button>. |
|
|
197
|
+
| `loading` | `boolean` | | Shows a spinner and disables the button while an action runs. |
|
|
198
|
+
| `size` | `"default" \| "icon-lg" \| "icon-sm" \| "icon-xs" \| "icon" \| "lg" \| "sm" \| null` | | |
|
|
199
|
+
| `tone` | `"default" \| "inherit" \| "muted" \| "negative" \| null` | | |
|
|
200
|
+
| `variant` | `"default" \| "ghost" \| "link" \| "outline" \| "secondary" \| null` | | |
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Progress Ring
|
|
2
|
+
|
|
3
|
+
Circular progress with room for a label in the middle.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { ProgressRing } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { ProgressRing } from '@rinzai/zen';
|
|
15
|
+
|
|
16
|
+
export default function Default() {
|
|
17
|
+
return (
|
|
18
|
+
<div className="flex items-center gap-6">
|
|
19
|
+
{[0.28, 0.64, 1].map((value) => (
|
|
20
|
+
<ProgressRing key={value} value={value} label={`${Math.round(value * 100)}% saved`}>
|
|
21
|
+
<span className="text-sm font-semibold tabular-nums">{Math.round(value * 100)}%</span>
|
|
22
|
+
</ProgressRing>
|
|
23
|
+
))}
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Goal
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { Button, ProgressRing } from '@rinzai/zen';
|
|
33
|
+
import { useState } from 'react';
|
|
34
|
+
|
|
35
|
+
/** Reaching the goal sends out a ring of glow. */
|
|
36
|
+
export default function Goal() {
|
|
37
|
+
const [saved, setSaved] = useState(120);
|
|
38
|
+
const goal = 200;
|
|
39
|
+
return (
|
|
40
|
+
<div className="flex items-center gap-6">
|
|
41
|
+
<ProgressRing value={saved / goal} label="Holiday fund">
|
|
42
|
+
<span className="text-sm font-semibold tabular-nums">${saved}</span>
|
|
43
|
+
</ProgressRing>
|
|
44
|
+
<div className="flex flex-col gap-2">
|
|
45
|
+
<Button variant="outline" onClick={() => setSaved((s) => Math.min(s + 40, goal))}>
|
|
46
|
+
Save $40
|
|
47
|
+
</Button>
|
|
48
|
+
<Button variant="ghost" onClick={() => setSaved(120)}>
|
|
49
|
+
Reset
|
|
50
|
+
</Button>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## API reference
|
|
58
|
+
|
|
59
|
+
### ProgressRing
|
|
60
|
+
|
|
61
|
+
Circular progress, 0–1, with optional content in the middle. Reaching 1
|
|
62
|
+
while shown sends out a ring of glow, once.
|
|
63
|
+
|
|
64
|
+
| Prop | Type | Default | Description |
|
|
65
|
+
| --- | --- | --- | --- |
|
|
66
|
+
| `label` (required) | `string` | | Accessible name, e.g. "Savings goal". |
|
|
67
|
+
| `value` (required) | `number` | | Progress from 0 to 1; values outside are clamped. |
|
|
68
|
+
| `className` | `string` | | |
|
|
69
|
+
| `size` | `number` | `84` | |
|
|
70
|
+
| `stroke` | `number` | `7` | |
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Radio Cards
|
|
2
|
+
|
|
3
|
+
Choose one of a few cards that each show their option, like a colour palette.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { RadioCards, RadioCard } from '@rinzai/zen';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Default
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { RadioCard, RadioCards } from '@rinzai/zen';
|
|
15
|
+
import { useState } from 'react';
|
|
16
|
+
|
|
17
|
+
const PALETTES = [
|
|
18
|
+
{ id: 'violet', label: 'Violet', hue: 295 },
|
|
19
|
+
{ id: 'ocean', label: 'Ocean', hue: 250 },
|
|
20
|
+
{ id: 'teal', label: 'Teal', hue: 195 },
|
|
21
|
+
{ id: 'amber', label: 'Amber', hue: 75 },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
/** Each card shows what it picks; the whole card is the click target, and arrow keys move the choice. */
|
|
25
|
+
export default function Default() {
|
|
26
|
+
const [palette, setPalette] = useState('violet');
|
|
27
|
+
return (
|
|
28
|
+
<RadioCards
|
|
29
|
+
label="Palette"
|
|
30
|
+
value={palette}
|
|
31
|
+
onValueChange={setPalette}
|
|
32
|
+
className="grid grid-cols-2 gap-2 sm:grid-cols-4"
|
|
33
|
+
>
|
|
34
|
+
{PALETTES.map((p) => (
|
|
35
|
+
<RadioCard key={p.id} value={p.id}>
|
|
36
|
+
<span
|
|
37
|
+
aria-hidden
|
|
38
|
+
className="size-7 shrink-0 rounded-full"
|
|
39
|
+
style={{
|
|
40
|
+
background: `oklch(0.72 0.13 ${p.hue})`,
|
|
41
|
+
boxShadow: `0 0 14px -2px oklch(0.7 0.13 ${p.hue})`,
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
44
|
+
<span className="font-medium">{p.label}</span>
|
|
45
|
+
</RadioCard>
|
|
46
|
+
))}
|
|
47
|
+
</RadioCards>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## API reference
|
|
53
|
+
|
|
54
|
+
### RadioCards
|
|
55
|
+
|
|
56
|
+
A choice of one among a few cards, each showing what it picks (a palette, a
|
|
57
|
+
plan). Native radio inputs underneath, so arrow keys and forms work as for any
|
|
58
|
+
radio group. Lay the cards out with className (e.g. a grid); fill it with
|
|
59
|
+
RadioCard. For short text options, use Segmented.
|
|
60
|
+
|
|
61
|
+
| Prop | Type | Default | Description |
|
|
62
|
+
| --- | --- | --- | --- |
|
|
63
|
+
| `children` (required) | `ReactNode` | | The RadioCards. |
|
|
64
|
+
| `className` | `string` | | Classes for the cards' container: its layout (e.g. a grid). |
|
|
65
|
+
| `defaultValue` | `string` | | The first chosen card, when the group keeps its own. |
|
|
66
|
+
| `label` | `string` | | Shown above the cards, naming the group. Without it, give the group an aria-label or aria-labelledby. |
|
|
67
|
+
| `name` | `string` | | Form field name; defaults to a generated one. |
|
|
68
|
+
| `onValueChange` | `((value: V) => void)` | | |
|
|
69
|
+
| `value` | `string` | | The chosen card, when you keep it. |
|
|
70
|
+
|
|
71
|
+
### RadioCard
|
|
72
|
+
|
|
73
|
+
One card: a native radio covering whatever you put inside, so the whole card
|
|
74
|
+
is the click target. Outlined, lit when chosen; style it further with
|
|
75
|
+
className and style (e.g. a background showing the choice).
|
|
76
|
+
|
|
77
|
+
| Prop | Type | Default | Description |
|
|
78
|
+
| --- | --- | --- | --- |
|
|
79
|
+
| `value` (required) | `string` | | |
|
|
80
|
+
| `className` | `string` | | |
|
|
81
|
+
| `disabled` | `boolean` | | Can't be chosen (and the arrow keys skip it). |
|
|
82
|
+
| `style` | `CSSProperties` | | |
|