@sableui/react 0.13.2 → 0.14.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/AGENTS.md +2 -2
- package/CHANGELOG.md +114 -0
- package/CONTRIBUTING.md +6 -4
- package/DESIGN.md +8 -3
- package/dist/blocks/approval-block.js +93 -0
- package/dist/blocks/approval-block.meta.js +23 -0
- package/dist/blocks/index.d.ts +16 -1
- package/dist/blocks/index.js +4 -0
- package/dist/components/accordion/accordion.meta.js +8 -2
- package/dist/components/area-chart/area-chart.meta.js +10 -3
- package/dist/components/attachment/attachment.meta.js +26 -0
- package/dist/components/bar-chart/bar-chart.meta.js +11 -4
- package/dist/components/bottom-nav/bottom-nav.js +3 -1
- package/dist/components/bottom-nav/bottom-nav.meta.js +32 -0
- package/dist/components/breadcrumbs/breadcrumbs.meta.js +26 -0
- package/dist/components/button/button.js +21 -4
- package/dist/components/button/button.meta.js +7 -7
- package/dist/components/calendar/calendar.js +18 -1
- package/dist/components/calendar/calendar.meta.js +38 -0
- package/dist/components/carousel/carousel.js +6 -4
- package/dist/components/carousel/carousel.meta.js +42 -0
- package/dist/components/chat-composer/chat-composer.js +21 -21
- package/dist/components/chat-composer/chat-composer.meta.js +38 -0
- package/dist/components/chat-composer/use-eased-growth.js +70 -0
- package/dist/components/checkbox/checkbox-mark.js +8 -1
- package/dist/components/checkbox/checkbox.js +3 -1
- package/dist/components/checkbox/checkbox.meta.js +32 -0
- package/dist/components/checkbox-card/checkbox-card.js +3 -0
- package/dist/components/checkbox-card/checkbox-card.meta.js +32 -0
- package/dist/components/code-block/code-block.js +14 -3
- package/dist/components/code-block/code-block.meta.js +28 -1
- package/dist/components/collapsible/collapsible.meta.js +32 -0
- package/dist/components/color-field/color-field.meta.js +26 -0
- package/dist/components/combobox/combobox.meta.js +44 -0
- package/dist/components/command-menu/command-menu.js +8 -3
- package/dist/components/command-menu/command-menu.meta.js +38 -0
- package/dist/components/confirm-button/confirm-button.js +179 -0
- package/dist/components/confirm-button/confirm-button.meta.js +143 -0
- package/dist/components/confirm-button/index.js +7 -0
- package/dist/components/data-table/data-table.js +6 -2
- package/dist/components/data-table/data-table.meta.js +32 -0
- package/dist/components/entity/entity.meta.js +26 -0
- package/dist/components/feedback/feedback.js +113 -47
- package/dist/components/feedback/feedback.meta.js +49 -4
- package/dist/components/input-group/input-group.meta.js +27 -1
- package/dist/components/line-chart/line-chart.meta.js +10 -3
- package/dist/components/middle-truncate/middle-truncate.meta.js +1 -1
- package/dist/components/note/note.meta.js +32 -0
- package/dist/components/notification-badge/notification-badge.js +17 -5
- package/dist/components/notification-badge/notification-badge.meta.js +34 -2
- package/dist/components/number-ticker/index.js +7 -0
- package/dist/components/number-ticker/number-ticker.js +246 -0
- package/dist/components/number-ticker/number-ticker.meta.js +159 -0
- package/dist/components/otp-field/otp-field.js +23 -3
- package/dist/components/otp-field/otp-field.meta.js +23 -2
- package/dist/components/panel-group/panel-group.meta.js +26 -0
- package/dist/components/panel-section/panel-section.js +37 -10
- package/dist/components/panel-section/panel-section.meta.js +32 -0
- package/dist/components/pie-chart/pie-chart.js +20 -11
- package/dist/components/pie-chart/pie-chart.meta.js +17 -4
- package/dist/components/pill/pill.meta.js +32 -0
- package/dist/components/radar-chart/radar-chart.js +4 -3
- package/dist/components/radar-chart/radar-chart.meta.js +10 -3
- package/dist/components/radial-chart/radial-chart.js +22 -22
- package/dist/components/radial-chart/radial-chart.meta.js +17 -4
- package/dist/components/radio/radio.meta.js +26 -0
- package/dist/components/radio-card/radio-card.meta.js +38 -0
- package/dist/components/relative-time/relative-time.meta.js +32 -0
- package/dist/components/scroller/scroller.meta.js +32 -0
- package/dist/components/scrub-field/scrub-field.meta.js +32 -0
- package/dist/components/search-input/search-input.js +9 -7
- package/dist/components/search-input/search-input.meta.js +26 -0
- package/dist/components/show-more/show-more.js +1 -4
- package/dist/components/show-more/show-more.meta.js +38 -0
- package/dist/components/side-nav/side-nav.js +21 -1
- package/dist/components/side-nav/side-nav.meta.js +32 -0
- package/dist/components/side-nav/side-nav.variants.js +3 -1
- package/dist/components/sidebar/sidebar.js +31 -8
- package/dist/components/sidebar/sidebar.meta.js +38 -0
- package/dist/components/slider/slider.meta.js +26 -0
- package/dist/components/sparkline/sparkline.js +7 -4
- package/dist/components/sparkline/sparkline.meta.js +9 -2
- package/dist/components/table/table.meta.js +26 -0
- package/dist/components/tabs/tabs.js +27 -8
- package/dist/components/tabs/tabs.meta.js +46 -3
- package/dist/components/theme-selector/theme-selector.meta.js +32 -0
- package/dist/components/theme-switcher/theme-switcher.js +4 -1
- package/dist/components/theme-switcher/theme-switcher.meta.js +42 -0
- package/dist/components/toggle/toggle.js +28 -5
- package/dist/components/toggle/toggle.meta.js +44 -2
- package/dist/components/tool-call/index.js +8 -0
- package/dist/components/tool-call/tool-call.js +117 -0
- package/dist/components/tool-call/tool-call.meta.js +147 -0
- package/dist/components/tool-call/tool-call.variants.js +33 -0
- package/dist/components/tooltip/index.js +3 -1
- package/dist/components/tooltip/tooltip.js +84 -0
- package/dist/components/tooltip/tooltip.meta.js +16 -1
- package/dist/components/tree/tree.js +71 -6
- package/dist/components/tree/tree.meta.js +32 -0
- package/dist/components/video/video.meta.js +32 -0
- package/dist/file-hashes.json +114 -97
- package/dist/foundations/motion.js +49 -3
- package/dist/index.d.ts +614 -44
- package/dist/index.js +4 -1
- package/dist/lib/chart/cartesian.js +9 -10
- package/dist/lib/chart/chart-center.js +30 -0
- package/dist/lib/chart/chart-frame.js +15 -8
- package/dist/lib/chart/dim.js +13 -0
- package/dist/lib/chart/format.js +7 -1
- package/dist/lib/chart/motion.js +6 -4
- package/dist/lib/highlight-glide.js +68 -10
- package/dist/lib/taxonomy.js +3 -0
- package/dist/motion/MotionProvider.js +45 -3
- package/dist/motion/Reveal.js +1 -1
- package/dist/motion/TextReveal.js +1 -1
- package/dist/motion/index.js +15 -2
- package/dist/motion/useAutoSize.js +42 -0
- package/dist/motion/useShake.js +25 -0
- package/dist/styles.css +298 -5
- package/llms/blocks/approval-block.md +128 -0
- package/llms/components/accordion.md +3 -2
- package/llms/components/area-chart.md +4 -3
- package/llms/components/attachment.md +7 -0
- package/llms/components/bar-chart.md +5 -4
- package/llms/components/bottom-nav.md +8 -0
- package/llms/components/breadcrumbs.md +7 -0
- package/llms/components/button.md +6 -6
- package/llms/components/calendar.md +9 -0
- package/llms/components/carousel.md +17 -0
- package/llms/components/chat-composer.md +9 -0
- package/llms/components/checkbox-card.md +8 -0
- package/llms/components/checkbox.md +8 -0
- package/llms/components/code-block.md +8 -0
- package/llms/components/collapsible.md +8 -0
- package/llms/components/color-field.md +7 -0
- package/llms/components/combobox.md +10 -0
- package/llms/components/command-menu.md +9 -0
- package/llms/components/confirm-button.md +92 -0
- package/llms/components/data-table.md +8 -0
- package/llms/components/entity.md +7 -0
- package/llms/components/feedback.md +22 -3
- package/llms/components/input-group.md +8 -1
- package/llms/components/line-chart.md +4 -3
- package/llms/components/middle-truncate.md +1 -1
- package/llms/components/note.md +8 -0
- package/llms/components/notification-badge.md +10 -2
- package/llms/components/number-ticker.md +86 -0
- package/llms/components/otp-field.md +15 -1
- package/llms/components/panel-group.md +7 -0
- package/llms/components/panel-section.md +8 -0
- package/llms/components/pie-chart.md +6 -4
- package/llms/components/pill.md +8 -0
- package/llms/components/radar-chart.md +4 -3
- package/llms/components/radial-chart.md +6 -4
- package/llms/components/radio-card.md +9 -0
- package/llms/components/radio.md +7 -0
- package/llms/components/relative-time.md +8 -0
- package/llms/components/scroller.md +8 -0
- package/llms/components/scrub-field.md +8 -0
- package/llms/components/search-input.md +7 -0
- package/llms/components/show-more.md +9 -0
- package/llms/components/side-nav.md +8 -0
- package/llms/components/sidebar.md +9 -0
- package/llms/components/slider.md +7 -0
- package/llms/components/sparkline.md +3 -2
- package/llms/components/table.md +7 -0
- package/llms/components/tabs.md +20 -2
- package/llms/components/theme-selector.md +8 -0
- package/llms/components/theme-switcher.md +17 -0
- package/llms/components/toggle.md +19 -2
- package/llms/components/tool-call.md +115 -0
- package/llms/components/tooltip.md +11 -1
- package/llms/components/tree.md +8 -0
- package/llms/components/video.md +8 -0
- package/llms/foundations/motion.md +48 -3
- package/llms/reference/utilities.md +23 -1
- package/llms/tasks/reviewing-motion.md +14 -0
- package/llms-full.txt +955 -49
- package/llms.txt +15 -10
- package/package.json +1 -1
- package/registry.json +2565 -210
- package/targets/ios.json +27 -0
package/AGENTS.md
CHANGED
|
@@ -42,7 +42,7 @@ These are short on purpose. Each links to a foundations doc with the full vocabu
|
|
|
42
42
|
3. **Space on the 4px scale, by role.** Inset, stack, inline; related things closer than unrelated things. No off-scale `p-[15px]`. Padding, gaps and insets stay on the scale (a step, or `--spacing(n)` inside an arbitrary value) so the density control moves them; fixed `rem`/`px` only for what must not scale, like a hairline. Single-column content in a card is a flex column, not a grid. → `sable docs spacing`
|
|
43
43
|
4. **Controls read the ladder.** Heights come from `--control-*`, so `<Density>` can retune a region at once. Never `h-[40px]`. → `sable docs spacing`
|
|
44
44
|
5. **Shape and depth are roles.** Radii from the `rounded-*` ladder, concentric when nested (inner = outer − inset; `rounded-concentric-*`, `sable-frame-inner`); elevation by transience (resting → raised → overlay → modal → toast), one shadow per surface. → `sable docs shape` · `sable docs surfaces`
|
|
45
|
-
6. **Motion has a reason and a recipe.** Apply a named `motion-*` recipe; drop to `duration-(--motion-duration-*)` + `ease-*` tokens only when none fits. Never Tailwind's `animate-*`, never a raw `duration-150`. Exits are faster than entrances and never overshoot. → `sable docs motion`
|
|
45
|
+
6. **Motion has a reason and a recipe.** Apply a named `motion-*` recipe; drop to `duration-(--motion-duration-*)` + `ease-*` tokens only when none fits. Never Tailwind's `animate-*`, never a raw `duration-150`. Exits are faster than entrances and never overshoot. Components ship their craft (gliding indicators, rolling numbers, drawn ticks) on by default; to calm a dense view use `motion="subtle"` on the component or `<MotionScope level="subtle">` around a region, never a CSS override. → `sable docs motion`
|
|
46
46
|
7. **Icons by semantic name.** `<Icon name="check" />`; size with `text-{size}`, never type utilities. A mark attached to a line of type (dot, bullet, leading icon) sits in an `h-[1lh] items-center` box and is sized in em, never nudged with px. → `sable docs typography`
|
|
47
47
|
8. **Every scroll container declares a policy:** `scrollbar-quiet` (vertical) or `scrollbar-hidden` (horizontal, with an edge fade carrying the affordance).
|
|
48
48
|
9. **Accessible by construction.** WCAG AA contrast (and a label on a brand or status fill also clears APCA Lc 60 — `label-contrast`), 44px hit areas, visible focus, logical properties, live regions that exist before they speak. → `sable docs accessibility`
|
|
@@ -65,7 +65,7 @@ The machine-readable version of this section is the `consume` block in `registry
|
|
|
65
65
|
|
|
66
66
|
`styles.css` carries **Sable only** — the default theme, and the layout floor every other theme inherits (`--page-inset`, `--region-*`, `--rhythm-unit` on a bare `:root`). The bundled **alternates are opt-in**, because they are `[data-sable-theme]`-scoped and an app that never sets the attribute ships CSS that can never match: 5.5 KB gzip of permanently-dead weight, which is exactly how a real app's build audit found this. Using one? `@import "@sableui/react/theme/prism.css";`. Offering the whole set through a `<ThemeSelector>`? `@import "@sableui/react/themes.css";`. **Symptom of forgetting:** `data-sable-theme` is set, nothing matches, and the page silently renders as the Sable theme — no error, just the wrong theme.
|
|
67
67
|
4. **Fonts:** load Figtree (sans) + DM Mono (mono) — the theme declares the stacks, the host provides the files. Apply `antialiased` (`-webkit-font-smoothing: antialiased` / `-moz-osx-font-smoothing: grayscale`) **once on the app root**: Sable's type scale was drawn against smoothed text, and without it macOS renders every weight heavier than designed. The package deliberately doesn't set it — a library has no business styling the host's whole page — so it has to happen here.
|
|
68
|
-
5. **Providers:** `<IconProvider>` is **required** — wrap the app once in `<IconProvider set={reiconIconSet}>` with `import { reiconIconSet } from "@sableui/react/icons/reicon"` (without it every `<Icon>` renders nothing; development builds warn once). Pass another icon package's set instead to replace Reicon, or extend it using `composeIconSets(appExtras, reiconIconSet)` (list the added names in `.sable/icons.json` as `{ "names": [...] }` so `sable grade` accepts them); `<MotionProvider>` is optional too. Use `<SableThemeProvider>` + `useSableTheme()` when the app should expose user-selectable themes; pair `themeId` / `setThemeId` with `<ThemeSelector>` and `preference` / `setPreference` with `<ThemeSwitcher>`. The provider stores `light` / `dark` / `system`, toggles the `dark` class on `<html>`, stores the active design theme, and writes `data-sable-theme` for non-default themes. Apps with their own state layer can call `applySableColorMode()` / `applySableTheme()` instead. You only need a Tailwind `@custom-variant dark` if **your** app code uses `dark:` utilities (Sable ships none).
|
|
68
|
+
5. **Providers:** `<IconProvider>` is **required** — wrap the app once in `<IconProvider set={reiconIconSet}>` with `import { reiconIconSet } from "@sableui/react/icons/reicon"` (without it every `<Icon>` renders nothing; development builds warn once). Pass another icon package's set instead to replace Reicon, or extend it using `composeIconSets(appExtras, reiconIconSet)` (list the added names in `.sable/icons.json` as `{ "names": [...] }` so `sable grade` accepts them); `<MotionProvider>` is optional too (`policy="subtle"` keeps feedback and drops flourishes app-wide). Use `<SableThemeProvider>` + `useSableTheme()` when the app should expose user-selectable themes; pair `themeId` / `setThemeId` with `<ThemeSelector>` and `preference` / `setPreference` with `<ThemeSwitcher>`. The provider stores `light` / `dark` / `system`, toggles the `dark` class on `<html>`, stores the active design theme, and writes `data-sable-theme` for non-default themes. Apps with their own state layer can call `applySableColorMode()` / `applySableTheme()` instead. You only need a Tailwind `@custom-variant dark` if **your** app code uses `dark:` utilities (Sable ships none).
|
|
69
69
|
6. Import from the root barrel: `import { Button } from "@sableui/react"`. Page shells live on the `patterns` subpath (`import { SettingsPage } from "@sableui/react/patterns"`) — preview one, then copy its source from `registry.json → patterns` when the route needs to diverge. Blocks — section-sized compositions such as a sign-in card or a pricing card — live on the `blocks` subpath (`import { SignInBlock } from "@sableui/react/blocks"`) and ship the same way (`registry.json → blocks`); copy one and edit its content constants. A block's props are only `className`, `framed` and its primary action's callback, never content.
|
|
70
70
|
|
|
71
71
|
7. **Check the wiring:** `npx sable doctor`. Every check it runs corresponds to a way Sable fails *silently* — a second React (blank page, "invalid hook call"), a missing `@source` (Tailwind emits none of the package's classes, everything renders unstyled), or `data-sable-theme` set with no theme CSS imported (every theme renders as the Sable theme and the switcher looks broken). Exits 1 on any failure; `--json` for the structured report.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,119 @@
|
|
|
1
1
|
# What's new
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
Motion that is crafted by default and calm on request, and the first pieces for agent products.
|
|
6
|
+
|
|
7
|
+
- **New: two motion levels, and `subtle`.** Motion is now *essential*
|
|
8
|
+
(colour and focus feedback, press, overlay arrival, disclosure, loaders,
|
|
9
|
+
chart hover) or *expressive* (glides, rolling numbers, drawn ticks,
|
|
10
|
+
shakes, staggers, chart arrival and morph, scroll reveals, spring
|
|
11
|
+
overshoot). Expressive durations also multiply by `--motion-expressive`,
|
|
12
|
+
which the new `subtle` policy sets to 0, and `subtle` takes the overshoot
|
|
13
|
+
out of `ease-spring` and `ease-bounce`. So `full` (the default) plays both,
|
|
14
|
+
`subtle` keeps every answer and drops every flourish, and `off` stills
|
|
15
|
+
everything. Set it app-wide with `<MotionProvider policy="subtle">`, for a
|
|
16
|
+
region with the new `<MotionScope level="subtle">`, or per component with a
|
|
17
|
+
new `motion` prop. Scopes and props can only turn motion **down**: `full`
|
|
18
|
+
on a component means "inherit", so nothing can override a reader's
|
|
19
|
+
reduce-motion setting. `useMotion()` gains `expressive`, and
|
|
20
|
+
`useMotionLevel(motion)` resolves a component's prop against everything
|
|
21
|
+
around it. `Reveal` and `TextReveal` are expressive: under `subtle` the
|
|
22
|
+
content is simply there.
|
|
23
|
+
- **New recipes:** `motion-glide` (one indicator travelling to the selected
|
|
24
|
+
option), `motion-shake` (a refused answer, driven by the new `useShake()`),
|
|
25
|
+
`motion-tick` (a mark that draws itself in), `motion-appear` (a small
|
|
26
|
+
control mounting in place, through `@starting-style`), `motion-arrive`
|
|
27
|
+
(items a user action adds to a list, staggered) and `motion-size` (easing
|
|
28
|
+
between sizes, with the new `useAutoSize()` measuring a label change).
|
|
29
|
+
`sable utility <name>` has each one.
|
|
30
|
+
- **New: `NumberTicker`.** A number that rolls to its new value, digit by
|
|
31
|
+
digit, keyed by place value, so 999 → 1,000 rolls the columns it keeps and
|
|
32
|
+
grows the one it gains. It formats through `Intl.NumberFormat`, reads as
|
|
33
|
+
plain text to assistive tech and copy, and lands without a roll under
|
|
34
|
+
`subtle`. `NotificationBadge` counts and the `PieChart` / `RadialChart`
|
|
35
|
+
centre values now roll through it.
|
|
36
|
+
- **New: `ConfirmButton`.** An inline two-step for low-stakes destructive
|
|
37
|
+
actions. The first press arms it: the label changes, the button eases to
|
|
38
|
+
the new width, a thin line drains while it waits, and the armed state is
|
|
39
|
+
announced. A second press within `timeout` confirms; Escape, a click
|
|
40
|
+
elsewhere, blur or the timeout disarms it, and a double-click cannot
|
|
41
|
+
confirm. `DestructiveActionModal` stays the answer for high stakes.
|
|
42
|
+
- **New: `TooltipGroup`.** Wrap a row of `SimpleTooltip`s and they share one
|
|
43
|
+
tooltip: moving along a toolbar, it glides to the next button and its label
|
|
44
|
+
slides across instead of closing and reopening.
|
|
45
|
+
- **Selections travel.** `Tabs` renders a gliding indicator in both variants
|
|
46
|
+
(the underline and the pill), and a changed panel fades in. Single-select
|
|
47
|
+
`ToggleGroup appearance="segmented"` has one fill that glides and resizes
|
|
48
|
+
to the pressed option. `ThemeSwitcher` uses the same recipe, and
|
|
49
|
+
`Carousel`'s active dot eases its width. `SideNav` and `SidebarNav` draw
|
|
50
|
+
the current page as one fill that glides to the new item when the page
|
|
51
|
+
changes inside a persistent layout. Menu, Select and Combobox highlights
|
|
52
|
+
keep their glide, which is now expressive.
|
|
53
|
+
- **Calendar pages with direction.** Going to the next or previous month
|
|
54
|
+
slides the old grid out toward the edge it leaves by and the new one in
|
|
55
|
+
from the other, over a short travel with a fade (react-day-picker's
|
|
56
|
+
`animate` mode on Sable keyframes, mirrored in RTL). It is on at full
|
|
57
|
+
motion only (a `DatePicker`'s calendar follows the app or scope policy);
|
|
58
|
+
`Calendar` takes `motion`, and an explicit `animate` still wins.
|
|
59
|
+
- **Sidebar collapse eases.** Collapsing to the icon rail eases the width
|
|
60
|
+
instead of jumping 192px in one frame, so the content beside it visibly
|
|
61
|
+
takes the room. Icons stay on one column throughout and labels fade back
|
|
62
|
+
in on reopen. `Sidebar` takes `motion`; `subtle` snaps.
|
|
63
|
+
- **Focus outlines ease in.** `motion-interactive` now also transitions
|
|
64
|
+
`outline-color`, `outline-width` and `outline-offset`, so a keyboard focus
|
|
65
|
+
ring drawn with `outline-*` grows from the element's edge over the fast
|
|
66
|
+
step instead of appearing in one frame. Instant under reduced motion.
|
|
67
|
+
- **States finish their story.** `OtpField` shakes once when it turns
|
|
68
|
+
invalid, and its digits take the destructive colour. `CodeBlock`'s copy
|
|
69
|
+
button swaps to a check in place and announces "Copied". `Feedback` shows a
|
|
70
|
+
drawn check and a thank-you before it closes. A loading `Button` keeps its
|
|
71
|
+
width: the spinner lays over the label instead of replacing it. The
|
|
72
|
+
`ChatComposer` grows with an ease, and its send and stop swap in one
|
|
73
|
+
button. `SearchInput` and `CommandMenu`'s clear buttons appear rather than
|
|
74
|
+
pop. Expanding a `Tree` folder settles its rows in, staggered. The
|
|
75
|
+
`Checkbox` tick draws in from its leading stroke.
|
|
76
|
+
- **Fixed: disclosure chevrons snapped.** Tailwind v4's `rotate-*` writes the
|
|
77
|
+
standalone `rotate` property, which `motion-interactive` did not list, so
|
|
78
|
+
the chevrons on `Accordion`, `Collapsible`, `Tree` and `ShowMore` jumped
|
|
79
|
+
180° in one frame while the panel beside them eased. The recipe now lists
|
|
80
|
+
`rotate`. `DataTable`'s sort chevron rotates instead of swapping glyphs.
|
|
81
|
+
- **Chart hover dims mix toward the surface.** The bars, slices and rings
|
|
82
|
+
that are not under the pointer now step back by mixing toward the surface
|
|
83
|
+
colour instead of fading, so gridlines no longer strike through a dimmed
|
|
84
|
+
bar and a dimmed ring stays solid over its track.
|
|
85
|
+
- **Deprecated: `animate` on charts.** Every chart takes `motion` instead.
|
|
86
|
+
`animate={false}` maps to `motion="subtle"` (no arrival or morph, hover
|
|
87
|
+
feedback kept); use `motion="off"` to still everything.
|
|
88
|
+
- **Motion declaration is a build gate.** A component that reacts to the
|
|
89
|
+
pointer or carries state must declare its four motion slots (arrival,
|
|
90
|
+
response, transition, activity) in its meta, or decline each with a reason.
|
|
91
|
+
It used to be a report, and 36 components said nothing; all of them now
|
|
92
|
+
answer.
|
|
93
|
+
- **Docs:** a new **Interactions** page shows the catalog by task (inspect,
|
|
94
|
+
select, confirm, recover, succeed, finish loading, type, point, notify).
|
|
95
|
+
Every preview has its own motion level and Replay. Playgrounds gain a Motion
|
|
96
|
+
row and **Copy for agent**, which copies the exact JSX, imports, install
|
|
97
|
+
commands, the active theme and `sable component <Name>`. The Motion page
|
|
98
|
+
sets the policy to Full, Subtle or Off and shows the two levels side by
|
|
99
|
+
side.
|
|
100
|
+
|
|
101
|
+
- **New: `ToolCall`.** One tool an agent called, as a quiet row inside its
|
|
102
|
+
turn: a status glyph, what it did in plain words, the tool's name in mono,
|
|
103
|
+
and one word for where it is (`running`, `awaiting`, `complete`, `error`,
|
|
104
|
+
`denied`). Input and output sit behind a disclosure, as formatted JSON or
|
|
105
|
+
as a rendered result passed as `children`. A failure's message shows under
|
|
106
|
+
the row without opening anything. When a call settles, its spinner swaps
|
|
107
|
+
for the outcome in place (`motion-icon-swap`), and that's the only motion
|
|
108
|
+
in the row. The change is announced once, and a thread restored from
|
|
109
|
+
history announces nothing.
|
|
110
|
+
- **New: `ApprovalBlock`**, in a new "AI & agents" block family. An agent
|
|
111
|
+
asking permission for one call: the question in plain words, what can't be
|
|
112
|
+
undone, the call's details, an "allow for the rest of this chat" Checkbox,
|
|
113
|
+
and Deny / Allow. Neither button is focused on arrival. After a decision,
|
|
114
|
+
one status line replaces the buttons, focus moves to it, and it's
|
|
115
|
+
announced. `onApprove({ remember })` and `onDeny()` report the answer.
|
|
116
|
+
|
|
3
117
|
## 0.13.2
|
|
4
118
|
|
|
5
119
|
What to use instead, wherever a component is the wrong pick.
|
package/CONTRIBUTING.md
CHANGED
|
@@ -168,7 +168,9 @@ artwork, and nothing in the core graph imports `src/icon-sets/`.
|
|
|
168
168
|
|
|
169
169
|
## Motion
|
|
170
170
|
|
|
171
|
-
Motion is a first-class layer — **
|
|
171
|
+
Motion is a first-class layer — **crafted by default, calm on request, zero runtime dependency** (CSS tokens/recipes + tiny browser-native JS). When a component has any interaction, wire it to the system; don't leave it static or hand-roll a transition. When its selection, value or mark changes in place, move the one element that represents it (`motion-glide`, `NumberTicker`, `motion-tick`, `motion-icon-swap`) rather than switching two things on and off.
|
|
172
|
+
|
|
173
|
+
**Two levels.** Essential motion (feedback, press, overlay arrival, disclosure, loaders) runs on the plain tokens. Expressive motion (glides, rolling numbers, drawn ticks, shakes, staggers, arrival choreography, overshoot) multiplies its durations by `var(--motion-expressive, 1)` as well — the shipped expressive recipes already do — and JS-driven flourishes gate on `useMotionLevel(motion).expressive`. A component that owns a flourish takes `motion?: MotionLevel` (`full` | `subtle` | `off`) and spreads `data-motion={motionAttribute(motion)}` on its root; the prop only turns motion down.
|
|
172
174
|
|
|
173
175
|
**Four slots.** Every component answers all four — by filling the slot, or by declining it in `meta.motion` with `recipe: "none"` and a reason:
|
|
174
176
|
|
|
@@ -179,13 +181,13 @@ Motion is a first-class layer — **subtle by default, tasteful spring welcome,
|
|
|
179
181
|
| `transition` | how does it move between its own states? | `motion-disclosure`, or ease the transform |
|
|
180
182
|
| `activity` | how do you tell "working" from "frozen"? | `motion-spin` · `motion-pulse` · `motion-shimmer` |
|
|
181
183
|
|
|
182
|
-
This paragraph said "don't leave it static" for many releases while **nothing checked it**. The four motion rules below are all prohibitive — they fire on motion that is present and wrong, and none can fire on motion that is absent — so a component that snapped between every state passed the whole contract, and motion stayed a thing a human had to request by name. Two gates now close that: `static-interactive` (lint, fires on an element styled for pointer states with no recipe on it) and `meta.motion`
|
|
184
|
+
This paragraph said "don't leave it static" for many releases while **nothing checked it**. The four motion rules below are all prohibitive — they fire on motion that is present and wrong, and none can fire on motion that is absent — so a component that snapped between every state passed the whole contract, and motion stayed a thing a human had to request by name. Two gates now close that: `static-interactive` (lint, fires on an element styled for pointer states with no recipe on it) and `meta.motion` — `build-registry` **fails** when a component that reacts to the pointer or carries state declares no `motion` array (`pnpm build:registry -- --motion-report` prints the slot-by-slot coverage).
|
|
183
185
|
|
|
184
|
-
1. **Reach for a recipe first** — apply one named class instead of a bespoke transition string: `motion-interactive` (default hover/focus/press chrome — **replaces** `transition-colors`), `motion-press` (tactile press; always pair with `motion-interactive`), `motion-fade`/`motion-pop`/`motion-slide-{up,down,left,right}` (overlay enter/exit, on Base UI `data-[starting-style]`/`data-[ending-style]`), `motion-disclosure` (height), and loaders `motion-{spin,shimmer,pulse,dot-bounce,ping}`.
|
|
186
|
+
1. **Reach for a recipe first** — apply one named class instead of a bespoke transition string: `motion-interactive` (default hover/focus/press chrome — **replaces** `transition-colors`), `motion-press` (tactile press; always pair with `motion-interactive`), `motion-fade`/`motion-pop`/`motion-slide-{up,down,left,right}` (overlay enter/exit, on Base UI `data-[starting-style]`/`data-[ending-style]`), `motion-disclosure` (height), `motion-icon-swap` (a glyph that changes with state), the expressive `motion-{glide,shake,tick,appear,arrive,size}`, and loaders `motion-{spin,shimmer,pulse,dot-bounce,ping}`.
|
|
185
187
|
2. **Drop to tokens only when a recipe doesn't fit.** Durations via `duration-(--motion-duration-{instant|fast|base|slow|slower})` — **never** a raw `duration-150` (it bypasses the kill-switch). Easings via `ease-{standard|decelerate|emphasized|spring|bounce}`. Spring/bounce overshoot is **enter-only**; exits stay clean.
|
|
186
188
|
3. **Never** use Tailwind's built-in `animate-spin`/`animate-pulse`/`animate-ping`/`animate-bounce` — they ignore `--motion-scale`, so the on/off policy can't freeze them. Use the scale-aware `motion-*` loader recipes.
|
|
187
189
|
4. **Compositor-first & subtle:** animate opacity + transform (scale/translate) only; short durations; no exit overshoot; no gaudy motion.
|
|
188
|
-
5. **Policy:** all token-driven motion collapses to static under `prefers-reduced-motion` or `<MotionProvider>` off (one `--motion-scale: 0`). JS primitives read `useMotion().enabled` and must no-op when motion is off
|
|
190
|
+
5. **Policy:** all token-driven motion collapses to static under `prefers-reduced-motion` or `<MotionProvider>` off (one `--motion-scale: 0`); `subtle` zeroes `--motion-expressive` only. JS primitives read `useMotion().enabled` and must no-op when motion is off; JS flourishes read `expressive`.
|
|
189
191
|
6. **JS motion lives in `src/motion/`** (zero-dep, browser-native: WAAPI / IntersectionObserver / View Transitions). Do **not** add the `motion` library to core — drag/gesture, FLIP layout, and velocity springs are the future optional `@sableui/motion` adapter (peer-dep `motion`).
|
|
190
192
|
|
|
191
193
|
Full token table + rationale: `DESIGN.md → Motion`. Live demo: docs → Foundations → Motion (also the on/off toggle).
|
package/DESIGN.md
CHANGED
|
@@ -477,7 +477,11 @@ Reference via `z-(--z-overlay)`, never a raw `z-[9999]`. Base UI overlays portal
|
|
|
477
477
|
|
|
478
478
|
## Motion
|
|
479
479
|
|
|
480
|
-
Motion is a first-class token layer, not an afterthought. **
|
|
480
|
+
Motion is a first-class token layer, not an afterthought. **Crafted by default, calm on request** — quick, deliberate, skippable, and never gaudy. Sable components ship the small continuity that makes an interface feel made rather than assembled: one indicator travels to the selected tab instead of two fills blinking, a total rolls to its new value, a check draws itself in, a wrong code shakes and clears. Built compositor-first (opacity + transform, with height and an absolutely positioned glider's size as the sanctioned exceptions).
|
|
481
|
+
|
|
482
|
+
**The principle behind the craft: one thing travels; two things do not blink.** When a selection, a value or a mark changes in place, move the element that represents it. And finish the story — a refused answer shakes *and* says why, a success confirms where it happened, an armed action asks again before it acts.
|
|
483
|
+
|
|
484
|
+
**Two levels.** *Essential* motion answers the user — colour and focus feedback, press, overlay arrival, disclosure height, loaders, chart hover. *Expressive* motion narrates — glides, rolling numbers, drawn ticks, shakes, staggers, chart arrival and morph, scroll reveals, spring overshoot. Expressive durations are multiplied by a second factor, `--motion-expressive`, which `data-motion="subtle"` sets to 0 (and which takes the overshoot out of `ease-spring`). So the policy ladder is `full` (default: both) → `subtle` (essential only) → `off` (nothing moves; every state still changes). Set it app-wide on `<MotionProvider policy>`, for a region with `<MotionScope level>`, or per component with the `motion` prop that every component owning a flourish takes. Scopes and props can only turn motion **down** — `full` on a component means "inherit", so nothing below the provider can override a reader's reduce-motion setting.
|
|
481
485
|
|
|
482
486
|
**Three parts:**
|
|
483
487
|
|
|
@@ -487,9 +491,10 @@ Motion is a first-class token layer, not an afterthought. **Subtle by default, w
|
|
|
487
491
|
- Consume easings via the `ease-*` utilities; durations via `duration-(--motion-duration-*)` (so the kill-switch always applies).
|
|
488
492
|
|
|
489
493
|
2. **Recipes** (`styles.css`) — named, reusable behaviours a component opts into with one class instead of a bespoke transition string:
|
|
490
|
-
- `motion-interactive` (hover/focus colour + transform transitions) · `motion-press` (tactile press settle) · `motion-fade` (scrims) · `motion-pop` (anchored/centred surfaces — gentle spring on open) · `motion-slide-{up,down,left,right}` (directional fade-slide) · `motion-disclosure` (height) · `motion-draw` (SVG line-art draw-on — a `pathLength="1"` path inside a `<Reveal>` draws in when scrolled into view; stagger siblings via `--motion-draw-delay`) · loaders `motion-spin`/`motion-pulse`/`motion-shimmer`/`motion-dot-bounce`. Enter/exit recipes drive off Base UI's `data-[starting-style]`/`data-[ending-style]`.
|
|
494
|
+
- `motion-interactive` (hover/focus colour + transform transitions, including `rotate`) · `motion-press` (tactile press settle) · `motion-fade` (scrims) · `motion-pop` (anchored/centred surfaces — gentle spring on open) · `motion-slide-{up,down,left,right}` (directional fade-slide) · `motion-disclosure` (height) · `motion-draw` (SVG line-art draw-on — a `pathLength="1"` path inside a `<Reveal>` draws in when scrolled into view; stagger siblings via `--motion-draw-delay`) · loaders `motion-spin`/`motion-pulse`/`motion-shimmer`/`motion-dot-bounce`. Enter/exit recipes drive off Base UI's `data-[starting-style]`/`data-[ending-style]`.
|
|
495
|
+
- Expressive: `motion-glide` (one indicator travelling to the selection) · `motion-shake` (a refused answer, driven by `useShake()`) · `motion-tick` (a mark that draws itself in) · `motion-appear` (a small control mounting via `@starting-style`) · `motion-arrive` (items a user action adds to a list, staggered) · `motion-size` (easing between intrinsic sizes via `interpolate-size`). `NumberTicker` is the rolling-number primitive.
|
|
491
496
|
|
|
492
|
-
3. **Policy** — `prefers-reduced-motion` and `<MotionProvider>` collapse `--motion-scale` to 0 in one place: transitions resolve instantly, loaders hold their first frame.
|
|
497
|
+
3. **Policy** — `prefers-reduced-motion` and `<MotionProvider>` collapse `--motion-scale` to 0 in one place: transitions resolve instantly, loaders hold their first frame. `subtle` zeroes `--motion-expressive` instead, so essential motion keeps playing. In-app, `useMotion()` reads and sets the policy; `useMotionLevel(motion)` resolves a component's own prop against everything around it, and JS-driven flourishes gate on its `expressive`.
|
|
493
498
|
|
|
494
499
|
Richer physics (interruptible velocity-springs, FLIP layout, drag/gesture) is intentionally **out of core** — it's the future optional `@sableui/motion` adapter (peer-dep `motion`), reached for only when CSS genuinely can't express a surface.
|
|
495
500
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// src/blocks/approval-block.tsx
|
|
2
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
3
|
+
import { Button } from "../components/button/index.js";
|
|
4
|
+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../components/card/index.js";
|
|
5
|
+
import { Checkbox } from "../components/checkbox/index.js";
|
|
6
|
+
import { Code } from "../components/code/index.js";
|
|
7
|
+
import { InlineStatus } from "../components/inline-status/index.js";
|
|
8
|
+
import { Label } from "../components/label/index.js";
|
|
9
|
+
import { cn } from "../lib/cn.js";
|
|
10
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
var request = {
|
|
12
|
+
tool: "send_email",
|
|
13
|
+
title: "Send the invoice to Ada?",
|
|
14
|
+
description: "The agent wants to email invoice INV-1042. An email can't be unsent.",
|
|
15
|
+
details: [
|
|
16
|
+
{ label: "To", value: "ada@example.com" },
|
|
17
|
+
{ label: "Subject", value: "INV-1042 for October" },
|
|
18
|
+
{ label: "Attached", value: "INV-1042.pdf" }
|
|
19
|
+
],
|
|
20
|
+
allowed: "Allowed. Sending the email.",
|
|
21
|
+
denied: "Denied. The agent won't send it."
|
|
22
|
+
};
|
|
23
|
+
function ApprovalBlock({ className, framed = true, onApprove, onDeny }) {
|
|
24
|
+
const [decision, setDecision] = useState(null);
|
|
25
|
+
const [remember, setRemember] = useState(false);
|
|
26
|
+
const id = useId();
|
|
27
|
+
const outcome = useRef(null);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (decision) outcome.current?.focus();
|
|
30
|
+
}, [decision]);
|
|
31
|
+
return /* @__PURE__ */ jsxs(Card, { variant: framed ? "default" : "flat", className: cn("w-full max-w-md", className), children: [
|
|
32
|
+
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
33
|
+
/* @__PURE__ */ jsx(CardTitle, { children: request.title }),
|
|
34
|
+
/* @__PURE__ */ jsx(CardDescription, { children: request.description })
|
|
35
|
+
] }),
|
|
36
|
+
/* @__PURE__ */ jsxs(CardContent, { className: "flex flex-col gap-4", children: [
|
|
37
|
+
/* @__PURE__ */ jsxs("dl", { className: "grid grid-cols-[auto_minmax(0,1fr)] gap-x-4 gap-y-1.5 text-body-sm", children: [
|
|
38
|
+
request.details.map((d) => /* @__PURE__ */ jsxs("div", { className: "contents", children: [
|
|
39
|
+
/* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: d.label }),
|
|
40
|
+
/* @__PURE__ */ jsx("dd", { className: "truncate text-foreground", children: d.value })
|
|
41
|
+
] }, d.label)),
|
|
42
|
+
/* @__PURE__ */ jsxs("div", { className: "contents", children: [
|
|
43
|
+
/* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: "Tool" }),
|
|
44
|
+
/* @__PURE__ */ jsx("dd", { className: "min-w-0", children: /* @__PURE__ */ jsx(Code, { children: request.tool }) })
|
|
45
|
+
] })
|
|
46
|
+
] }),
|
|
47
|
+
decision ? null : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
48
|
+
/* @__PURE__ */ jsx(Checkbox, { id: `${id}-remember`, checked: remember, onCheckedChange: (v) => setRemember(v) }),
|
|
49
|
+
/* @__PURE__ */ jsxs(Label, { htmlFor: `${id}-remember`, className: "text-body-sm", children: [
|
|
50
|
+
"Allow ",
|
|
51
|
+
/* @__PURE__ */ jsx(Code, { children: request.tool }),
|
|
52
|
+
" for the rest of this chat"
|
|
53
|
+
] })
|
|
54
|
+
] })
|
|
55
|
+
] }),
|
|
56
|
+
/* @__PURE__ */ jsx(CardFooter, { actions: true, className: "justify-end", children: decision ? /* @__PURE__ */ jsx(
|
|
57
|
+
InlineStatus,
|
|
58
|
+
{
|
|
59
|
+
ref: outcome,
|
|
60
|
+
tabIndex: -1,
|
|
61
|
+
role: "status",
|
|
62
|
+
tone: decision === "allowed" ? "success" : "neutral",
|
|
63
|
+
className: "me-auto outline-none",
|
|
64
|
+
children: decision === "allowed" ? request.allowed : request.denied
|
|
65
|
+
}
|
|
66
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
67
|
+
/* @__PURE__ */ jsx(
|
|
68
|
+
Button,
|
|
69
|
+
{
|
|
70
|
+
variant: "outline",
|
|
71
|
+
onClick: () => {
|
|
72
|
+
setDecision("denied");
|
|
73
|
+
onDeny?.();
|
|
74
|
+
},
|
|
75
|
+
children: "Deny"
|
|
76
|
+
}
|
|
77
|
+
),
|
|
78
|
+
/* @__PURE__ */ jsx(
|
|
79
|
+
Button,
|
|
80
|
+
{
|
|
81
|
+
onClick: () => {
|
|
82
|
+
setDecision("allowed");
|
|
83
|
+
onApprove?.({ remember });
|
|
84
|
+
},
|
|
85
|
+
children: "Allow"
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
] }) })
|
|
89
|
+
] });
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
ApprovalBlock
|
|
93
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/blocks/approval-block.meta.ts
|
|
2
|
+
var approvalBlockMeta = {
|
|
3
|
+
name: "ApprovalBlock",
|
|
4
|
+
family: "AI & agents",
|
|
5
|
+
kind: "card",
|
|
6
|
+
width: { min: "18rem", max: "28rem" },
|
|
7
|
+
summary: `An agent asking permission for one tool call: the question, what will happen and what can't be undone, the call's details, an "allow for the rest of this chat" Checkbox, and Deny / Allow.`,
|
|
8
|
+
intent: 'Reach for this in a chat or agent run when a tool call waits on the person \u2014 sending, paying, deleting, publishing. The call itself, before and after the decision, is a ToolCall with status="awaiting", then "complete" or "denied". A destructive action outside an agent is DestructiveActionModal.',
|
|
9
|
+
notes: [
|
|
10
|
+
`The title is the question in plain words ("Send the invoice to Ada?"), not the tool's name; the tool's name is one row of the details, for the person who wants to know exactly what runs.`,
|
|
11
|
+
"The description says what can't be undone. That sentence is what the person is deciding on.",
|
|
12
|
+
"Deny comes first and Allow is the primary on the end, the same order as every Sable confirmation. Neither is focused on arrival: an approval that a stray Enter can grant is not an approval.",
|
|
13
|
+
"After a decision the buttons give way to one InlineStatus line saying what happened, so the thread keeps a record of the answer."
|
|
14
|
+
],
|
|
15
|
+
a11y: [
|
|
16
|
+
"Focus moves to the outcome line when the buttons leave, instead of dropping to the page; the line is a role=status region, so the decision is announced.",
|
|
17
|
+
"The details are a dl, so each value is read with its label.",
|
|
18
|
+
"The remember Checkbox has its own Label, so the tool name inside it is part of its accessible name."
|
|
19
|
+
]
|
|
20
|
+
};
|
|
21
|
+
export {
|
|
22
|
+
approvalBlockMeta
|
|
23
|
+
};
|
package/dist/blocks/index.d.ts
CHANGED
|
@@ -13,6 +13,21 @@ declare function ApiKeysBlock({ className, framed, onCreate, onKeyAction }: ApiK
|
|
|
13
13
|
|
|
14
14
|
declare const apiKeysBlockMeta: BlockMeta;
|
|
15
15
|
|
|
16
|
+
interface ApprovalBlockProps {
|
|
17
|
+
className?: string;
|
|
18
|
+
/** false drops the card's surface and border — for a chat column that already frames it. */
|
|
19
|
+
framed?: boolean;
|
|
20
|
+
/** The person allowed the call. `remember` is the "for the rest of this chat" choice. */
|
|
21
|
+
onApprove?: (options: {
|
|
22
|
+
remember: boolean;
|
|
23
|
+
}) => void;
|
|
24
|
+
/** The person declined the call. */
|
|
25
|
+
onDeny?: () => void;
|
|
26
|
+
}
|
|
27
|
+
declare function ApprovalBlock({ className, framed, onApprove, onDeny }: ApprovalBlockProps): react.JSX.Element;
|
|
28
|
+
|
|
29
|
+
declare const approvalBlockMeta: BlockMeta;
|
|
30
|
+
|
|
16
31
|
interface ChatThreadBlockProps {
|
|
17
32
|
className?: string;
|
|
18
33
|
/** false drops the card's surface and border. Keep it framed on a muted page: incoming bubbles are the muted surface too. */
|
|
@@ -393,4 +408,4 @@ declare function WalletBlock({ className, framed, onSend, onReceive }: WalletBlo
|
|
|
393
408
|
|
|
394
409
|
declare const walletBlockMeta: BlockMeta;
|
|
395
410
|
|
|
396
|
-
export { ApiKeysBlock, ChatThreadBlock, CommandPaletteBlock, CtaBandBlock, DangerZoneBlock, DeviceComparisonBlock, EventCardBlock, FaqBlock, FeatureGridBlock, FeedbackFormBlock, GoalProgressBlock, HeroBlock, InviteTeamBlock, InvoiceHistoryBlock, KpiSparklineBlock, MediaPlayerBlock, NewsletterBlock, NotFoundBlock, NotificationListBlock, NotificationSettingsBlock, OrderTrackingBlock, PricingCardBlock, PricingTableBlock, ProductCardBlock, ProfileCardBlock, ProfileFormBlock, RevenueBreakdownBlock, SignInBlock, SignUpBlock, SiteFooterBlock, StatRowBlock, StepsBlock, TaskListBlock, TeamMembersBlock, TestimonialBlock, TrafficSourcesBlock, TrafficTrendBlock, UploadListBlock, VerifyCodeBlock, WalletBlock, apiKeysBlockMeta, chatThreadBlockMeta, commandPaletteBlockMeta, ctaBandBlockMeta, dangerZoneBlockMeta, deviceComparisonBlockMeta, eventCardBlockMeta, faqBlockMeta, featureGridBlockMeta, feedbackFormBlockMeta, goalProgressBlockMeta, heroBlockMeta, inviteTeamBlockMeta, invoiceHistoryBlockMeta, kpiSparklineBlockMeta, mediaPlayerBlockMeta, newsletterBlockMeta, notFoundBlockMeta, notificationListBlockMeta, notificationSettingsBlockMeta, orderTrackingBlockMeta, pricingCardBlockMeta, pricingTableBlockMeta, productCardBlockMeta, profileCardBlockMeta, profileFormBlockMeta, revenueBreakdownBlockMeta, signInBlockMeta, signUpBlockMeta, siteFooterBlockMeta, statRowBlockMeta, stepsBlockMeta, taskListBlockMeta, teamMembersBlockMeta, testimonialBlockMeta, trafficSourcesBlockMeta, trafficTrendBlockMeta, uploadListBlockMeta, verifyCodeBlockMeta, walletBlockMeta };
|
|
411
|
+
export { ApiKeysBlock, ApprovalBlock, ChatThreadBlock, CommandPaletteBlock, CtaBandBlock, DangerZoneBlock, DeviceComparisonBlock, EventCardBlock, FaqBlock, FeatureGridBlock, FeedbackFormBlock, GoalProgressBlock, HeroBlock, InviteTeamBlock, InvoiceHistoryBlock, KpiSparklineBlock, MediaPlayerBlock, NewsletterBlock, NotFoundBlock, NotificationListBlock, NotificationSettingsBlock, OrderTrackingBlock, PricingCardBlock, PricingTableBlock, ProductCardBlock, ProfileCardBlock, ProfileFormBlock, RevenueBreakdownBlock, SignInBlock, SignUpBlock, SiteFooterBlock, StatRowBlock, StepsBlock, TaskListBlock, TeamMembersBlock, TestimonialBlock, TrafficSourcesBlock, TrafficTrendBlock, UploadListBlock, VerifyCodeBlock, WalletBlock, apiKeysBlockMeta, approvalBlockMeta, chatThreadBlockMeta, commandPaletteBlockMeta, ctaBandBlockMeta, dangerZoneBlockMeta, deviceComparisonBlockMeta, eventCardBlockMeta, faqBlockMeta, featureGridBlockMeta, feedbackFormBlockMeta, goalProgressBlockMeta, heroBlockMeta, inviteTeamBlockMeta, invoiceHistoryBlockMeta, kpiSparklineBlockMeta, mediaPlayerBlockMeta, newsletterBlockMeta, notFoundBlockMeta, notificationListBlockMeta, notificationSettingsBlockMeta, orderTrackingBlockMeta, pricingCardBlockMeta, pricingTableBlockMeta, productCardBlockMeta, profileCardBlockMeta, profileFormBlockMeta, revenueBreakdownBlockMeta, signInBlockMeta, signUpBlockMeta, siteFooterBlockMeta, statRowBlockMeta, stepsBlockMeta, taskListBlockMeta, teamMembersBlockMeta, testimonialBlockMeta, trafficSourcesBlockMeta, trafficTrendBlockMeta, uploadListBlockMeta, verifyCodeBlockMeta, walletBlockMeta };
|
package/dist/blocks/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// src/blocks/index.ts
|
|
2
2
|
import { ApiKeysBlock } from "./api-keys-block.js";
|
|
3
3
|
import { apiKeysBlockMeta } from "./api-keys-block.meta.js";
|
|
4
|
+
import { ApprovalBlock } from "./approval-block.js";
|
|
5
|
+
import { approvalBlockMeta } from "./approval-block.meta.js";
|
|
4
6
|
import { ChatThreadBlock } from "./chat-thread-block.js";
|
|
5
7
|
import { chatThreadBlockMeta } from "./chat-thread-block.meta.js";
|
|
6
8
|
import { CommandPaletteBlock } from "./command-palette-block.js";
|
|
@@ -81,6 +83,7 @@ import { WalletBlock } from "./wallet-block.js";
|
|
|
81
83
|
import { walletBlockMeta } from "./wallet-block.meta.js";
|
|
82
84
|
export {
|
|
83
85
|
ApiKeysBlock,
|
|
86
|
+
ApprovalBlock,
|
|
84
87
|
ChatThreadBlock,
|
|
85
88
|
CommandPaletteBlock,
|
|
86
89
|
CtaBandBlock,
|
|
@@ -121,6 +124,7 @@ export {
|
|
|
121
124
|
VerifyCodeBlock,
|
|
122
125
|
WalletBlock,
|
|
123
126
|
apiKeysBlockMeta,
|
|
127
|
+
approvalBlockMeta,
|
|
124
128
|
chatThreadBlockMeta,
|
|
125
129
|
commandPaletteBlockMeta,
|
|
126
130
|
ctaBandBlockMeta,
|
|
@@ -122,6 +122,12 @@ var accordionMeta = {
|
|
|
122
122
|
part: "AccordionPanel",
|
|
123
123
|
why: "Panel height is the only cue that content came from THIS header rather than appearing at the bottom of the page. Snapping it open moves everything below by a screenful with nothing connecting cause to effect."
|
|
124
124
|
},
|
|
125
|
+
{
|
|
126
|
+
slot: "transition",
|
|
127
|
+
recipe: "motion-interactive",
|
|
128
|
+
part: "Chevron",
|
|
129
|
+
why: "The chevron turning is the header's half of the same event as the panel opening. motion-interactive lists `rotate` (what Tailwind's rotate-* writes), so it turns with the panel instead of flipping 180\xB0 in one frame beside an easing panel. A custom `indicator` that rotates should carry motion-interactive itself."
|
|
130
|
+
},
|
|
125
131
|
{
|
|
126
132
|
slot: "activity",
|
|
127
133
|
recipe: "none",
|
|
@@ -134,7 +140,7 @@ var accordionMeta = {
|
|
|
134
140
|
"Arrow keys move roving focus between triggers; Home/End jump to the first/last; loopFocus wraps by default.",
|
|
135
141
|
"Each trigger toggles its panel with Enter/Space; the panel is announced as a labelled region.",
|
|
136
142
|
"A disabled AccordionItem dims and is skipped by keyboard focus while neighbouring items stay interactive.",
|
|
137
|
-
"The chevron rotation is driven by data-[panel-open] and the focus ring by focus-visible
|
|
143
|
+
"The chevron rotation is driven by data-[panel-open] and the focus ring by focus-visible; both ride motion-interactive, so reduced motion stills them with everything else."
|
|
138
144
|
],
|
|
139
145
|
examples: [
|
|
140
146
|
{
|
|
@@ -147,7 +153,7 @@ var accordionMeta = {
|
|
|
147
153
|
},
|
|
148
154
|
{
|
|
149
155
|
label: "Custom indicator (plus-becomes-\xD7)",
|
|
150
|
-
code: '<Accordion defaultValue={["a"]}><AccordionItem value="a"><AccordionTrigger indicator={<Icon name="plus" className="text-xs text-muted-foreground
|
|
156
|
+
code: '<Accordion defaultValue={["a"]}><AccordionItem value="a"><AccordionTrigger indicator={<Icon name="plus" className="text-xs text-muted-foreground motion-interactive [[data-panel-open]_&]:rotate-45" />}>Can I cancel anytime?</AccordionTrigger><AccordionPanel>Yes \u2014 from billing settings.</AccordionPanel></AccordionItem></Accordion>'
|
|
151
157
|
}
|
|
152
158
|
]
|
|
153
159
|
};
|
|
@@ -117,11 +117,18 @@ var areaChartMeta = {
|
|
|
117
117
|
default: "false for columns; true for horizontal bars",
|
|
118
118
|
description: "The vertical axis."
|
|
119
119
|
},
|
|
120
|
+
{
|
|
121
|
+
name: "motion",
|
|
122
|
+
type: '"full" | "subtle" | "off"',
|
|
123
|
+
default: '"full"',
|
|
124
|
+
description: "How much motion this component uses: `full` (default) inherits the app's policy, `subtle` keeps feedback and drops the flourish, `off` stills it. It can only turn motion down \u2014 it never overrides a reader's reduce-motion setting. The arrival (draw-on, rising fill) and the data-change morph are the flourish; the gliding cursor and tooltip are the feedback `subtle` keeps."
|
|
125
|
+
},
|
|
120
126
|
{
|
|
121
127
|
name: "animate",
|
|
122
128
|
type: "boolean",
|
|
123
129
|
default: "true",
|
|
124
|
-
description:
|
|
130
|
+
description: 'Play the arrival once the chart scrolls into view. Reduced motion shows the finished chart. `animate={false}` is `motion="subtle"`.',
|
|
131
|
+
deprecated: 'motion="subtle" (what animate={false} now maps to: no arrival, hover kept) or motion="off"'
|
|
125
132
|
},
|
|
126
133
|
{
|
|
127
134
|
name: "activeIndex",
|
|
@@ -176,7 +183,7 @@ var areaChartMeta = {
|
|
|
176
183
|
slot: "arrival",
|
|
177
184
|
recipe: "custom",
|
|
178
185
|
part: "Area + line",
|
|
179
|
-
why:
|
|
186
|
+
why: 'Once on screen, each line draws on (stroke-dashoffset) and its fill rises from the baseline, series staggered \u2014 the trend is read as it is drawn. On the motion tokens; reduced motion shows the finished chart. The arrival is expressive: it plays at the full motion level only, so `motion="subtle"` (or a subtle app policy) shows the finished chart while hover feedback stays.'
|
|
180
187
|
},
|
|
181
188
|
{
|
|
182
189
|
slot: "response",
|
|
@@ -188,7 +195,7 @@ var areaChartMeta = {
|
|
|
188
195
|
slot: "transition",
|
|
189
196
|
recipe: "custom",
|
|
190
197
|
part: "Area + line",
|
|
191
|
-
why: "Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place."
|
|
198
|
+
why: "Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place. Expressive, like the arrival: under `subtle` new data swaps in place."
|
|
192
199
|
},
|
|
193
200
|
{
|
|
194
201
|
slot: "activity",
|
|
@@ -148,6 +148,32 @@ var attachmentMeta = {
|
|
|
148
148
|
"overlay-foreground",
|
|
149
149
|
"ring"
|
|
150
150
|
],
|
|
151
|
+
motion: [
|
|
152
|
+
{
|
|
153
|
+
slot: "arrival",
|
|
154
|
+
recipe: "none",
|
|
155
|
+
part: "Root",
|
|
156
|
+
why: "An attachment lands as part of a composer or a message, and that surface owns the entrance. A card that animated in on its own would read as a second, separate event \u2014 as if another file had been added."
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
slot: "response",
|
|
160
|
+
recipe: "motion-interactive",
|
|
161
|
+
part: "Root",
|
|
162
|
+
why: "With `onOpen`, the edge steps up under the pointer; it is the only cue that the card itself opens the file, not just its actions. Eased, it reads as the card answering the cursor rather than a border flickering as the pointer crosses a busy composer."
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
slot: "transition",
|
|
166
|
+
recipe: "motion-interactive",
|
|
167
|
+
part: "Root",
|
|
168
|
+
why: "Uploading to failed re-colours the edge and fill without changing the shape. The ease keeps it legible as the same file turning red; snapped, a failed card looks like a different card replaced the one that was sent."
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
slot: "activity",
|
|
172
|
+
recipe: "motion-pulse",
|
|
173
|
+
part: "Progress",
|
|
174
|
+
why: "While uploading with no known `progress`, the bar is indeterminate and pulses (Progress's own recipe); with a number, the fill eases toward each new value. A still bar at a fixed width is indistinguishable from an upload that has stalled."
|
|
175
|
+
}
|
|
176
|
+
],
|
|
151
177
|
a11y: [
|
|
152
178
|
"The full-card trigger is a real stretched `<button>` with an accessible label, not a click handler on a div \u2014 so it is one tab stop, keyboard-activatable, and focus-ringed.",
|
|
153
179
|
"Actions sit above that trigger in the stacking order, which keeps them individually focusable and stops a click on `remove` from also opening the file. Text is only made click-through when a trigger is present, so a filename stays selectable otherwise.",
|
|
@@ -117,11 +117,18 @@ var barChartMeta = {
|
|
|
117
117
|
default: "false for columns; true for horizontal bars",
|
|
118
118
|
description: "The vertical axis."
|
|
119
119
|
},
|
|
120
|
+
{
|
|
121
|
+
name: "motion",
|
|
122
|
+
type: '"full" | "subtle" | "off"',
|
|
123
|
+
default: '"full"',
|
|
124
|
+
description: "How much motion this component uses: `full` (default) inherits the app's policy, `subtle` keeps feedback and drops the flourish, `off` stills it. It can only turn motion down \u2014 it never overrides a reader's reduce-motion setting. The arrival (bars rising in sequence) and the data-change morph are the flourish; the hover dim and gliding cursor are the feedback `subtle` keeps."
|
|
125
|
+
},
|
|
120
126
|
{
|
|
121
127
|
name: "animate",
|
|
122
128
|
type: "boolean",
|
|
123
129
|
default: "true",
|
|
124
|
-
description:
|
|
130
|
+
description: 'Play the arrival once the chart scrolls into view. Reduced motion shows the finished chart. `animate={false}` is `motion="subtle"`.',
|
|
131
|
+
deprecated: 'motion="subtle" (what animate={false} now maps to: no arrival, hover kept) or motion="off"'
|
|
125
132
|
},
|
|
126
133
|
{
|
|
127
134
|
name: "activeIndex",
|
|
@@ -199,19 +206,19 @@ var barChartMeta = {
|
|
|
199
206
|
slot: "arrival",
|
|
200
207
|
recipe: "custom",
|
|
201
208
|
part: "Bar",
|
|
202
|
-
why:
|
|
209
|
+
why: 'Once on screen, bars rise from the baseline in sequence on the emphasized easing (scale from the zero line) and their labels fade in. On the motion tokens; reduced motion shows the finished chart. The arrival is expressive: it plays at the full motion level only, so `motion="subtle"` (or a subtle app policy) shows the finished chart while hover feedback stays.'
|
|
203
210
|
},
|
|
204
211
|
{
|
|
205
212
|
slot: "response",
|
|
206
213
|
recipe: "none",
|
|
207
214
|
part: "Cursor + tooltip",
|
|
208
|
-
why: "The hovered category gets a band behind it and the other bars dim at once \u2014 a lagging highlight reads as a slow chart."
|
|
215
|
+
why: "The hovered category gets a band behind it and the other bars dim at once \u2014 a lagging highlight reads as a slow chart. The dim mixes each bar toward the surface instead of fading it, so gridlines never strike through a dimmed column and stacked segments stay solid. Essential: `subtle` keeps it."
|
|
209
216
|
},
|
|
210
217
|
{
|
|
211
218
|
slot: "transition",
|
|
212
219
|
recipe: "custom",
|
|
213
220
|
part: "Bar",
|
|
214
|
-
why: "Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place."
|
|
221
|
+
why: "Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place. Expressive, like the arrival: under `subtle` new data swaps in place."
|
|
215
222
|
},
|
|
216
223
|
{
|
|
217
224
|
slot: "activity",
|
|
@@ -38,8 +38,10 @@ function BottomNavItem({
|
|
|
38
38
|
"data-active": active ? "" : void 0,
|
|
39
39
|
"aria-current": active ? "page" : void 0,
|
|
40
40
|
className: cn(
|
|
41
|
+
// motion-press: on a phone there is no hover to say the tap landed, and
|
|
42
|
+
// a route change can take a beat to paint — the settle is the receipt.
|
|
41
43
|
// design-lint-disable-next-line raw-control-height -- the WCAG 2.5.5 touch-target floor, deliberately density-independent: a compact register still owes a 44px hit area.
|
|
42
|
-
"relative flex min-h-[44px] min-w-[44px] flex-col items-center justify-center gap-0.5 rounded-lg text-muted-foreground motion-interactive hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring data-[active]:text-foreground",
|
|
44
|
+
"relative flex min-h-[44px] min-w-[44px] flex-col items-center justify-center gap-0.5 rounded-lg text-muted-foreground motion-interactive motion-press hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring data-[active]:text-foreground",
|
|
43
45
|
className
|
|
44
46
|
),
|
|
45
47
|
children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -57,6 +57,38 @@ var bottomNavMeta = {
|
|
|
57
57
|
description: 'A single tab, placed as a child of BottomNav. `label` is the accessible name (aria-label) and optional visible label; `icon` is the glyph node (consumer swaps fill weight for the active state); `active` marks the current route (active ink + aria-current); `badge` overlays a corner dot/count on the icon; `showLabel` shows the label text beneath the icon \u2014 defaults to false (icon-only); turn it on when the icons alone are not self-evident; `render` renders as another element, e.g. render={<Link to="/x" />}.'
|
|
58
58
|
}
|
|
59
59
|
],
|
|
60
|
+
motion: [
|
|
61
|
+
{
|
|
62
|
+
slot: "arrival",
|
|
63
|
+
recipe: "none",
|
|
64
|
+
part: "Root",
|
|
65
|
+
why: "Fixed app chrome, present from the first paint of every screen. Animating it in on navigation would make each route change look like the app restarting."
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
slot: "response",
|
|
69
|
+
recipe: "motion-interactive",
|
|
70
|
+
part: "BottomNavItem",
|
|
71
|
+
why: "The ink steps up on hover and the focus ring eases in, so a pointer or a keyboard on a tablet gets the same answer as every other control in the app."
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
slot: "response",
|
|
75
|
+
recipe: "motion-press",
|
|
76
|
+
part: "BottomNavItem",
|
|
77
|
+
why: "On a phone there is no hover to say a tap landed, and a route can take a beat to paint. The settle on press is the receipt; without it people tap the tab a second time."
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
slot: "transition",
|
|
81
|
+
recipe: "motion-interactive",
|
|
82
|
+
part: "BottomNavItem",
|
|
83
|
+
why: "Changing tab eases the active ink out of one item and into the next. It is a cross-fade, not a travelling indicator: each item owns its colour, so there is no single mark to glide."
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
slot: "activity",
|
|
87
|
+
recipe: "none",
|
|
88
|
+
part: "Root",
|
|
89
|
+
why: "Navigation has no working state of its own. A slow route shows its pending state in the page it is loading, not in the tab bar."
|
|
90
|
+
}
|
|
91
|
+
],
|
|
60
92
|
a11y: [
|
|
61
93
|
"BottomNav renders a <nav> landmark.",
|
|
62
94
|
"Each tab is a 44px minimum touch target with an aria-label.",
|