@waveso/ui 0.5.0 → 0.7.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/README.md +152 -68
- package/dist/accordion.js +3 -3
- package/dist/accordion.js.map +1 -1
- package/dist/action-bar.js +2 -2
- package/dist/action-bar.js.map +1 -1
- package/dist/alert-dialog.js +4 -4
- package/dist/alert-dialog.js.map +1 -1
- package/dist/alert.js +5 -5
- package/dist/alert.js.map +1 -1
- package/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +1 -2
- package/dist/animate.js.map +1 -1
- package/dist/autocomplete.js +8 -8
- package/dist/autocomplete.js.map +1 -1
- package/dist/avatar.js +5 -5
- package/dist/avatar.js.map +1 -1
- package/dist/badge.d.ts +1 -1
- package/dist/badge.d.ts.map +1 -1
- package/dist/badge.js +5 -6
- package/dist/badge.js.map +1 -1
- package/dist/breadcrumb.js +3 -3
- package/dist/breadcrumb.js.map +1 -1
- package/dist/button-group.js +5 -5
- package/dist/button-group.js.map +1 -1
- package/dist/button.d.ts +2 -2
- package/dist/button.d.ts.map +1 -1
- package/dist/button.js +9 -10
- package/dist/button.js.map +1 -1
- package/dist/card.js +4 -4
- package/dist/card.js.map +1 -1
- package/dist/checkbox.js +1 -1
- package/dist/checkbox.js.map +1 -1
- package/dist/combobox.d.ts.map +1 -1
- package/dist/combobox.js +10 -10
- package/dist/combobox.js.map +1 -1
- package/dist/context-menu.js +9 -9
- package/dist/context-menu.js.map +1 -1
- package/dist/dialog.d.ts.map +1 -1
- package/dist/dialog.js +4 -4
- package/dist/dialog.js.map +1 -1
- package/dist/drawer.js +4 -4
- package/dist/drawer.js.map +1 -1
- package/dist/field.js +2 -2
- package/dist/field.js.map +1 -1
- package/dist/form.js +2 -2
- package/dist/form.js.map +1 -1
- package/dist/infinite-scroll.js +2 -2
- package/dist/infinite-scroll.js.map +1 -1
- package/dist/input-group.d.ts +1 -1
- package/dist/input-group.js +5 -5
- package/dist/input-group.js.map +1 -1
- package/dist/input-otp.js +3 -3
- package/dist/input-otp.js.map +1 -1
- package/dist/input.js +1 -1
- package/dist/input.js.map +1 -1
- package/dist/item.d.ts +1 -1
- package/dist/item.js +4 -4
- package/dist/item.js.map +1 -1
- package/dist/kbd.js +1 -1
- package/dist/kbd.js.map +1 -1
- package/dist/menu.js +9 -9
- package/dist/menu.js.map +1 -1
- package/dist/menubar.js +1 -1
- package/dist/menubar.js.map +1 -1
- package/dist/popover.js +2 -2
- package/dist/popover.js.map +1 -1
- package/dist/preview-card.js +1 -1
- package/dist/preview-card.js.map +1 -1
- package/dist/progress.js +2 -2
- package/dist/progress.js.map +1 -1
- package/dist/radio.js +2 -2
- package/dist/radio.js.map +1 -1
- package/dist/scroll-area.js +2 -2
- package/dist/scroll-area.js.map +1 -1
- package/dist/select.js +8 -8
- package/dist/select.js.map +1 -1
- package/dist/separator.js +1 -1
- package/dist/separator.js.map +1 -1
- package/dist/sidebar.js +19 -19
- package/dist/sidebar.js.map +1 -1
- package/dist/skeleton.js +1 -1
- package/dist/skeleton.js.map +1 -1
- package/dist/slider.js +2 -2
- package/dist/slider.js.map +1 -1
- package/dist/styles.css +493 -202
- package/dist/switch.js +2 -2
- package/dist/switch.js.map +1 -1
- package/dist/table.js +5 -5
- package/dist/table.js.map +1 -1
- package/dist/tabs.js +3 -3
- package/dist/tabs.js.map +1 -1
- package/dist/textarea.js +1 -1
- package/dist/textarea.js.map +1 -1
- package/dist/toast.d.ts +3 -3
- package/dist/toast.d.ts.map +1 -1
- package/dist/toast.js +37 -10
- package/dist/toast.js.map +1 -1
- package/dist/toggle-group.js +2 -2
- package/dist/toggle-group.js.map +1 -1
- package/dist/toggle.js +3 -3
- package/dist/toggle.js.map +1 -1
- package/dist/tooltip.js +1 -1
- package/dist/tooltip.js.map +1 -1
- package/package.json +9 -20
package/README.md
CHANGED
|
@@ -1,8 +1,135 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="assets/logo.svg" alt="Wave UI" width="220" />
|
|
2
3
|
|
|
3
|
-
A component library built on
|
|
4
|
+
<p><strong>A modern React component library built on Base UI and Tailwind CSS,<br />with a 3-tier semantic theme system.</strong></p>
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
[](https://www.npmjs.com/package/@waveso/ui)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
<br />
|
|
12
|
+
|
|
13
|
+
<!-- TODO: replace assets/showcase-placeholder.svg with a real screenshot of the Showcase story across the three themes -->
|
|
14
|
+
<img src="assets/showcase-placeholder.svg" alt="Wave UI across its three themes — Graphite, Ink, and Paper" width="760" />
|
|
15
|
+
|
|
16
|
+
<br /><br />
|
|
17
|
+
|
|
18
|
+
Full documentation coming soon at **[ui.wave.so](https://ui.wave.so)**
|
|
19
|
+
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Why Wave UI
|
|
25
|
+
|
|
26
|
+
A production-ready design-token architecture that's modern, semantic, and highly maintainable.
|
|
27
|
+
|
|
28
|
+
Most UI libraries carry two structural problems:
|
|
29
|
+
|
|
30
|
+
1. **Redundant token names** (`border-border`, `ring-ring`) — which read backwards the moment background and text swap roles: a background becomes `bg-foreground`, text becomes `text-background`.
|
|
31
|
+
2. **Material-style paired tokens** (`card` + `card-foreground`, `popover` + `popover-foreground`) — flexible per layer, but they add complexity, kill the emphasis ladder, and lose free composability. `foreground-muted` is meant as "text _on_ muted," yet most people read it as "muted text." The result is ~14 tokens, half of them redundant.
|
|
32
|
+
|
|
33
|
+
Both models work, and end users never notice. But for the people _building_ on the system, the shape matters.
|
|
34
|
+
|
|
35
|
+
Wave builds around an intuitive, intentional **3-tier hierarchy** — for almost everything:
|
|
36
|
+
|
|
37
|
+
- 3 themes — Paper, Graphite, Ink palettes
|
|
38
|
+
- 3 background colors for surfaces
|
|
39
|
+
- 3 content colors for text
|
|
40
|
+
- 3 identity colors for brand
|
|
41
|
+
- 3 border colors for structural luminance
|
|
42
|
+
- 3 duration tiers for transitions
|
|
43
|
+
- 3 blur values
|
|
44
|
+
- 3 scale sizes
|
|
45
|
+
- 3 stagger times
|
|
46
|
+
- 3 shadows with adaptable color
|
|
47
|
+
- 3 offset amounts for transforms
|
|
48
|
+
- …and more
|
|
49
|
+
|
|
50
|
+
It also builds around 3 semantic intents:
|
|
51
|
+
|
|
52
|
+
- **Background** colors focus on elevation — how deep a surface sits
|
|
53
|
+
- **Content** colors focus on emphasis — how strongly something reads
|
|
54
|
+
- **Border** colors focus on structure — how functional a boundary is
|
|
55
|
+
|
|
56
|
+
This is the right shape for a system centered on an emphasis ladder: clean and minimal, but flexible.
|
|
57
|
+
|
|
58
|
+
### Themes
|
|
59
|
+
|
|
60
|
+
The three themes are a homage to the **pre-digital writing desk** — the _surface_, the _pen_, the _pencil_ — and each maps to its color: cream ***Paper***, blue-black ***Ink***, gray ***Graphite***.
|
|
61
|
+
|
|
62
|
+
**Graphite** is the default **neutral pencil-gray** theme, built on a low-saturation dark-neutral family with a subtle blue bias. That bias keeps the light steps from going flat — a tiny cool cast instead of pure gray.
|
|
63
|
+
|
|
64
|
+
**Paper** and **Ink** are an elegant classic pair, each with a purpose: Paper is tuned for daytime and bright spaces; Ink for night and dark spaces. In every theme the _structure_ (text and borders) stays a cool-biased neutral, while the _surface_ carries the identity.
|
|
65
|
+
|
|
66
|
+
## Taxonomy
|
|
67
|
+
|
|
68
|
+
Wave pays close attention to token taxonomy. Surfaces encode elevation; borders act as _light interference rather than geometry_.
|
|
69
|
+
|
|
70
|
+
### Background
|
|
71
|
+
|
|
72
|
+
- `--foundation` — the deepest layer, the base where elevation starts
|
|
73
|
+
- `--surface` — the middle layer: cards, sidebars, content boxes
|
|
74
|
+
- `--elevated` — the highest layer: floating windows, modals, dropdowns, dialogs
|
|
75
|
+
|
|
76
|
+
### Foreground
|
|
77
|
+
|
|
78
|
+
- `--contrast` — titles, primary text
|
|
79
|
+
- `--muted` — body text, icons, any mark
|
|
80
|
+
- `--soft` — placeholders, hints
|
|
81
|
+
|
|
82
|
+
### Borders
|
|
83
|
+
|
|
84
|
+
- `--line` — subtle separators
|
|
85
|
+
- `--edge` — component boundary
|
|
86
|
+
- `--solid` — structural definition
|
|
87
|
+
|
|
88
|
+
### Ring
|
|
89
|
+
|
|
90
|
+
- `--focus` — active / focus states
|
|
91
|
+
|
|
92
|
+
### Brand
|
|
93
|
+
|
|
94
|
+
- `--primary` — wired to the Wave ramp
|
|
95
|
+
- `--secondary` — neutral fill
|
|
96
|
+
- `--accent` — alternative emphasis
|
|
97
|
+
|
|
98
|
+
### Border strategy
|
|
99
|
+
|
|
100
|
+
This is the sophisticated move most libraries skip.
|
|
101
|
+
|
|
102
|
+
Wave biases heavily toward **transparent (alpha) borders as the default**, with a **solid token reserved for functional states**.
|
|
103
|
+
|
|
104
|
+
Wave's palette behaves like a layered _material system_, not a flat UI. Dark surfaces, a soft text hierarchy, and subtle elevation shifts (low-contrast steps) mean borders should not introduce a new "color layer" — that would break the illusion of depth — yet structure still needs a solid option.
|
|
105
|
+
|
|
106
|
+
Alpha borders **inherit the surface beneath them**, take the **content color as their luminance source**, keep hue consistent across surfaces, and scale naturally.
|
|
107
|
+
|
|
108
|
+
Solid borders compete with the surface ladder and create visual "grid noise" if overused, so use them sparingly. Overusing them flattens everything into "outlined boxes" with reduced perceived elevation — the "Bootstrap feel."
|
|
109
|
+
|
|
110
|
+
The default **alpha** borders are ideal for:
|
|
111
|
+
|
|
112
|
+
- Cards
|
|
113
|
+
- Panels
|
|
114
|
+
- Inputs
|
|
115
|
+
- Subtle separators
|
|
116
|
+
|
|
117
|
+
The **solid** borders are ideal for:
|
|
118
|
+
|
|
119
|
+
- Layout definition (sidebar vs. content)
|
|
120
|
+
- Component grouping
|
|
121
|
+
- Focus containment
|
|
122
|
+
- "Frame-like" boundaries
|
|
123
|
+
|
|
124
|
+
**Rule of thumb:**
|
|
125
|
+
|
|
126
|
+
- If it separates **surface from surface** → alpha border
|
|
127
|
+
- If it separates **layout regions** → solid border
|
|
128
|
+
- If it indicates **interaction / state** → colored alpha border
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Installation
|
|
6
133
|
|
|
7
134
|
```bash
|
|
8
135
|
npm install @waveso/ui @base-ui/react class-variance-authority clsx tailwind-merge
|
|
@@ -10,82 +137,44 @@ npm install @waveso/ui @base-ui/react class-variance-authority clsx tailwind-mer
|
|
|
10
137
|
|
|
11
138
|
## Setup
|
|
12
139
|
|
|
13
|
-
Add the theme preset and
|
|
140
|
+
Add the theme preset and Tailwind to your CSS entry point:
|
|
14
141
|
|
|
15
142
|
```css
|
|
16
143
|
@import "@waveso/ui/styles.css";
|
|
17
144
|
@import "tailwindcss";
|
|
18
145
|
```
|
|
19
146
|
|
|
20
|
-
The preset provides
|
|
147
|
+
The preset provides every CSS variable (colors, radii, motion, shadows) with light and dark support. Override any variable in your own `:root` / `.dark` blocks to customize the theme.
|
|
21
148
|
|
|
22
149
|
## Usage
|
|
23
150
|
|
|
24
|
-
|
|
151
|
+
Every component is its own entry point, so you ship only what you import:
|
|
25
152
|
|
|
26
153
|
```tsx
|
|
27
154
|
import { Button } from '@waveso/ui/button';
|
|
28
|
-
import { Masonry, MasonryItem, MasonrySpannedItem } from '@waveso/ui/masonry';
|
|
29
155
|
import { Card, CardHeader, CardTitle, CardContent } from '@waveso/ui/card';
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Button
|
|
33
|
-
|
|
34
|
-
8 variants, 8 sizes, built on Base UI's `Button` primitive with full keyboard and ARIA support.
|
|
35
156
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
<Button>Default</Button>
|
|
40
|
-
<Button variant="solid">Solid</Button>
|
|
41
|
-
<Button variant="outline">Outline</Button>
|
|
42
|
-
<Button variant="ghost">Ghost</Button>
|
|
43
|
-
<Button variant="destructive">Delete</Button>
|
|
44
|
-
<Button variant="success">Confirm</Button>
|
|
45
|
-
<Button size="xs">Tiny</Button>
|
|
46
|
-
<Button size="icon"><SearchIcon /></Button>
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### Masonry
|
|
50
|
-
|
|
51
|
-
Responsive masonry grid with staggered animations, spanning items, and automatic reflow.
|
|
52
|
-
|
|
53
|
-
```tsx
|
|
54
|
-
import { Masonry, MasonryItem, MasonrySpannedItem } from '@waveso/ui/masonry';
|
|
55
|
-
|
|
56
|
-
<Masonry columns={3} gap={16}>
|
|
57
|
-
<MasonryItem>
|
|
58
|
-
<Card>
|
|
59
|
-
<CardContent>Standard item</CardContent>
|
|
60
|
-
</Card>
|
|
61
|
-
</MasonryItem>
|
|
62
|
-
<MasonrySpannedItem>
|
|
63
|
-
<Card>
|
|
64
|
-
<CardContent>This spans two columns</CardContent>
|
|
65
|
-
</Card>
|
|
66
|
-
</MasonrySpannedItem>
|
|
67
|
-
<MasonryItem>
|
|
157
|
+
export function Example() {
|
|
158
|
+
return (
|
|
68
159
|
<Card>
|
|
69
|
-
<
|
|
160
|
+
<CardHeader>
|
|
161
|
+
<CardTitle>Get started</CardTitle>
|
|
162
|
+
</CardHeader>
|
|
163
|
+
<CardContent>
|
|
164
|
+
<Button>Click me</Button>
|
|
165
|
+
</CardContent>
|
|
70
166
|
</Card>
|
|
71
|
-
|
|
72
|
-
|
|
167
|
+
);
|
|
168
|
+
}
|
|
73
169
|
```
|
|
74
170
|
|
|
171
|
+
Every component is built on a [Base UI](https://base-ui.com) primitive — full keyboard and ARIA support — and styled entirely through the theme tokens above, so overriding a token propagates everywhere.
|
|
172
|
+
|
|
75
173
|
## Components
|
|
76
174
|
|
|
77
|
-
|
|
175
|
+
A comprehensive set spanning **actions, forms, layout, navigation, overlays, feedback, data display, and motion effects** — all built on Base UI primitives and driven by the theme tokens.
|
|
78
176
|
|
|
79
|
-
|
|
80
|
-
|---|---|
|
|
81
|
-
| **Actions** | Button, Button Group, Toggle, Toggle Group |
|
|
82
|
-
| **Forms** | Input, Textarea, Checkbox, Switch, Radio, Radio Group, Select, Combobox, Autocomplete, Slider, Calendar, Input OTP, Field, Form, Label, Input Group |
|
|
83
|
-
| **Layout** | Card, Masonry, Separator, Aspect Ratio, Scroll Area, Collapsible, Accordion, Tabs, Sidebar |
|
|
84
|
-
| **Navigation** | Breadcrumb, Navigation Menu, Pagination, Menubar |
|
|
85
|
-
| **Overlays** | Dialog, Alert Dialog, Sheet, Popover, Tooltip, Preview Card, Context Menu, Menu |
|
|
86
|
-
| **Feedback** | Alert, Badge, Progress, Skeleton, Spinner, Toaster, Empty |
|
|
87
|
-
| **Data** | Table, Avatar, Kbd, Item, Infinite Scroll |
|
|
88
|
-
| **Effects** | Burst, Explode, Encrypted Text, Shimmering Text, Expandable Tab, Dotted Glow Background |
|
|
177
|
+
Browse every component, with live variants and source, in **Storybook** (`npm run storybook`). A full documentation site is on the way at **[ui.wave.so](https://ui.wave.so)**.
|
|
89
178
|
|
|
90
179
|
## Requirements
|
|
91
180
|
|
|
@@ -93,23 +182,19 @@ import { Masonry, MasonryItem, MasonrySpannedItem } from '@waveso/ui/masonry';
|
|
|
93
182
|
|---|---|
|
|
94
183
|
| React | ^19.0.0 |
|
|
95
184
|
| React DOM | ^19.0.0 |
|
|
96
|
-
| Base UI | ^1.
|
|
185
|
+
| Base UI | ^1.5.0 |
|
|
97
186
|
| Tailwind CSS | v4 |
|
|
98
187
|
| CVA | ^0.7.0 |
|
|
99
188
|
| clsx | ^2.0.0 |
|
|
100
189
|
| tailwind-merge | ^3.0.0 |
|
|
101
190
|
|
|
102
|
-
Some components have optional peer dependencies:
|
|
191
|
+
Some components have optional peer dependencies — install only what you use:
|
|
103
192
|
|
|
104
|
-
- **Calendar** — `react-day-picker`
|
|
105
|
-
- **Carousel** — `embla-carousel-react`
|
|
106
193
|
- **Form** — `react-hook-form`
|
|
107
194
|
- **Input OTP** — `input-otp`
|
|
108
195
|
- **Animations** — `motion`
|
|
109
196
|
- **Sidebar** — `usehooks-ts`
|
|
110
197
|
|
|
111
|
-
Install only what you use.
|
|
112
|
-
|
|
113
198
|
## Development
|
|
114
199
|
|
|
115
200
|
```bash
|
|
@@ -125,6 +210,7 @@ npm run dev # Watch mode
|
|
|
125
210
|
```
|
|
126
211
|
.changeset/ # Changesets config
|
|
127
212
|
.storybook/ # Storybook config + theme CSS
|
|
213
|
+
assets/ # README / brand assets
|
|
128
214
|
src/
|
|
129
215
|
*.tsx # Component source files
|
|
130
216
|
*.stories.tsx # Storybook stories
|
|
@@ -144,9 +230,7 @@ This project uses [Changesets](https://github.com/changesets/changesets) with Gi
|
|
|
144
230
|
<details>
|
|
145
231
|
<summary>Manual release (without CI)</summary>
|
|
146
232
|
|
|
147
|
-
If you're not using the GitHub Actions workflow, you can publish manually.
|
|
148
|
-
Changesets will skip versions already published to npm, so this won't
|
|
149
|
-
conflict if CI has already run.
|
|
233
|
+
If you're not using the GitHub Actions workflow, you can publish manually. Changesets skips versions already published to npm, so this won't conflict if CI has already run.
|
|
150
234
|
|
|
151
235
|
```bash
|
|
152
236
|
npx changeset # Create a changeset
|
|
@@ -158,4 +242,4 @@ npm run release # Build and publish to npm
|
|
|
158
242
|
|
|
159
243
|
## License
|
|
160
244
|
|
|
161
|
-
MIT
|
|
245
|
+
[MIT](./LICENSE)
|
package/dist/accordion.js
CHANGED
|
@@ -15,7 +15,7 @@ function Accordion({ className, ...props }) {
|
|
|
15
15
|
function AccordionItem({ className, ...props }) {
|
|
16
16
|
return /* @__PURE__ */ jsx(Accordion$1.Item, {
|
|
17
17
|
"data-slot": "accordion-item",
|
|
18
|
-
className: cn("not-last:border-b", className),
|
|
18
|
+
className: cn("not-last:border-b border-edge", className),
|
|
19
19
|
...props
|
|
20
20
|
});
|
|
21
21
|
}
|
|
@@ -25,7 +25,7 @@ function AccordionTrigger({ className, children, ...props }) {
|
|
|
25
25
|
className: "flex",
|
|
26
26
|
children: /* @__PURE__ */ jsxs(Accordion$1.Trigger, {
|
|
27
27
|
"data-slot": "accordion-trigger",
|
|
28
|
-
className: cn("cursor-clickable focus-visible:ring-
|
|
28
|
+
className: cn("cursor-clickable focus-visible:ring-focus/50 focus-visible:border-focus focus-visible:after:border-focus **:data-[slot=accordion-trigger-icon]:text-muted group/accordion-trigger relative flex flex-1 items-start justify-between rounded-md border border-transparent py-2.5 text-left text-sm font-medium motion-color outline-none hover:underline focus-visible:ring-3 aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4", className),
|
|
29
29
|
...props,
|
|
30
30
|
children: [
|
|
31
31
|
children,
|
|
@@ -48,7 +48,7 @@ function AccordionContent({ className, children, ...props }) {
|
|
|
48
48
|
...props,
|
|
49
49
|
children: /* @__PURE__ */ jsx("div", {
|
|
50
50
|
"data-slot": "accordion-content",
|
|
51
|
-
className: cn("[&_a]:hover:text-
|
|
51
|
+
className: cn("[&_a]:hover:text-contrast h-(--accordion-panel-height) pt-0 pb-2.5 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4", className),
|
|
52
52
|
children
|
|
53
53
|
})
|
|
54
54
|
});
|
package/dist/accordion.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion.js","names":["AccordionPrimitive"],"sources":["../src/accordion.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\n\nimport { Accordion as AccordionPrimitive } from \"@base-ui/react/accordion\"\n\nimport { cn } from \"./lib/utils\"\n\nimport { ChevronDownIcon, ChevronUpIcon } from \"./lib/internal-icons\"\n\ntype AccordionProps = React.ComponentProps<typeof AccordionPrimitive.Root>\ntype AccordionItemProps = React.ComponentProps<typeof AccordionPrimitive.Item>\ntype AccordionTriggerProps = React.ComponentProps<typeof AccordionPrimitive.Trigger>\ntype AccordionContentProps = React.ComponentProps<typeof AccordionPrimitive.Panel>\n\nfunction Accordion({ className, ...props }: AccordionProps) {\n return (\n <AccordionPrimitive.Root\n data-slot=\"accordion\"\n className={cn(\n \"overflow-hidden flex w-full flex-col\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction AccordionItem({ className, ...props }: AccordionItemProps) {\n return (\n <AccordionPrimitive.Item\n data-slot=\"accordion-item\"\n className={cn(\n \"not-last:border-b\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction AccordionTrigger({\n className,\n children,\n ...props\n}: AccordionTriggerProps) {\n return (\n <AccordionPrimitive.Header data-slot=\"accordion-header\" className=\"flex\">\n <AccordionPrimitive.Trigger\n data-slot=\"accordion-trigger\"\n className={cn(\n \"cursor-clickable focus-visible:ring-
|
|
1
|
+
{"version":3,"file":"accordion.js","names":["AccordionPrimitive"],"sources":["../src/accordion.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\n\nimport { Accordion as AccordionPrimitive } from \"@base-ui/react/accordion\"\n\nimport { cn } from \"./lib/utils\"\n\nimport { ChevronDownIcon, ChevronUpIcon } from \"./lib/internal-icons\"\n\ntype AccordionProps = React.ComponentProps<typeof AccordionPrimitive.Root>\ntype AccordionItemProps = React.ComponentProps<typeof AccordionPrimitive.Item>\ntype AccordionTriggerProps = React.ComponentProps<typeof AccordionPrimitive.Trigger>\ntype AccordionContentProps = React.ComponentProps<typeof AccordionPrimitive.Panel>\n\nfunction Accordion({ className, ...props }: AccordionProps) {\n return (\n <AccordionPrimitive.Root\n data-slot=\"accordion\"\n className={cn(\n \"overflow-hidden flex w-full flex-col\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction AccordionItem({ className, ...props }: AccordionItemProps) {\n return (\n <AccordionPrimitive.Item\n data-slot=\"accordion-item\"\n className={cn(\n \"not-last:border-b border-edge\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction AccordionTrigger({\n className,\n children,\n ...props\n}: AccordionTriggerProps) {\n return (\n <AccordionPrimitive.Header data-slot=\"accordion-header\" className=\"flex\">\n <AccordionPrimitive.Trigger\n data-slot=\"accordion-trigger\"\n className={cn(\n \"cursor-clickable focus-visible:ring-focus/50 focus-visible:border-focus focus-visible:after:border-focus **:data-[slot=accordion-trigger-icon]:text-muted group/accordion-trigger relative flex flex-1 items-start justify-between rounded-md border border-transparent py-2.5 text-left text-sm font-medium motion-color outline-none hover:underline focus-visible:ring-3 aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4\",\n className\n )}\n {...props}\n >\n {children}\n <ChevronDownIcon data-slot=\"accordion-trigger-icon\" className=\"pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden\" />\n <ChevronUpIcon data-slot=\"accordion-trigger-icon\" className=\"pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline\" />\n </AccordionPrimitive.Trigger>\n </AccordionPrimitive.Header>\n )\n}\n\nfunction AccordionContent({\n className,\n children,\n ...props\n}: AccordionContentProps) {\n return (\n <AccordionPrimitive.Panel\n data-slot=\"accordion-panel\"\n className=\"data-open:animate-accordion-down data-closed:animate-accordion-up overflow-hidden text-sm\"\n {...props}\n >\n <div\n data-slot=\"accordion-content\"\n className={cn(\n \"[&_a]:hover:text-contrast h-(--accordion-panel-height) pt-0 pb-2.5 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4\",\n className\n )}\n >\n {children}\n </div>\n </AccordionPrimitive.Panel>\n )\n}\n\nexport {\n Accordion,\n AccordionItem,\n AccordionTrigger,\n AccordionContent,\n}\n"],"mappings":";;;;;;;AAeA,SAAS,UAAU,EAAE,WAAW,GAAG,SAAyB;AAC1D,QACE,oBAACA,YAAmB,MAApB;EACE,aAAU;EACV,WAAW,GACT,wCACA,UACD;EACD,GAAI;EACJ,CAAA;;AAIN,SAAS,cAAc,EAAE,WAAW,GAAG,SAA6B;AAClE,QACE,oBAACA,YAAmB,MAApB;EACE,aAAU;EACV,WAAW,GACT,iCACA,UACD;EACD,GAAI;EACJ,CAAA;;AAIN,SAAS,iBAAiB,EACxB,WACA,UACA,GAAG,SACqB;AACxB,QACE,oBAACA,YAAmB,QAApB;EAA2B,aAAU;EAAmB,WAAU;YAChE,qBAACA,YAAmB,SAApB;GACE,aAAU;GACV,WAAW,GACT,qgBACA,UACD;GACD,GAAI;aANN;IAQG;IACD,oBAAC,iBAAD;KAAiB,aAAU;KAAyB,WAAU;KAA8E,CAAA;IAC5I,oBAAC,eAAD;KAAe,aAAU;KAAyB,WAAU;KAAqF,CAAA;IACtH;;EACH,CAAA;;AAIhC,SAAS,iBAAiB,EACxB,WACA,UACA,GAAG,SACqB;AACxB,QACE,oBAACA,YAAmB,OAApB;EACE,aAAU;EACV,WAAU;EACV,GAAI;YAEJ,oBAAC,OAAD;GACE,aAAU;GACV,WAAW,GACT,yLACA,UACD;GAEA;GACG,CAAA;EACmB,CAAA"}
|
package/dist/action-bar.js
CHANGED
|
@@ -182,10 +182,10 @@ function ActionBarProvider({ children, message = "You have unsaved changes", plu
|
|
|
182
182
|
className: "fixed bottom-6 left-1/2 z-50",
|
|
183
183
|
children: /* @__PURE__ */ jsxs("div", {
|
|
184
184
|
"data-slot": "action-bar-content",
|
|
185
|
-
className: cn("flex items-center gap-6 rounded-
|
|
185
|
+
className: cn("flex items-center gap-6 rounded-lg border border-line/60 bg-surface px-5 py-2.5 shadow-lg ring-1 ring-line backdrop-blur-sm", className),
|
|
186
186
|
children: [/* @__PURE__ */ jsx("span", {
|
|
187
187
|
"data-slot": "action-bar-message",
|
|
188
|
-
className: "whitespace-nowrap text-sm font-medium text-
|
|
188
|
+
className: "whitespace-nowrap text-sm font-medium text-contrast",
|
|
189
189
|
children: displayMessage
|
|
190
190
|
}), /* @__PURE__ */ jsxs("div", {
|
|
191
191
|
"data-slot": "action-bar-actions",
|
package/dist/action-bar.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-bar.js","names":[],"sources":["../src/action-bar.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"./lib/utils\"\nimport { Button } from \"./button\"\nimport { Spinner } from \"./spinner\"\nimport { Kbd } from \"./kbd\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** State a form registers with the ActionBar. */\nexport interface ActionBarEntry {\n /** Whether this form has unsaved changes. */\n hasChanges: boolean\n /** Whether this form is currently saving. */\n saving: boolean\n /** Save this form's changes. */\n onSave: () => void | Promise<void>\n /** Discard this form's changes. */\n onReset: () => void\n}\n\ninterface ActionBarContextValue {\n register: (id: string, entry: ActionBarEntry) => void\n unregister: (id: string) => void\n /** Returns true if any registered form has unsaved changes. */\n hasDirty: () => boolean\n /** Trigger the jiggle effect on blocked navigation. */\n jiggle: () => void\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst JIGGLE_STEPS = 6\nconst JIGGLE_INTERVAL = 50\nconst JIGGLE_RANGE = 15\n\n/**\n * SSR-safe platform detection. Returns `false` on the server and on the\n * first client render so hydration matches, then resolves the real\n * platform after mount. Avoids the `⌘S`/`Ctrl+S` hydration mismatch\n * that a module-scope `navigator` read would cause.\n */\nfunction useIsMac(): boolean {\n const [isMac, setIsMac] = React.useState(false)\n React.useEffect(() => {\n setIsMac(/Mac|iPhone|iPad/.test(navigator.userAgent))\n }, [])\n return isMac\n}\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst ActionBarContext = React.createContext<ActionBarContextValue | null>(null)\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\nexport interface ActionBarProviderProps {\n children: React.ReactNode\n /** Message when one form has changes. */\n message?: string\n /** Message template for multiple dirty forms. */\n pluralMessage?: (count: number) => string\n /**\n * Announced to assistive tech (and the only feedback reduced-motion\n * users get) when guarded navigation is blocked.\n */\n blockedMessage?: string\n /** Additional className for the bar's outer wrapper. */\n className?: string\n}\n\n/**\n * Global action bar for unsaved changes.\n *\n * Place once at the root layout. Forms register via `useActionBar()`.\n *\n * Features:\n * - Multi-form aggregation (Save All / Reset All)\n * - ⌘S / Ctrl+S keyboard shortcut\n * - Navigation guard with Discord-style page jiggle\n * - `beforeunload` guard for browser close/refresh\n *\n * @example\n * ```tsx\n * // Root layout — once\n * <ActionBarProvider>{children}</ActionBarProvider>\n *\n * // Any form component\n * useActionBar('profile', { hasChanges, saving, onSave, onReset });\n * ```\n */\nfunction ActionBarProvider({\n children,\n message = \"You have unsaved changes\",\n pluralMessage = (count) => `${count} unsaved changes`,\n blockedMessage = \"Save or reset your changes before leaving this page\",\n className,\n}: ActionBarProviderProps) {\n const entriesRef = React.useRef(new Map<string, ActionBarEntry>())\n const [, forceUpdate] = React.useState(0)\n const [jiggleTransform, setJiggleTransform] = React.useState<string | null>(\n null\n )\n const jiggleTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(\n null\n )\n const contentRef = React.useRef<HTMLDivElement>(null)\n const isMac = useIsMac()\n const prefersReducedMotion = useReducedMotion()\n const reducedMotionRef = React.useRef(prefersReducedMotion)\n reducedMotionRef.current = prefersReducedMotion\n // Assertive announcement for blocked navigation. This is the *only*\n // feedback channel that works for screen-reader and reduced-motion\n // users (the visual jiggle is suppressed for them), so it is driven\n // independently of the shake.\n const [blockedNotice, setBlockedNotice] = React.useState(\"\")\n\n // --- Registry ---\n\n const register = React.useCallback((id: string, entry: ActionBarEntry) => {\n const prev = entriesRef.current.get(id)\n entriesRef.current.set(id, entry)\n if (\n !prev ||\n prev.hasChanges !== entry.hasChanges ||\n prev.saving !== entry.saving\n ) {\n forceUpdate((n) => n + 1)\n }\n }, [])\n\n const unregister = React.useCallback((id: string) => {\n if (entriesRef.current.delete(id)) {\n forceUpdate((n) => n + 1)\n }\n }, [])\n\n const hasDirty = React.useCallback(() => {\n for (const entry of entriesRef.current.values()) {\n if (entry.hasChanges) return true\n }\n return false\n }, [])\n\n // --- Jiggle ---\n\n const jiggle = React.useCallback(() => {\n // Announce to assistive tech on every blocked attempt. Clearing\n // first guarantees a text change so an identical message\n // re-announces on repeat presses.\n setBlockedNotice(\"\")\n requestAnimationFrame(() => setBlockedNotice(blockedMessage))\n\n // Vestibular safety: the random-translate shake is purely a motion\n // cue, so skip it under prefers-reduced-motion. The assertive\n // announcement above is the accessible equivalent.\n if (reducedMotionRef.current) return\n if (jiggleTimerRef.current) return\n\n let step = 0\n\n function tick() {\n step++\n if (step >= JIGGLE_STEPS) {\n setJiggleTransform(null)\n jiggleTimerRef.current = null\n return\n }\n const x = (Math.random() - 0.5) * 2 * JIGGLE_RANGE\n const y = (Math.random() - 0.5) * 2 * JIGGLE_RANGE\n setJiggleTransform(\n `translate3d(${x.toFixed(5)}px, ${y.toFixed(5)}px, 0px)`\n )\n jiggleTimerRef.current = setTimeout(tick, JIGGLE_INTERVAL)\n }\n\n tick()\n }, [blockedMessage])\n\n // --- Aggregated state ---\n\n const dirty: ActionBarEntry[] = []\n let anySaving = false\n\n for (const entry of entriesRef.current.values()) {\n if (entry.hasChanges) dirty.push(entry)\n if (entry.saving) anySaving = true\n }\n\n const showBar = dirty.length > 0 || anySaving\n const dirtyCount = dirty.length\n const displayMessage =\n dirtyCount > 1 ? pluralMessage(dirtyCount) : message\n\n // --- Save / Reset ---\n\n const handleSaveAll = React.useCallback(async () => {\n const saves = [...entriesRef.current.values()]\n .filter((e) => e.hasChanges)\n .map((e) => e.onSave())\n await Promise.all(saves)\n }, [])\n\n const handleResetAll = React.useCallback(() => {\n for (const entry of entriesRef.current.values()) {\n if (entry.hasChanges) entry.onReset()\n }\n }, [])\n\n // --- ⌘S / Ctrl+S ---\n\n React.useEffect(() => {\n function handleKeyDown(e: KeyboardEvent) {\n if ((e.metaKey || e.ctrlKey) && e.key === \"s\") {\n e.preventDefault()\n if (hasDirty()) {\n handleSaveAll()\n }\n }\n }\n\n document.addEventListener(\"keydown\", handleKeyDown)\n return () => document.removeEventListener(\"keydown\", handleKeyDown)\n }, [hasDirty, handleSaveAll])\n\n // --- beforeunload ---\n\n React.useEffect(() => {\n function handleBeforeUnload(e: BeforeUnloadEvent) {\n if (hasDirty()) {\n e.preventDefault()\n }\n }\n\n window.addEventListener(\"beforeunload\", handleBeforeUnload)\n return () => window.removeEventListener(\"beforeunload\", handleBeforeUnload)\n }, [hasDirty])\n\n // --- Context ---\n\n const ctx = React.useMemo(\n () => ({ register, unregister, hasDirty, jiggle }),\n [register, unregister, hasDirty, jiggle]\n )\n\n return (\n <ActionBarContext.Provider value={ctx}>\n <div\n data-slot=\"action-bar-shell\"\n ref={contentRef}\n style={jiggleTransform ? { transform: jiggleTransform } : undefined}\n >\n {children}\n </div>\n\n {/*\n Persistent SR-only live regions. They are always mounted so the\n announcement fires reliably when their text changes — a region\n mounted already-populated is not announced consistently across\n screen reader / browser combinations.\n */}\n <span data-slot=\"action-bar-status\" aria-live=\"polite\" className=\"sr-only\">\n {showBar ? displayMessage : \"\"}\n </span>\n <span\n data-slot=\"action-bar-blocked-status\"\n aria-live=\"assertive\"\n className=\"sr-only\"\n >\n {blockedNotice}\n </span>\n\n <AnimatePresence>\n {showBar && (\n <motion.div\n data-slot=\"action-bar\"\n initial={\n prefersReducedMotion\n ? { opacity: 0, x: \"-50%\" }\n : { opacity: 0, y: 20, x: \"-50%\" }\n }\n animate={{ opacity: 1, y: 0, x: \"-50%\" }}\n exit={\n prefersReducedMotion\n ? { opacity: 0, x: \"-50%\" }\n : { opacity: 0, y: 20, x: \"-50%\" }\n }\n transition={{ duration: 0.2, ease: \"easeOut\" }}\n className=\"fixed bottom-6 left-1/2 z-50\"\n >\n <div\n data-slot=\"action-bar-content\"\n className={cn(\n \"flex items-center gap-6 rounded-xl border border-border/60 bg-card px-5 py-2.5 shadow-lg ring-1 ring-black/5 backdrop-blur-sm dark:ring-white/5\",\n className\n )}\n >\n <span\n data-slot=\"action-bar-message\"\n className=\"whitespace-nowrap text-sm font-medium text-foreground\"\n >\n {displayMessage}\n </span>\n\n <div\n data-slot=\"action-bar-actions\"\n className=\"flex items-center gap-1.5\"\n >\n <Button\n type=\"button\"\n variant=\"ghost\"\n disabled={anySaving}\n onClick={handleResetAll}\n className=\"h-9 px-3 text-sm hover:text-destructive\"\n >\n Reset\n </Button>\n\n <Button\n type=\"button\"\n variant=\"default\"\n disabled={anySaving}\n onClick={handleSaveAll}\n className=\"h-9 px-4 text-sm\"\n >\n {anySaving ? (\n <>\n <Spinner className=\"mr-1.5 h-3.5 w-3.5\" />\n Saving\n </>\n ) : (\n <>\n Save\n <Kbd className=\"ml-1.5\">\n {isMac ? \"⌘S\" : \"Ctrl+S\"}\n </Kbd>\n </>\n )}\n </Button>\n </div>\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n </ActionBarContext.Provider>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Hook — register form state\n// ---------------------------------------------------------------------------\n\n/**\n * Register a form's unsaved state with the global ActionBar.\n *\n * Unregisters automatically on unmount.\n *\n * @param id — Unique key (e.g., 'profile', 'notifications')\n * @param entry — Current form state\n */\nfunction useActionBar(id: string, entry: ActionBarEntry) {\n const ctx = React.useContext(ActionBarContext)\n if (!ctx)\n throw new Error(\"useActionBar must be used within <ActionBarProvider>\")\n\n const { register, unregister } = ctx\n\n // Intentionally no dependency array: `entry` (and its `onSave`/\n // `onReset` closures) is rebuilt by the caller every render, so we\n // must re-register each render to keep the latest closures. The\n // `register` impl diffs the entry and only forces an update when\n // `hasChanges`/`saving` actually changed, so this can't loop.\n React.useEffect(() => {\n register(id, entry)\n })\n\n React.useEffect(() => {\n return () => unregister(id)\n }, [id, unregister])\n}\n\n// ---------------------------------------------------------------------------\n// Navigation guard hook\n// ---------------------------------------------------------------------------\n\n/**\n * Returns a function that guards navigation.\n * If unsaved changes exist, triggers the jiggle and blocks navigation.\n * Otherwise navigates normally via the provided callback.\n *\n * @param navigate — Your navigation function (e.g., `router.push`)\n *\n * @example\n * ```tsx\n * const router = useRouter();\n * const guardedPush = useActionBarGuard(router.push);\n * <button onClick={() => guardedPush('/settings')}>Settings</button>\n * ```\n */\nfunction useActionBarGuard(navigate: (href: string) => void) {\n const ctx = React.useContext(ActionBarContext)\n\n return React.useCallback(\n (href: string) => {\n if (ctx?.hasDirty()) {\n ctx.jiggle()\n return false\n }\n navigate(href)\n return true\n },\n [ctx, navigate]\n )\n}\n\n// ---------------------------------------------------------------------------\n// Exports\n// ---------------------------------------------------------------------------\n\nexport {\n ActionBarProvider,\n useActionBar,\n useActionBarGuard,\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"action-bar.js","names":[],"sources":["../src/action-bar.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"./lib/utils\"\nimport { Button } from \"./button\"\nimport { Spinner } from \"./spinner\"\nimport { Kbd } from \"./kbd\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** State a form registers with the ActionBar. */\nexport interface ActionBarEntry {\n /** Whether this form has unsaved changes. */\n hasChanges: boolean\n /** Whether this form is currently saving. */\n saving: boolean\n /** Save this form's changes. */\n onSave: () => void | Promise<void>\n /** Discard this form's changes. */\n onReset: () => void\n}\n\ninterface ActionBarContextValue {\n register: (id: string, entry: ActionBarEntry) => void\n unregister: (id: string) => void\n /** Returns true if any registered form has unsaved changes. */\n hasDirty: () => boolean\n /** Trigger the jiggle effect on blocked navigation. */\n jiggle: () => void\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst JIGGLE_STEPS = 6\nconst JIGGLE_INTERVAL = 50\nconst JIGGLE_RANGE = 15\n\n/**\n * SSR-safe platform detection. Returns `false` on the server and on the\n * first client render so hydration matches, then resolves the real\n * platform after mount. Avoids the `⌘S`/`Ctrl+S` hydration mismatch\n * that a module-scope `navigator` read would cause.\n */\nfunction useIsMac(): boolean {\n const [isMac, setIsMac] = React.useState(false)\n React.useEffect(() => {\n setIsMac(/Mac|iPhone|iPad/.test(navigator.userAgent))\n }, [])\n return isMac\n}\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst ActionBarContext = React.createContext<ActionBarContextValue | null>(null)\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\nexport interface ActionBarProviderProps {\n children: React.ReactNode\n /** Message when one form has changes. */\n message?: string\n /** Message template for multiple dirty forms. */\n pluralMessage?: (count: number) => string\n /**\n * Announced to assistive tech (and the only feedback reduced-motion\n * users get) when guarded navigation is blocked.\n */\n blockedMessage?: string\n /** Additional className for the bar's outer wrapper. */\n className?: string\n}\n\n/**\n * Global action bar for unsaved changes.\n *\n * Place once at the root layout. Forms register via `useActionBar()`.\n *\n * Features:\n * - Multi-form aggregation (Save All / Reset All)\n * - ⌘S / Ctrl+S keyboard shortcut\n * - Navigation guard with Discord-style page jiggle\n * - `beforeunload` guard for browser close/refresh\n *\n * @example\n * ```tsx\n * // Root layout — once\n * <ActionBarProvider>{children}</ActionBarProvider>\n *\n * // Any form component\n * useActionBar('profile', { hasChanges, saving, onSave, onReset });\n * ```\n */\nfunction ActionBarProvider({\n children,\n message = \"You have unsaved changes\",\n pluralMessage = (count) => `${count} unsaved changes`,\n blockedMessage = \"Save or reset your changes before leaving this page\",\n className,\n}: ActionBarProviderProps) {\n const entriesRef = React.useRef(new Map<string, ActionBarEntry>())\n const [, forceUpdate] = React.useState(0)\n const [jiggleTransform, setJiggleTransform] = React.useState<string | null>(\n null\n )\n const jiggleTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(\n null\n )\n const contentRef = React.useRef<HTMLDivElement>(null)\n const isMac = useIsMac()\n const prefersReducedMotion = useReducedMotion()\n const reducedMotionRef = React.useRef(prefersReducedMotion)\n reducedMotionRef.current = prefersReducedMotion\n // Assertive announcement for blocked navigation. This is the *only*\n // feedback channel that works for screen-reader and reduced-motion\n // users (the visual jiggle is suppressed for them), so it is driven\n // independently of the shake.\n const [blockedNotice, setBlockedNotice] = React.useState(\"\")\n\n // --- Registry ---\n\n const register = React.useCallback((id: string, entry: ActionBarEntry) => {\n const prev = entriesRef.current.get(id)\n entriesRef.current.set(id, entry)\n if (\n !prev ||\n prev.hasChanges !== entry.hasChanges ||\n prev.saving !== entry.saving\n ) {\n forceUpdate((n) => n + 1)\n }\n }, [])\n\n const unregister = React.useCallback((id: string) => {\n if (entriesRef.current.delete(id)) {\n forceUpdate((n) => n + 1)\n }\n }, [])\n\n const hasDirty = React.useCallback(() => {\n for (const entry of entriesRef.current.values()) {\n if (entry.hasChanges) return true\n }\n return false\n }, [])\n\n // --- Jiggle ---\n\n const jiggle = React.useCallback(() => {\n // Announce to assistive tech on every blocked attempt. Clearing\n // first guarantees a text change so an identical message\n // re-announces on repeat presses.\n setBlockedNotice(\"\")\n requestAnimationFrame(() => setBlockedNotice(blockedMessage))\n\n // Vestibular safety: the random-translate shake is purely a motion\n // cue, so skip it under prefers-reduced-motion. The assertive\n // announcement above is the accessible equivalent.\n if (reducedMotionRef.current) return\n if (jiggleTimerRef.current) return\n\n let step = 0\n\n function tick() {\n step++\n if (step >= JIGGLE_STEPS) {\n setJiggleTransform(null)\n jiggleTimerRef.current = null\n return\n }\n const x = (Math.random() - 0.5) * 2 * JIGGLE_RANGE\n const y = (Math.random() - 0.5) * 2 * JIGGLE_RANGE\n setJiggleTransform(\n `translate3d(${x.toFixed(5)}px, ${y.toFixed(5)}px, 0px)`\n )\n jiggleTimerRef.current = setTimeout(tick, JIGGLE_INTERVAL)\n }\n\n tick()\n }, [blockedMessage])\n\n // --- Aggregated state ---\n\n const dirty: ActionBarEntry[] = []\n let anySaving = false\n\n for (const entry of entriesRef.current.values()) {\n if (entry.hasChanges) dirty.push(entry)\n if (entry.saving) anySaving = true\n }\n\n const showBar = dirty.length > 0 || anySaving\n const dirtyCount = dirty.length\n const displayMessage =\n dirtyCount > 1 ? pluralMessage(dirtyCount) : message\n\n // --- Save / Reset ---\n\n const handleSaveAll = React.useCallback(async () => {\n const saves = [...entriesRef.current.values()]\n .filter((e) => e.hasChanges)\n .map((e) => e.onSave())\n await Promise.all(saves)\n }, [])\n\n const handleResetAll = React.useCallback(() => {\n for (const entry of entriesRef.current.values()) {\n if (entry.hasChanges) entry.onReset()\n }\n }, [])\n\n // --- ⌘S / Ctrl+S ---\n\n React.useEffect(() => {\n function handleKeyDown(e: KeyboardEvent) {\n if ((e.metaKey || e.ctrlKey) && e.key === \"s\") {\n e.preventDefault()\n if (hasDirty()) {\n handleSaveAll()\n }\n }\n }\n\n document.addEventListener(\"keydown\", handleKeyDown)\n return () => document.removeEventListener(\"keydown\", handleKeyDown)\n }, [hasDirty, handleSaveAll])\n\n // --- beforeunload ---\n\n React.useEffect(() => {\n function handleBeforeUnload(e: BeforeUnloadEvent) {\n if (hasDirty()) {\n e.preventDefault()\n }\n }\n\n window.addEventListener(\"beforeunload\", handleBeforeUnload)\n return () => window.removeEventListener(\"beforeunload\", handleBeforeUnload)\n }, [hasDirty])\n\n // --- Context ---\n\n const ctx = React.useMemo(\n () => ({ register, unregister, hasDirty, jiggle }),\n [register, unregister, hasDirty, jiggle]\n )\n\n return (\n <ActionBarContext.Provider value={ctx}>\n <div\n data-slot=\"action-bar-shell\"\n ref={contentRef}\n style={jiggleTransform ? { transform: jiggleTransform } : undefined}\n >\n {children}\n </div>\n\n {/*\n Persistent SR-only live regions. They are always mounted so the\n announcement fires reliably when their text changes — a region\n mounted already-populated is not announced consistently across\n screen reader / browser combinations.\n */}\n <span data-slot=\"action-bar-status\" aria-live=\"polite\" className=\"sr-only\">\n {showBar ? displayMessage : \"\"}\n </span>\n <span\n data-slot=\"action-bar-blocked-status\"\n aria-live=\"assertive\"\n className=\"sr-only\"\n >\n {blockedNotice}\n </span>\n\n <AnimatePresence>\n {showBar && (\n <motion.div\n data-slot=\"action-bar\"\n initial={\n prefersReducedMotion\n ? { opacity: 0, x: \"-50%\" }\n : { opacity: 0, y: 20, x: \"-50%\" }\n }\n animate={{ opacity: 1, y: 0, x: \"-50%\" }}\n exit={\n prefersReducedMotion\n ? { opacity: 0, x: \"-50%\" }\n : { opacity: 0, y: 20, x: \"-50%\" }\n }\n transition={{ duration: 0.2, ease: \"easeOut\" }}\n className=\"fixed bottom-6 left-1/2 z-50\"\n >\n <div\n data-slot=\"action-bar-content\"\n className={cn(\n \"flex items-center gap-6 rounded-lg border border-line/60 bg-surface px-5 py-2.5 shadow-lg ring-1 ring-line backdrop-blur-sm\",\n className\n )}\n >\n <span\n data-slot=\"action-bar-message\"\n className=\"whitespace-nowrap text-sm font-medium text-contrast\"\n >\n {displayMessage}\n </span>\n\n <div\n data-slot=\"action-bar-actions\"\n className=\"flex items-center gap-1.5\"\n >\n <Button\n type=\"button\"\n variant=\"ghost\"\n disabled={anySaving}\n onClick={handleResetAll}\n className=\"h-9 px-3 text-sm hover:text-destructive\"\n >\n Reset\n </Button>\n\n <Button\n type=\"button\"\n variant=\"default\"\n disabled={anySaving}\n onClick={handleSaveAll}\n className=\"h-9 px-4 text-sm\"\n >\n {anySaving ? (\n <>\n <Spinner className=\"mr-1.5 h-3.5 w-3.5\" />\n Saving\n </>\n ) : (\n <>\n Save\n <Kbd className=\"ml-1.5\">\n {isMac ? \"⌘S\" : \"Ctrl+S\"}\n </Kbd>\n </>\n )}\n </Button>\n </div>\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n </ActionBarContext.Provider>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Hook — register form state\n// ---------------------------------------------------------------------------\n\n/**\n * Register a form's unsaved state with the global ActionBar.\n *\n * Unregisters automatically on unmount.\n *\n * @param id — Unique key (e.g., 'profile', 'notifications')\n * @param entry — Current form state\n */\nfunction useActionBar(id: string, entry: ActionBarEntry) {\n const ctx = React.useContext(ActionBarContext)\n if (!ctx)\n throw new Error(\"useActionBar must be used within <ActionBarProvider>\")\n\n const { register, unregister } = ctx\n\n // Intentionally no dependency array: `entry` (and its `onSave`/\n // `onReset` closures) is rebuilt by the caller every render, so we\n // must re-register each render to keep the latest closures. The\n // `register` impl diffs the entry and only forces an update when\n // `hasChanges`/`saving` actually changed, so this can't loop.\n React.useEffect(() => {\n register(id, entry)\n })\n\n React.useEffect(() => {\n return () => unregister(id)\n }, [id, unregister])\n}\n\n// ---------------------------------------------------------------------------\n// Navigation guard hook\n// ---------------------------------------------------------------------------\n\n/**\n * Returns a function that guards navigation.\n * If unsaved changes exist, triggers the jiggle and blocks navigation.\n * Otherwise navigates normally via the provided callback.\n *\n * @param navigate — Your navigation function (e.g., `router.push`)\n *\n * @example\n * ```tsx\n * const router = useRouter();\n * const guardedPush = useActionBarGuard(router.push);\n * <button onClick={() => guardedPush('/settings')}>Settings</button>\n * ```\n */\nfunction useActionBarGuard(navigate: (href: string) => void) {\n const ctx = React.useContext(ActionBarContext)\n\n return React.useCallback(\n (href: string) => {\n if (ctx?.hasDirty()) {\n ctx.jiggle()\n return false\n }\n navigate(href)\n return true\n },\n [ctx, navigate]\n )\n}\n\n// ---------------------------------------------------------------------------\n// Exports\n// ---------------------------------------------------------------------------\n\nexport {\n ActionBarProvider,\n useActionBar,\n useActionBarGuard,\n}\n"],"mappings":"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"}
|
package/dist/alert-dialog.js
CHANGED
|
@@ -34,7 +34,7 @@ function AlertDialogContent({ className, size = "default", ...props }) {
|
|
|
34
34
|
return /* @__PURE__ */ jsxs(AlertDialogPortal, { children: [/* @__PURE__ */ jsx(AlertDialogOverlay, {}), /* @__PURE__ */ jsx(AlertDialog$1.Popup, {
|
|
35
35
|
"data-slot": "alert-dialog-content",
|
|
36
36
|
"data-size": size,
|
|
37
|
-
className: cn("motion-scale bg-
|
|
37
|
+
className: cn("motion-scale-lg bg-foundation ring-contrast/10 group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-lg p-4 ring-1 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=lg]:max-w-lg data-[size=xl]:max-w-xl data-[size=default]:sm:max-w-sm data-[size=lg]:sm:max-w-lg data-[size=xl]:sm:max-w-xl", className),
|
|
38
38
|
...props
|
|
39
39
|
})] });
|
|
40
40
|
}
|
|
@@ -48,14 +48,14 @@ function AlertDialogHeader({ className, ...props }) {
|
|
|
48
48
|
function AlertDialogFooter({ className, ...props }) {
|
|
49
49
|
return /* @__PURE__ */ jsx("div", {
|
|
50
50
|
"data-slot": "alert-dialog-footer",
|
|
51
|
-
className: cn("bg-
|
|
51
|
+
className: cn("bg-secondary/50 -mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-lg border-t border-line p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end", className),
|
|
52
52
|
...props
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
function AlertDialogMedia({ className, ...props }) {
|
|
56
56
|
return /* @__PURE__ */ jsx("div", {
|
|
57
57
|
"data-slot": "alert-dialog-media",
|
|
58
|
-
className: cn("bg-
|
|
58
|
+
className: cn("bg-secondary mb-2 inline-flex size-10 items-center justify-center rounded-md sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6", className),
|
|
59
59
|
...props
|
|
60
60
|
});
|
|
61
61
|
}
|
|
@@ -69,7 +69,7 @@ function AlertDialogTitle({ className, ...props }) {
|
|
|
69
69
|
function AlertDialogDescription({ className, ...props }) {
|
|
70
70
|
return /* @__PURE__ */ jsx(AlertDialog$1.Description, {
|
|
71
71
|
"data-slot": "alert-dialog-description",
|
|
72
|
-
className: cn("text-muted
|
|
72
|
+
className: cn("text-muted *:[a]:hover:text-contrast text-sm text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3", className),
|
|
73
73
|
...props
|
|
74
74
|
});
|
|
75
75
|
}
|
package/dist/alert-dialog.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert-dialog.js","names":["AlertDialogPrimitive"],"sources":["../src/alert-dialog.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { AlertDialog as AlertDialogPrimitive } from \"@base-ui/react/alert-dialog\"\n\nimport { Button } from \"./button\"\nimport { cn } from \"./lib/utils\"\n\ntype AlertDialogProps = React.ComponentProps<typeof AlertDialogPrimitive.Root>\n\ntype AlertDialogTriggerProps = React.ComponentProps<typeof AlertDialogPrimitive.Trigger>\n\ntype AlertDialogPortalProps = React.ComponentProps<typeof AlertDialogPrimitive.Portal>\n\ntype AlertDialogOverlayProps = React.ComponentProps<typeof AlertDialogPrimitive.Backdrop>\n\ntype AlertDialogContentProps = React.ComponentProps<typeof AlertDialogPrimitive.Popup> & {\n size?: \"default\" | \"sm\" | \"lg\" | \"xl\"\n}\n\ntype AlertDialogHeaderProps = React.ComponentProps<\"div\">\n\ntype AlertDialogFooterProps = React.ComponentProps<\"div\">\n\ntype AlertDialogMediaProps = React.ComponentProps<\"div\">\n\ntype AlertDialogTitleProps = React.ComponentProps<typeof AlertDialogPrimitive.Title>\n\ntype AlertDialogDescriptionProps = React.ComponentProps<typeof AlertDialogPrimitive.Description>\n\ntype AlertDialogActionProps = React.ComponentProps<typeof Button>\n\ntype AlertDialogCancelProps = React.ComponentProps<typeof AlertDialogPrimitive.Close> &\n Pick<React.ComponentProps<typeof Button>, \"variant\" | \"size\">\n\nfunction AlertDialog({ ...props }: AlertDialogProps) {\n return <AlertDialogPrimitive.Root data-slot=\"alert-dialog\" {...props} />\n}\n\nfunction AlertDialogTrigger({ ...props }: AlertDialogTriggerProps) {\n return <AlertDialogPrimitive.Trigger data-slot=\"alert-dialog-trigger\" {...props} />\n}\n\nfunction AlertDialogPortal({ ...props }: AlertDialogPortalProps) {\n return <AlertDialogPrimitive.Portal data-slot=\"alert-dialog-portal\" {...props} />\n}\n\nfunction AlertDialogOverlay({ className, ...props }: AlertDialogOverlayProps) {\n return (\n <AlertDialogPrimitive.Backdrop\n data-slot=\"alert-dialog-overlay\"\n className={cn(\n \"motion-scrim fixed inset-0 isolate z-50 bg-black/10 supports-backdrop-filter:backdrop-blur-xs\",\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction AlertDialogContent({\n className,\n size = \"default\",\n ...props\n}: AlertDialogContentProps) {\n return (\n <AlertDialogPortal>\n <AlertDialogOverlay />\n <AlertDialogPrimitive.Popup\n data-slot=\"alert-dialog-content\"\n data-size={size}\n className={cn(\n \"motion-scale bg-
|
|
1
|
+
{"version":3,"file":"alert-dialog.js","names":["AlertDialogPrimitive"],"sources":["../src/alert-dialog.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { AlertDialog as AlertDialogPrimitive } from \"@base-ui/react/alert-dialog\"\n\nimport { Button } from \"./button\"\nimport { cn } from \"./lib/utils\"\n\ntype AlertDialogProps = React.ComponentProps<typeof AlertDialogPrimitive.Root>\n\ntype AlertDialogTriggerProps = React.ComponentProps<typeof AlertDialogPrimitive.Trigger>\n\ntype AlertDialogPortalProps = React.ComponentProps<typeof AlertDialogPrimitive.Portal>\n\ntype AlertDialogOverlayProps = React.ComponentProps<typeof AlertDialogPrimitive.Backdrop>\n\ntype AlertDialogContentProps = React.ComponentProps<typeof AlertDialogPrimitive.Popup> & {\n size?: \"default\" | \"sm\" | \"lg\" | \"xl\"\n}\n\ntype AlertDialogHeaderProps = React.ComponentProps<\"div\">\n\ntype AlertDialogFooterProps = React.ComponentProps<\"div\">\n\ntype AlertDialogMediaProps = React.ComponentProps<\"div\">\n\ntype AlertDialogTitleProps = React.ComponentProps<typeof AlertDialogPrimitive.Title>\n\ntype AlertDialogDescriptionProps = React.ComponentProps<typeof AlertDialogPrimitive.Description>\n\ntype AlertDialogActionProps = React.ComponentProps<typeof Button>\n\ntype AlertDialogCancelProps = React.ComponentProps<typeof AlertDialogPrimitive.Close> &\n Pick<React.ComponentProps<typeof Button>, \"variant\" | \"size\">\n\nfunction AlertDialog({ ...props }: AlertDialogProps) {\n return <AlertDialogPrimitive.Root data-slot=\"alert-dialog\" {...props} />\n}\n\nfunction AlertDialogTrigger({ ...props }: AlertDialogTriggerProps) {\n return <AlertDialogPrimitive.Trigger data-slot=\"alert-dialog-trigger\" {...props} />\n}\n\nfunction AlertDialogPortal({ ...props }: AlertDialogPortalProps) {\n return <AlertDialogPrimitive.Portal data-slot=\"alert-dialog-portal\" {...props} />\n}\n\nfunction AlertDialogOverlay({ className, ...props }: AlertDialogOverlayProps) {\n return (\n <AlertDialogPrimitive.Backdrop\n data-slot=\"alert-dialog-overlay\"\n className={cn(\n \"motion-scrim fixed inset-0 isolate z-50 bg-black/10 supports-backdrop-filter:backdrop-blur-xs\",\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction AlertDialogContent({\n className,\n size = \"default\",\n ...props\n}: AlertDialogContentProps) {\n return (\n <AlertDialogPortal>\n <AlertDialogOverlay />\n <AlertDialogPrimitive.Popup\n data-slot=\"alert-dialog-content\"\n data-size={size}\n className={cn(\n \"motion-scale-lg bg-foundation ring-contrast/10 group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-lg p-4 ring-1 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=lg]:max-w-lg data-[size=xl]:max-w-xl data-[size=default]:sm:max-w-sm data-[size=lg]:sm:max-w-lg data-[size=xl]:sm:max-w-xl\",\n className,\n )}\n {...props}\n />\n </AlertDialogPortal>\n )\n}\n\nfunction AlertDialogHeader({ className, ...props }: AlertDialogHeaderProps) {\n return (\n <div\n data-slot=\"alert-dialog-header\"\n className={cn(\n \"grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr] group-data-[size=lg]/alert-dialog-content:place-items-start group-data-[size=lg]/alert-dialog-content:text-left group-data-[size=xl]/alert-dialog-content:place-items-start group-data-[size=xl]/alert-dialog-content:text-left\",\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction AlertDialogFooter({ className, ...props }: AlertDialogFooterProps) {\n return (\n <div\n data-slot=\"alert-dialog-footer\"\n className={cn(\n \"bg-secondary/50 -mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-lg border-t border-line p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end\",\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction AlertDialogMedia({ className, ...props }: AlertDialogMediaProps) {\n return (\n <div\n data-slot=\"alert-dialog-media\"\n className={cn(\n \"bg-secondary mb-2 inline-flex size-10 items-center justify-center rounded-md sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6\",\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction AlertDialogTitle({ className, ...props }: AlertDialogTitleProps) {\n return (\n <AlertDialogPrimitive.Title\n data-slot=\"alert-dialog-title\"\n className={cn(\n \"text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2\",\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction AlertDialogDescription({\n className,\n ...props\n}: AlertDialogDescriptionProps) {\n return (\n <AlertDialogPrimitive.Description\n data-slot=\"alert-dialog-description\"\n className={cn(\n \"text-muted *:[a]:hover:text-contrast text-sm text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3\",\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction AlertDialogAction({\n className,\n ...props\n}: AlertDialogActionProps) {\n return (\n <Button\n data-slot=\"alert-dialog-action\"\n className={cn(className)}\n {...props}\n />\n )\n}\n\nfunction AlertDialogCancel({\n className,\n variant = \"outline\",\n size = \"default\",\n ...props\n}: AlertDialogCancelProps) {\n return (\n <AlertDialogPrimitive.Close\n data-slot=\"alert-dialog-cancel\"\n className={className}\n render={<Button variant={variant} size={size} />}\n {...props}\n />\n )\n}\n\nexport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogMedia,\n AlertDialogOverlay,\n AlertDialogPortal,\n AlertDialogTitle,\n AlertDialogTrigger,\n}\n"],"mappings":";;;;;;;AAmCA,SAAS,YAAY,EAAE,GAAG,SAA2B;AACnD,QAAO,oBAACA,cAAqB,MAAtB;EAA2B,aAAU;EAAe,GAAI;EAAS,CAAA;;AAG1E,SAAS,mBAAmB,EAAE,GAAG,SAAkC;AACjE,QAAO,oBAACA,cAAqB,SAAtB;EAA8B,aAAU;EAAuB,GAAI;EAAS,CAAA;;AAGrF,SAAS,kBAAkB,EAAE,GAAG,SAAiC;AAC/D,QAAO,oBAACA,cAAqB,QAAtB;EAA6B,aAAU;EAAsB,GAAI;EAAS,CAAA;;AAGnF,SAAS,mBAAmB,EAAE,WAAW,GAAG,SAAkC;AAC5E,QACE,oBAACA,cAAqB,UAAtB;EACE,aAAU;EACV,WAAW,GACT,iGACA,UACD;EACD,GAAI;EACJ,CAAA;;AAIN,SAAS,mBAAmB,EAC1B,WACA,OAAO,WACP,GAAG,SACuB;AAC1B,QACE,qBAAC,mBAAD,EAAA,UAAA,CACE,oBAAC,oBAAD,EAAsB,CAAA,EACtB,oBAACA,cAAqB,OAAtB;EACE,aAAU;EACV,aAAW;EACX,WAAW,GACT,2XACA,UACD;EACD,GAAI;EACJ,CAAA,CACgB,EAAA,CAAA;;AAIxB,SAAS,kBAAkB,EAAE,WAAW,GAAG,SAAiC;AAC1E,QACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,snBACA,UACD;EACD,GAAI;EACJ,CAAA;;AAIN,SAAS,kBAAkB,EAAE,WAAW,GAAG,SAAiC;AAC1E,QACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,iOACA,UACD;EACD,GAAI;EACJ,CAAA;;AAIN,SAAS,iBAAiB,EAAE,WAAW,GAAG,SAAgC;AACxE,QACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,kLACA,UACD;EACD,GAAI;EACJ,CAAA;;AAIN,SAAS,iBAAiB,EAAE,WAAW,GAAG,SAAgC;AACxE,QACE,oBAACA,cAAqB,OAAtB;EACE,aAAU;EACV,WAAW,GACT,qJACA,UACD;EACD,GAAI;EACJ,CAAA;;AAIN,SAAS,uBAAuB,EAC9B,WACA,GAAG,SAC2B;AAC9B,QACE,oBAACA,cAAqB,aAAtB;EACE,aAAU;EACV,WAAW,GACT,qHACA,UACD;EACD,GAAI;EACJ,CAAA;;AAIN,SAAS,kBAAkB,EACzB,WACA,GAAG,SACsB;AACzB,QACE,oBAAC,QAAD;EACE,aAAU;EACV,WAAW,GAAG,UAAU;EACxB,GAAI;EACJ,CAAA;;AAIN,SAAS,kBAAkB,EACzB,WACA,UAAU,WACV,OAAO,WACP,GAAG,SACsB;AACzB,QACE,oBAACA,cAAqB,OAAtB;EACE,aAAU;EACC;EACX,QAAQ,oBAAC,QAAD;GAAiB;GAAe;GAAQ,CAAA;EAChD,GAAI;EACJ,CAAA"}
|
package/dist/alert.js
CHANGED
|
@@ -3,10 +3,10 @@ import "react";
|
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
import { cva } from "class-variance-authority";
|
|
5
5
|
//#region src/alert.tsx
|
|
6
|
-
const alertVariants = cva("grid gap-0.5 rounded-
|
|
6
|
+
const alertVariants = cva("grid gap-0.5 rounded-md border border-edge px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-data-[icon]:grid-cols-[auto_1fr] has-data-[icon]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 w-full relative group/alert", {
|
|
7
7
|
variants: { variant: {
|
|
8
|
-
default: "bg-
|
|
9
|
-
destructive: "text-destructive bg-
|
|
8
|
+
default: "bg-surface text-contrast",
|
|
9
|
+
destructive: "text-destructive bg-surface *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current"
|
|
10
10
|
} },
|
|
11
11
|
defaultVariants: { variant: "default" }
|
|
12
12
|
});
|
|
@@ -21,14 +21,14 @@ function Alert({ className, variant, ...props }) {
|
|
|
21
21
|
function AlertTitle({ className, ...props }) {
|
|
22
22
|
return /* @__PURE__ */ jsx("div", {
|
|
23
23
|
"data-slot": "alert-title",
|
|
24
|
-
className: cn("[&_a]:hover:text-
|
|
24
|
+
className: cn("[&_a]:hover:text-contrast font-medium group-has-data-[icon]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3", className),
|
|
25
25
|
...props
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
function AlertDescription({ className, ...props }) {
|
|
29
29
|
return /* @__PURE__ */ jsx("div", {
|
|
30
30
|
"data-slot": "alert-description",
|
|
31
|
-
className: cn("text-muted
|
|
31
|
+
className: cn("text-muted [&_a]:hover:text-contrast text-sm text-balance md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4", className),
|
|
32
32
|
...props
|
|
33
33
|
});
|
|
34
34
|
}
|
package/dist/alert.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert.js","names":[],"sources":["../src/alert.tsx"],"sourcesContent":["import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"./lib/utils\"\n\nconst alertVariants = cva(\n \"grid gap-0.5 rounded-
|
|
1
|
+
{"version":3,"file":"alert.js","names":[],"sources":["../src/alert.tsx"],"sourcesContent":["import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"./lib/utils\"\n\nconst alertVariants = cva(\n \"grid gap-0.5 rounded-md border border-edge px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-data-[icon]:grid-cols-[auto_1fr] has-data-[icon]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 w-full relative group/alert\",\n {\n variants: {\n variant: {\n default: \"bg-surface text-contrast\",\n destructive:\n \"text-destructive bg-surface *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n)\n\ntype AlertProps = React.ComponentProps<\"div\"> & VariantProps<typeof alertVariants>\ntype AlertTitleProps = React.ComponentProps<\"div\">\ntype AlertDescriptionProps = React.ComponentProps<\"div\">\ntype AlertActionProps = React.ComponentProps<\"div\">\n\nfunction Alert({\n className,\n variant,\n ...props\n}: AlertProps) {\n return (\n <div\n data-slot=\"alert\"\n role=\"alert\"\n className={cn(alertVariants({ variant }), className)}\n {...props}\n />\n )\n}\n\nfunction AlertTitle({ className, ...props }: AlertTitleProps) {\n return (\n <div\n data-slot=\"alert-title\"\n className={cn(\n \"[&_a]:hover:text-contrast font-medium group-has-data-[icon]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction AlertDescription({\n className,\n ...props\n}: AlertDescriptionProps) {\n return (\n <div\n data-slot=\"alert-description\"\n className={cn(\n \"text-muted [&_a]:hover:text-contrast text-sm text-balance md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction AlertAction({ className, ...props }: AlertActionProps) {\n return (\n <div\n data-slot=\"alert-action\"\n className={cn(\"absolute top-2 right-2\", className)}\n {...props}\n />\n )\n}\n\nexport {\n Alert,\n AlertTitle,\n AlertDescription,\n AlertAction\n}\n"],"mappings":";;;;;AAKA,MAAM,gBAAgB,IACpB,mVACA;CACE,UAAU,EACR,SAAS;EACP,SAAS;EACT,aACE;EACH,EACF;CACD,iBAAiB,EACf,SAAS,WACV;CACF,CACF;AAOD,SAAS,MAAM,EACb,WACA,SACA,GAAG,SACU;AACb,QACE,oBAAC,OAAD;EACE,aAAU;EACV,MAAK;EACL,WAAW,GAAG,cAAc,EAAE,SAAS,CAAC,EAAE,UAAU;EACpD,GAAI;EACJ,CAAA;;AAIN,SAAS,WAAW,EAAE,WAAW,GAAG,SAA0B;AAC5D,QACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,0HACA,UACD;EACD,GAAI;EACJ,CAAA;;AAIN,SAAS,iBAAiB,EACxB,WACA,GAAG,SACqB;AACxB,QACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,iJACA,UACD;EACD,GAAI;EACJ,CAAA;;AAIN,SAAS,YAAY,EAAE,WAAW,GAAG,SAA2B;AAC9D,QACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,0BAA0B,UAAU;EAClD,GAAI;EACJ,CAAA"}
|
package/dist/animate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"animate.d.ts","names":[],"sources":["../src/animate.tsx"],"mappings":";;;;;;KAkBK,SAAA;AAAA,UAEK,kBAAA;EACR,QAAA,EAAU,YAAA;;EAEV,KAAA;EALY;EAOZ,IAAA,GAAO,SAAA;EAPK;EASZ,QAAA;EAPQ;EASR,KAAA;;EAEA,IAAA;EANO;EAQP,MAAA;EAQuB;EANvB,IAAA;EAdA;EAgBA,MAAA;EAdA;EAgBA,IAAA;EAdO;EAgBP,UAAA,GAAa,UAAA;AAAA;AAAA,UAGL,cAAA;EACR,QAAA,EAAU,YAAA;EAVV;EAYA,KAAA;EARA;EAUA,IAAA,GAAO,SAAA;EARM;EAUb,QAAA;EAVuB;EAYvB,KAAA;EATsB;EAWtB,IAAA;EAVU;EAYV,MAAA;EAMa;EAJb,IAAA;EAIuB;EAFvB,MAAA;EAhBU;EAkBV,UAAA,GAAa,UAAA;AAAA;AAAA,UAGL,YAAA;EACR,QAAA,EAAU,YAAA;EAdV;EAgBA,QAAA;EAZA;EAcA,SAAA;AAAA;AAAA,UAGQ,UAAA;EACR,QAAA,EAAU,YAAA;EAZa;EAcvB,GAAA;EAXQ;EAaR,GAAA;;EAEA,QAAA;EAdA;EAgBA,OAAA;EAdA;EAgBA,MAAA;AAAA;AAAA,UAGQ,UAAA;EACR,QAAA,EAAU,YAAA;EAfQ;EAiBlB,QAAA;EAhBsB;EAkBtB,QAAA;EAlBU;EAoBV,MAAA;EAhBA;EAkBA,MAAA;AAAA;;;;AAZM;;;;;;;;;;;;AAYA;
|
|
1
|
+
{"version":3,"file":"animate.d.ts","names":[],"sources":["../src/animate.tsx"],"mappings":";;;;;;KAkBK,SAAA;AAAA,UAEK,kBAAA;EACR,QAAA,EAAU,YAAA;;EAEV,KAAA;EALY;EAOZ,IAAA,GAAO,SAAA;EAPK;EASZ,QAAA;EAPQ;EASR,KAAA;;EAEA,IAAA;EANO;EAQP,MAAA;EAQuB;EANvB,IAAA;EAdA;EAgBA,MAAA;EAdA;EAgBA,IAAA;EAdO;EAgBP,UAAA,GAAa,UAAA;AAAA;AAAA,UAGL,cAAA;EACR,QAAA,EAAU,YAAA;EAVV;EAYA,KAAA;EARA;EAUA,IAAA,GAAO,SAAA;EARM;EAUb,QAAA;EAVuB;EAYvB,KAAA;EATsB;EAWtB,IAAA;EAVU;EAYV,MAAA;EAMa;EAJb,IAAA;EAIuB;EAFvB,MAAA;EAhBU;EAkBV,UAAA,GAAa,UAAA;AAAA;AAAA,UAGL,YAAA;EACR,QAAA,EAAU,YAAA;EAdV;EAgBA,QAAA;EAZA;EAcA,SAAA;AAAA;AAAA,UAGQ,UAAA;EACR,QAAA,EAAU,YAAA;EAZa;EAcvB,GAAA;EAXQ;EAaR,GAAA;;EAEA,QAAA;EAdA;EAgBA,OAAA;EAdA;EAgBA,MAAA;AAAA;AAAA,UAGQ,UAAA;EACR,QAAA,EAAU,YAAA;EAfQ;EAiBlB,QAAA;EAhBsB;EAkBtB,QAAA;EAlBU;EAoBV,MAAA;EAhBA;EAkBA,MAAA;AAAA;;;;AAZM;;;;;;;;;;;;AAYA;iBA+HC,aAAA,CAAA;EACP,QAAA;EACA,KAAA;EACA,IAAA;EACA,QAAA;EACA,KAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA;EACA;AAAA,GACC,kBAAA,GAAkB,YAAA,mBAAA,OAAA,CAAA,qBAAA;;;;;;;;;;;;;;iBAoDZ,SAAA,CAAA;EACP,QAAA;EACA,KAAA;EACA,IAAA;EACA,QAAA;EACA,KAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA;EACA,MAAA;EACA;AAAA,GACC,cAAA,GAAc,YAAA,mBAAA,OAAA,CAAA,qBAAA;;;;;;;;;;;;;iBA8CR,OAAA,CAAA;EACP,QAAA;EACA,QAAA;EACA;AAAA,GACC,YAAA,GAAY,oBAAA,CAAA,GAAA,CAAA,OAAA;;;;;;;;;;;;iBA0BN,KAAA,CAAA;EACP,QAAA;EACA,GAAA;EACA,GAAA;EACA,QAAA;EACA,OAAA;EACA;AAAA,GACC,UAAA,GAAU,oBAAA,CAAA,GAAA,CAAA,OAAA;;;;;;AAlJQ;;;;;;iBAsMZ,KAAA,CAAA;EACP,QAAA;EACA,QAAA;EACA,QAAA;EACA,MAAA;EACA;AAAA,GACC,UAAA,GAAU,oBAAA,CAAA,GAAA,CAAA,OAAA"}
|
package/dist/animate.js
CHANGED
|
@@ -34,8 +34,7 @@ const FLIP_MAP = {
|
|
|
34
34
|
};
|
|
35
35
|
/** Overshoot ("spring") easing for the opt-in `spring` prop on
|
|
36
36
|
* `AnimateIn` / `AnimateOnView`. This is `animate.tsx`'s own page-entrance
|
|
37
|
-
* bounce — the CSS recipe system intentionally has no spring
|
|
38
|
-
* curve), so this is a standalone constant, not a shared token. */
|
|
37
|
+
* bounce — the CSS recipe system intentionally has no spring, so this is a standalone constant, not a shared token. */
|
|
39
38
|
const SPRING_EASE = "cubic-bezier(0.34, 1.45, 0.64, 1)";
|
|
40
39
|
function buildStyles(opts) {
|
|
41
40
|
const { from, distance, doScale, blur, rotate, flip } = opts;
|