@sableui/react 0.13.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +172 -0
- package/CONTRIBUTING.md +6 -4
- package/DESIGN.md +10 -3
- package/dist/blocks/approval-block.js +93 -0
- package/dist/blocks/approval-block.meta.js +23 -0
- package/dist/blocks/index.d.ts +27 -1
- package/dist/blocks/index.js +8 -0
- package/dist/blocks/message-reactions-block.js +87 -0
- package/dist/blocks/message-reactions-block.meta.js +23 -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/bubble/bubble.js +105 -5
- package/dist/components/bubble/bubble.meta.js +42 -6
- package/dist/components/bubble/bubble.variants.js +1 -1
- package/dist/components/bubble/index.js +2 -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 +120 -47
- package/dist/components/feedback/feedback.meta.js +55 -4
- package/dist/components/input-group/input-group.meta.js +27 -1
- package/dist/components/like-button/index.js +7 -0
- package/dist/components/like-button/like-button.js +89 -0
- package/dist/components/like-button/like-button.meta.js +132 -0
- 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/reaction-picker/index.js +13 -0
- package/dist/components/reaction-picker/reaction-picker.js +104 -0
- package/dist/components/reaction-picker/reaction-picker.meta.js +156 -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/toast/toast.js +25 -13
- package/dist/components/toast/toast.meta.js +7 -1
- 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 +130 -104
- package/dist/foundations/motion.js +69 -3
- package/dist/icon-sets/reicon.js +11 -1
- package/dist/index.d.ts +907 -104
- package/dist/index.js +6 -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 +5 -0
- package/dist/motion/Burst.js +79 -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 +17 -2
- package/dist/motion/useAutoSize.js +42 -0
- package/dist/motion/useShake.js +25 -0
- package/dist/styles.css +439 -5
- package/llms/blocks/approval-block.md +128 -0
- package/llms/blocks/message-reactions-block.md +116 -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/bubble.md +17 -7
- 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 +23 -3
- package/llms/components/input-group.md +8 -1
- package/llms/components/like-button.md +91 -0
- 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/reaction-picker.md +115 -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/toast.md +2 -1
- 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 +64 -3
- package/llms/reference/utilities.md +26 -1
- package/llms/tasks/reviewing-motion.md +14 -0
- package/llms-full.txt +1337 -69
- package/llms.txt +19 -11
- package/package.json +1 -1
- package/registry.json +3040 -124
- package/targets/ios.json +45 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// src/components/calendar/calendar.tsx
|
|
4
4
|
import { Icon } from "../../icon/index.js";
|
|
5
5
|
import { cn } from "../../lib/cn.js";
|
|
6
|
+
import { motionAttribute, useMotionLevel } from "../../motion/MotionProvider.js";
|
|
6
7
|
import { buttonVariants } from "../button/button.variants.js";
|
|
7
8
|
import {
|
|
8
9
|
DayPicker
|
|
@@ -23,13 +24,18 @@ function Calendar({
|
|
|
23
24
|
showOutsideDays = true,
|
|
24
25
|
components,
|
|
25
26
|
bare = false,
|
|
27
|
+
motion,
|
|
28
|
+
animate,
|
|
26
29
|
...props
|
|
27
30
|
}) {
|
|
31
|
+
const level = useMotionLevel(motion);
|
|
28
32
|
return /* @__PURE__ */ jsx(
|
|
29
33
|
DayPicker,
|
|
30
34
|
{
|
|
31
35
|
"data-slot": "calendar",
|
|
36
|
+
"data-motion": motionAttribute(motion),
|
|
32
37
|
showOutsideDays,
|
|
38
|
+
animate: animate ?? level.expressive,
|
|
33
39
|
className: cn(
|
|
34
40
|
// relative: the nav is absolutely positioned (react-day-picker v9
|
|
35
41
|
// renders it inside `months`, not inside each month), and with no
|
|
@@ -53,7 +59,10 @@ function Calendar({
|
|
|
53
59
|
weekdays: "flex",
|
|
54
60
|
weekday: "flex size-9 items-center justify-center text-caption text-muted-foreground",
|
|
55
61
|
week: "mt-1 flex w-full",
|
|
56
|
-
|
|
62
|
+
// motion-interactive on the cell too: a range's fill is painted on the
|
|
63
|
+
// cells (see range_* below), so without it the band between the
|
|
64
|
+
// endpoints snapped while the endpoint buttons eased.
|
|
65
|
+
day: "p-0 text-center motion-interactive",
|
|
57
66
|
day_button: "inline-flex size-9 items-center justify-center rounded-md text-body-sm motion-interactive outline-none hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring",
|
|
58
67
|
// Range fill lives on the CELL so it forms one continuous bar; only the
|
|
59
68
|
// outer ends are rounded. The selected (primary) endpoint buttons sit on
|
|
@@ -76,6 +85,14 @@ function Calendar({
|
|
|
76
85
|
outside: "[&>button]:text-muted-foreground",
|
|
77
86
|
disabled: "[&>button]:pointer-events-none [&>button]:text-muted-foreground [&>button]:opacity-50",
|
|
78
87
|
hidden: "invisible",
|
|
88
|
+
weeks_after_enter: "sable-month-enter-after",
|
|
89
|
+
weeks_before_enter: "sable-month-enter-before",
|
|
90
|
+
weeks_after_exit: "sable-month-exit-after",
|
|
91
|
+
weeks_before_exit: "sable-month-exit-before",
|
|
92
|
+
caption_after_enter: "sable-caption-enter",
|
|
93
|
+
caption_before_enter: "sable-caption-enter",
|
|
94
|
+
caption_after_exit: "sable-caption-exit",
|
|
95
|
+
caption_before_exit: "sable-caption-exit",
|
|
79
96
|
...classNames
|
|
80
97
|
},
|
|
81
98
|
components: { Chevron: CalendarChevron, ...components },
|
|
@@ -84,6 +84,12 @@ var calendarMeta = {
|
|
|
84
84
|
default: "false",
|
|
85
85
|
description: "Drop the card frame (border, fill, padding) so the grid sits flush inside a surface that already has one, such as a Popover. DatePicker renders its Calendar this way."
|
|
86
86
|
},
|
|
87
|
+
{
|
|
88
|
+
name: "motion",
|
|
89
|
+
type: '"full" | "subtle" | "off"',
|
|
90
|
+
default: '"full"',
|
|
91
|
+
description: "How much motion this component uses: `full` inherits the app's policy, `subtle` keeps feedback and drops the month slide, `off` stills it. It can only turn motion down \u2014 it never overrides a reader's reduce-motion setting."
|
|
92
|
+
},
|
|
87
93
|
{
|
|
88
94
|
name: "className",
|
|
89
95
|
type: "string",
|
|
@@ -114,6 +120,38 @@ var calendarMeta = {
|
|
|
114
120
|
"muted-foreground",
|
|
115
121
|
"ring"
|
|
116
122
|
],
|
|
123
|
+
motion: [
|
|
124
|
+
{
|
|
125
|
+
slot: "arrival",
|
|
126
|
+
recipe: "none",
|
|
127
|
+
part: "Root",
|
|
128
|
+
why: "Inline, it arrives with its form; in a DatePicker the Popover carries the entrance. A grid that animated in by itself would put a second motion on the same opening."
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
slot: "response",
|
|
132
|
+
recipe: "motion-interactive",
|
|
133
|
+
part: "Day",
|
|
134
|
+
why: "Days ease their hover wash, and the month arrows are ghost Buttons on Button's own recipe. Across a seven-column grid the pointer crosses many cells at once, and an eased wash reads as a cursor travelling rather than a grid flashing."
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
slot: "transition",
|
|
138
|
+
recipe: "motion-interactive",
|
|
139
|
+
part: "Day",
|
|
140
|
+
why: "Picking a day eases the primary fill onto it and off the previous one, so a click reads as the selection moving rather than two cells flipping. A range's band is painted on the cells, which carry the same recipe, so the band eases along with its endpoints as the range grows."
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
slot: "activity",
|
|
144
|
+
recipe: "none",
|
|
145
|
+
part: "Root",
|
|
146
|
+
why: "Days are resident data; nothing in the grid loads. Availability fetched per month belongs to the surface around the calendar."
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
slot: "transition",
|
|
150
|
+
recipe: "custom",
|
|
151
|
+
part: "Month",
|
|
152
|
+
why: "Paging months slides the outgoing grid toward the edge it is leaving by and brings the new one in from the other, over a short travel with a fade (react-day-picker's `animate` mode on Sable keyframes in styles.css), so forward and back read as direction rather than a grid swapping in one frame. RDP adds a single class per part, which a recipe utility cannot be; mirrored in RTL. Expressive: Calendar turns `animate` on only at full motion, and an explicit `animate` prop still wins."
|
|
153
|
+
}
|
|
154
|
+
],
|
|
117
155
|
a11y: [
|
|
118
156
|
"react-day-picker manages the roving-tabindex grid, arrow-key navigation, and ARIA roles/labels for the calendar.",
|
|
119
157
|
"Selected days use bg-primary/text-primary-foreground for AA contrast; today is marked with a dot under the number (not a ring, which is reserved for keyboard focus) rather than color alone.",
|
|
@@ -14,7 +14,7 @@ import { Icon } from "../../icon/Icon.js";
|
|
|
14
14
|
import { cn } from "../../lib/cn.js";
|
|
15
15
|
import { PlayPauseGlyph } from "../../lib/glyphs.js";
|
|
16
16
|
import { useResizeObserver } from "../../lib/use-resize-observer.js";
|
|
17
|
-
import {
|
|
17
|
+
import { useMotionLevel } from "../../motion/MotionProvider.js";
|
|
18
18
|
import { Button } from "../button/button.js";
|
|
19
19
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
20
20
|
var CAROUSEL_BREAKPOINTS = [
|
|
@@ -92,9 +92,10 @@ function Carousel({
|
|
|
92
92
|
label,
|
|
93
93
|
className,
|
|
94
94
|
slideClassName,
|
|
95
|
-
arrowClassName
|
|
95
|
+
arrowClassName,
|
|
96
|
+
motion
|
|
96
97
|
}) {
|
|
97
|
-
const { enabled } =
|
|
98
|
+
const { enabled, attribute: motionAttr } = useMotionLevel(motion);
|
|
98
99
|
const slides = Children.toArray(children);
|
|
99
100
|
const count = slides.length;
|
|
100
101
|
const perView = Math.min(
|
|
@@ -278,6 +279,7 @@ function Carousel({
|
|
|
278
279
|
{
|
|
279
280
|
"aria-roledescription": "carousel",
|
|
280
281
|
"aria-label": label ?? "Slides",
|
|
282
|
+
"data-motion": motionAttr,
|
|
281
283
|
className: cn("relative flex flex-col", className),
|
|
282
284
|
onMouseEnter: () => setHovered(true),
|
|
283
285
|
onMouseLeave: () => setHovered(false),
|
|
@@ -401,7 +403,7 @@ function Carousel({
|
|
|
401
403
|
{
|
|
402
404
|
"aria-hidden": true,
|
|
403
405
|
className: cn(
|
|
404
|
-
"h-1.5 rounded-full motion-
|
|
406
|
+
"h-1.5 rounded-full motion-glide",
|
|
405
407
|
i === logical ? "w-4 bg-foreground" : "w-1.5 bg-muted-foreground/40 group-hover/dot:bg-muted-foreground/70"
|
|
406
408
|
)
|
|
407
409
|
}
|
|
@@ -60,6 +60,12 @@ var carouselMeta = {
|
|
|
60
60
|
name: "arrowClassName",
|
|
61
61
|
type: "string",
|
|
62
62
|
description: "Class for both prev/next arrow buttons. Merged through cn(), so a radius or shadow utility replaces the default rounded-full shadow-md rather than stacking with it."
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "motion",
|
|
66
|
+
type: '"full" | "subtle" | "off"',
|
|
67
|
+
default: '"full"',
|
|
68
|
+
description: "How much motion this component uses: `full` (default) inherits the app's policy, `subtle` keeps feedback and drops the glide, `off` stills it. It can only turn motion down \u2014 it never overrides a reader's reduce-motion setting. Under subtle the slides still travel and the active dot snaps; off also cuts between slides and pauses autoplay."
|
|
63
69
|
}
|
|
64
70
|
],
|
|
65
71
|
tokensUsed: [
|
|
@@ -70,6 +76,38 @@ var carouselMeta = {
|
|
|
70
76
|
"foreground",
|
|
71
77
|
"muted-foreground"
|
|
72
78
|
],
|
|
79
|
+
motion: [
|
|
80
|
+
{
|
|
81
|
+
slot: "arrival",
|
|
82
|
+
recipe: "none",
|
|
83
|
+
part: "Root",
|
|
84
|
+
why: "A carousel arrives with its section. Its content already moves on every advance; an entrance on top would make the first view feel like an advance nobody asked for."
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
slot: "response",
|
|
88
|
+
recipe: "motion-interactive",
|
|
89
|
+
part: "Dot",
|
|
90
|
+
why: "The dot's 24px hit area eases its focus ring and the dot brightens under the pointer, which is how a 6px mark tells you it is a button."
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
slot: "transition",
|
|
94
|
+
recipe: "custom",
|
|
95
|
+
part: "Track",
|
|
96
|
+
why: `The slide track's translate is set inline from the measured slide pitch, on --motion-duration-slow and the emphasized easing, and switched off during a drag so the track follows the finger. A recipe cannot carry a measured, drag-interrupted offset. Essential: it is the answer to "next", so it plays under subtle.`
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
slot: "transition",
|
|
100
|
+
recipe: "motion-glide",
|
|
101
|
+
part: "Dot",
|
|
102
|
+
why: 'The active dot stretches to a bar while the one it left shrinks, on one timeline, so the row keeps its length and the mark reads as travelling with the slide. It used to change width unanimated and jumped while the slides beside it glided. Expressive: motion="subtle" snaps it.'
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
slot: "activity",
|
|
106
|
+
recipe: "none",
|
|
107
|
+
part: "Root",
|
|
108
|
+
why: "Autoplay's progress is the slides themselves advancing and the play/pause control; a separate timer animation would put a second clock beside the first."
|
|
109
|
+
}
|
|
110
|
+
],
|
|
73
111
|
a11y: [
|
|
74
112
|
"Region carries aria-roledescription='carousel'; each slide is a group with aria-roledescription='slide' and an 'N of M' label; clones are aria-hidden.",
|
|
75
113
|
"Arrows and dots are real buttons (dots set aria-current); Left/Right arrow keys navigate when the viewport is focused.",
|
|
@@ -92,6 +130,10 @@ var carouselMeta = {
|
|
|
92
130
|
{
|
|
93
131
|
label: "Autoplay",
|
|
94
132
|
code: "<Carousel loop autoplay={4000} counter>{slides}</Carousel>"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
label: "Subtle motion",
|
|
136
|
+
code: '<Carousel loop motion="subtle" label="Gallery">{slides}</Carousel>'
|
|
95
137
|
}
|
|
96
138
|
]
|
|
97
139
|
};
|
|
@@ -6,9 +6,11 @@ import { Icon } from "../../icon/index.js";
|
|
|
6
6
|
import { cn } from "../../lib/cn.js";
|
|
7
7
|
import { useTypeTier } from "../../lib/density.js";
|
|
8
8
|
import { setRef } from "../../lib/refs.js";
|
|
9
|
+
import { motionAttribute } from "../../motion/MotionProvider.js";
|
|
9
10
|
import { Button } from "../button/index.js";
|
|
10
11
|
import { Textarea } from "../textarea/index.js";
|
|
11
12
|
import { chatComposerVariants } from "./chat-composer.variants.js";
|
|
13
|
+
import { useEasedGrowth } from "./use-eased-growth.js";
|
|
12
14
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
13
15
|
function ChatComposer({
|
|
14
16
|
value,
|
|
@@ -31,6 +33,7 @@ function ChatComposer({
|
|
|
31
33
|
stopLabel = "Stop generating",
|
|
32
34
|
footer,
|
|
33
35
|
fieldRef,
|
|
36
|
+
motion,
|
|
34
37
|
className,
|
|
35
38
|
children,
|
|
36
39
|
...props
|
|
@@ -82,9 +85,11 @@ function ChatComposer({
|
|
|
82
85
|
[send, mode]
|
|
83
86
|
);
|
|
84
87
|
const showStop = busy && Boolean(onStop);
|
|
88
|
+
useEasedGrowth(field, text);
|
|
85
89
|
return /* @__PURE__ */ jsxs(
|
|
86
90
|
"form",
|
|
87
91
|
{
|
|
92
|
+
"data-motion": motionAttribute(motion),
|
|
88
93
|
onSubmit: handleSubmit,
|
|
89
94
|
className: cn(chatComposerVariants({ invalid }), className),
|
|
90
95
|
...props,
|
|
@@ -120,6 +125,9 @@ function ChatComposer({
|
|
|
120
125
|
// Inline padding puts the text on the attach glyph's edge: an
|
|
121
126
|
// icon-sm button centres a 1.125rem glyph in --control-sm.
|
|
122
127
|
"min-h-0 max-h-40 resize-none border-0 bg-transparent px-[calc((var(--control-sm)-1.125rem)/2)] py-1 focus-visible:ring-0",
|
|
128
|
+
// Past the cap the field scrolls; the platform bar (stepper arrows on
|
|
129
|
+
// Windows) would poke into the composer's rounded corner.
|
|
130
|
+
"scrollbar-quiet",
|
|
123
131
|
// What you type has to match what it becomes: the field and the
|
|
124
132
|
// Bubble it turns into both read `prose`, so the message does not
|
|
125
133
|
// change size the moment it is sent.
|
|
@@ -143,33 +151,25 @@ function ChatComposer({
|
|
|
143
151
|
leading,
|
|
144
152
|
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
145
153
|
actions,
|
|
146
|
-
|
|
154
|
+
/* @__PURE__ */ jsx(
|
|
147
155
|
Button,
|
|
148
156
|
{
|
|
149
|
-
type: "button",
|
|
150
|
-
variant: "secondary",
|
|
157
|
+
type: showStop ? "button" : "submit",
|
|
158
|
+
variant: showStop ? "secondary" : "primary",
|
|
151
159
|
size: "icon-sm",
|
|
152
|
-
"aria-label": stopLabel,
|
|
153
|
-
|
|
160
|
+
"aria-label": showStop ? stopLabel : sendLabel,
|
|
161
|
+
disabled: showStop ? false : !canSend,
|
|
162
|
+
loading: !showStop && busy,
|
|
163
|
+
onClick: showStop ? (event) => {
|
|
164
|
+
event.preventDefault();
|
|
154
165
|
onStop?.();
|
|
155
166
|
if (!coarse) field.current?.focus();
|
|
156
|
-
},
|
|
157
|
-
children: /* @__PURE__ */
|
|
167
|
+
} : void 0,
|
|
168
|
+
children: /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "motion-icon-swap", children: [
|
|
169
|
+
/* @__PURE__ */ jsx("span", { "data-active": !showStop, className: "inline-flex", children: /* @__PURE__ */ jsx(Icon, { name: "arrow-up" }) }),
|
|
170
|
+
/* @__PURE__ */ jsx("span", { "data-active": showStop, className: "inline-flex", children: /* @__PURE__ */ jsx(Icon, { name: "stop", variant: "solid", className: "text-[0.75em]" }) })
|
|
171
|
+
] })
|
|
158
172
|
}
|
|
159
|
-
) : (
|
|
160
|
-
// Busy without a stop handler: Button's own `loading` swaps the glyph
|
|
161
|
-
// for its Spinner and keeps full colour, rather than a hand-spun icon.
|
|
162
|
-
/* @__PURE__ */ jsx(
|
|
163
|
-
Button,
|
|
164
|
-
{
|
|
165
|
-
type: "submit",
|
|
166
|
-
size: "icon-sm",
|
|
167
|
-
"aria-label": sendLabel,
|
|
168
|
-
disabled: !canSend,
|
|
169
|
-
loading: busy,
|
|
170
|
-
children: /* @__PURE__ */ jsx(Icon, { name: "arrow-up" })
|
|
171
|
-
}
|
|
172
|
-
)
|
|
173
173
|
)
|
|
174
174
|
] }),
|
|
175
175
|
footerContent ? /* @__PURE__ */ jsx("p", { className: cn("px-[calc((var(--control-sm)-1.125rem)/2)] text-muted-foreground", tier.secondary), children: footerContent }) : null,
|
|
@@ -209,6 +209,12 @@ var chatComposerMeta = {
|
|
|
209
209
|
type: "Ref<HTMLTextAreaElement>",
|
|
210
210
|
description: "Ref to the underlying textarea \u2014 for focusing after a send, or on mount, and the extension point for anything that listens to the field. Pass `useCaretTrigger`'s `fieldRef` here for mentions and slash commands (CaretMenu). An Enter another listener already handled (`preventDefault()`) does not send."
|
|
211
211
|
},
|
|
212
|
+
{
|
|
213
|
+
name: "motion",
|
|
214
|
+
type: '"full" | "subtle" | "off"',
|
|
215
|
+
default: '"full"',
|
|
216
|
+
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. Under `subtle` the field snaps to its new height and the send/stop glyphs swap without the morph."
|
|
217
|
+
},
|
|
212
218
|
{
|
|
213
219
|
name: "...form props",
|
|
214
220
|
type: 'Omit<ComponentPropsWithRef<"form">, "onSubmit" | "defaultValue">',
|
|
@@ -230,6 +236,38 @@ var chatComposerMeta = {
|
|
|
230
236
|
"accent",
|
|
231
237
|
"accent-foreground"
|
|
232
238
|
],
|
|
239
|
+
motion: [
|
|
240
|
+
{
|
|
241
|
+
slot: "arrival",
|
|
242
|
+
recipe: "none",
|
|
243
|
+
part: "Root",
|
|
244
|
+
why: "The composer is the fixed foot of a thread, there before anything else is. An entrance would only delay the first keystroke."
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
slot: "response",
|
|
248
|
+
recipe: "motion-interactive",
|
|
249
|
+
part: "Root",
|
|
250
|
+
why: "The surface's focus ring and invalid border ease in, so focusing the field reads as the whole box waking up rather than a frame flicker."
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
slot: "transition",
|
|
254
|
+
recipe: "custom",
|
|
255
|
+
part: "Field",
|
|
256
|
+
why: "As a line breaks or a paste lands, the field eases to its new height (up to its cap) instead of jumping, so the transcript above is visibly pushed rather than teleported. `field-sizing` alone cannot ease \u2014 its height stays `auto` \u2014 so a small hook measures and pins a pixel height. Expressive and short (`fast`): the text itself lands at once, and `subtle` snaps the box."
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
slot: "transition",
|
|
260
|
+
recipe: "motion-icon-swap",
|
|
261
|
+
part: "Send button",
|
|
262
|
+
why: "Send turning into stop is the moment a reply starts, and stop turning back is the moment it is over. One button changes role in place \u2014 the fill eases, the arrow collapses into the square \u2014 so it reads as the same control changing what it does, not a different button appearing under the cursor."
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
slot: "activity",
|
|
266
|
+
recipe: "motion-spin",
|
|
267
|
+
part: "Send button",
|
|
268
|
+
why: "Busy with no stop handler, Button's own loading Spinner replaces the arrow, so a send in flight is not mistaken for a disabled one."
|
|
269
|
+
}
|
|
270
|
+
],
|
|
233
271
|
a11y: [
|
|
234
272
|
"A real `<form>`, so the send button is a submit button and the whole thing behaves as one control to assistive tech and to password managers alike.",
|
|
235
273
|
"Enter never submits while an IME composition is open (`isComposing`), so a CJK reader confirming a candidate word does not mail a fragment of their first sentence.",
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/chat-composer/use-eased-growth.ts
|
|
4
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
5
|
+
import { useIsomorphicLayoutEffect } from "../../lib/use-isomorphic-layout-effect.js";
|
|
6
|
+
var GROW_TRANSITION = "height calc(var(--motion-duration-fast) * var(--motion-expressive, 1)) var(--motion-ease-standard), opacity var(--motion-duration-fast) var(--motion-ease-standard)";
|
|
7
|
+
function useEasedGrowth(ref, value) {
|
|
8
|
+
const mounted = useRef(false);
|
|
9
|
+
const width = useRef(0);
|
|
10
|
+
const fit = useCallback(
|
|
11
|
+
(animate) => {
|
|
12
|
+
const el = ref.current;
|
|
13
|
+
if (!el) return;
|
|
14
|
+
const style = getComputedStyle(el);
|
|
15
|
+
const from = Number.parseFloat(style.height);
|
|
16
|
+
const scrollTop = el.scrollTop;
|
|
17
|
+
el.style.transition = "none";
|
|
18
|
+
el.style.height = "auto";
|
|
19
|
+
if (el.scrollHeight === 0) {
|
|
20
|
+
el.style.height = "";
|
|
21
|
+
el.style.overflowY = "";
|
|
22
|
+
el.style.transition = GROW_TRANSITION;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const content = el.scrollHeight + (Number.parseFloat(style.borderTopWidth) || 0) + (Number.parseFloat(style.borderBottomWidth) || 0);
|
|
26
|
+
const max = Number.parseFloat(style.maxHeight);
|
|
27
|
+
const target = Number.isNaN(max) ? content : Math.min(content, max);
|
|
28
|
+
const capped = content > target + 0.5;
|
|
29
|
+
el.style.overflowY = capped ? "" : "hidden";
|
|
30
|
+
if (animate && Number.isFinite(from)) {
|
|
31
|
+
el.style.height = `${from}px`;
|
|
32
|
+
void el.offsetHeight;
|
|
33
|
+
el.style.transition = GROW_TRANSITION;
|
|
34
|
+
el.style.height = `${target}px`;
|
|
35
|
+
} else {
|
|
36
|
+
el.style.height = `${target}px`;
|
|
37
|
+
void el.offsetHeight;
|
|
38
|
+
el.style.transition = GROW_TRANSITION;
|
|
39
|
+
}
|
|
40
|
+
el.scrollTop = capped ? scrollTop : 0;
|
|
41
|
+
},
|
|
42
|
+
[ref]
|
|
43
|
+
);
|
|
44
|
+
useIsomorphicLayoutEffect(() => {
|
|
45
|
+
fit(mounted.current);
|
|
46
|
+
mounted.current = true;
|
|
47
|
+
}, [value, fit]);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const el = ref.current;
|
|
50
|
+
if (!el || typeof ResizeObserver === "undefined") return;
|
|
51
|
+
width.current = el.clientWidth;
|
|
52
|
+
const ro = new ResizeObserver(() => {
|
|
53
|
+
if (el.clientWidth === width.current) return;
|
|
54
|
+
width.current = el.clientWidth;
|
|
55
|
+
fit(false);
|
|
56
|
+
});
|
|
57
|
+
ro.observe(el);
|
|
58
|
+
let live = true;
|
|
59
|
+
document.fonts?.ready.then(() => {
|
|
60
|
+
if (live) fit(false);
|
|
61
|
+
});
|
|
62
|
+
return () => {
|
|
63
|
+
live = false;
|
|
64
|
+
ro.disconnect();
|
|
65
|
+
};
|
|
66
|
+
}, [ref, fit]);
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
useEasedGrowth
|
|
70
|
+
};
|
|
@@ -9,7 +9,14 @@ function CheckboxMark({ className }) {
|
|
|
9
9
|
Base.Indicator,
|
|
10
10
|
{
|
|
11
11
|
className: cn(
|
|
12
|
-
|
|
12
|
+
// motion-tick, not a scale from 0: the mark wipes in from its leading
|
|
13
|
+
// stroke, so checking reads as a tick being drawn rather than a dot
|
|
14
|
+
// swelling into a check. The dash takes the same wipe, which for a
|
|
15
|
+
// horizontal bar is simply a line drawn left to right. It declares its
|
|
16
|
+
// own transition list, so motion-interactive must not sit here too —
|
|
17
|
+
// two transition-property declarations on one element and the later
|
|
18
|
+
// class silently wins, dropping the clip-path.
|
|
19
|
+
"flex items-center justify-center text-current motion-tick",
|
|
13
20
|
className
|
|
14
21
|
),
|
|
15
22
|
render: (indicatorProps, state) => /* @__PURE__ */ jsx("span", { ...indicatorProps, children: /* @__PURE__ */ jsx(
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
// src/components/checkbox/checkbox.tsx
|
|
2
2
|
import { Checkbox as Base } from "@base-ui/react/checkbox";
|
|
3
3
|
import { cn } from "../../lib/cn.js";
|
|
4
|
+
import { motionAttribute } from "../../motion/MotionProvider.js";
|
|
4
5
|
import { CHECK_BOX } from "../../lib/selection-card.js";
|
|
5
6
|
import { CheckboxMark } from "./checkbox-mark.js";
|
|
6
7
|
import { jsx } from "react/jsx-runtime";
|
|
7
|
-
function Checkbox({ className, children, ...props }) {
|
|
8
|
+
function Checkbox({ className, children, motion, ...props }) {
|
|
8
9
|
return /* @__PURE__ */ jsx(
|
|
9
10
|
Base.Root,
|
|
10
11
|
{
|
|
12
|
+
"data-motion": motionAttribute(motion),
|
|
11
13
|
className: cn(
|
|
12
14
|
CHECK_BOX,
|
|
13
15
|
"text-primary-foreground",
|
|
@@ -91,6 +91,12 @@ var checkboxMeta = {
|
|
|
91
91
|
name: "render",
|
|
92
92
|
type: "ReactElement | (props, state) => ReactElement",
|
|
93
93
|
description: "Compose the root with another element through the Base UI render prop."
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "motion",
|
|
97
|
+
type: '"full" | "subtle" | "off"',
|
|
98
|
+
default: '"full"',
|
|
99
|
+
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. Under `subtle` the tick fades in instead of drawing itself."
|
|
94
100
|
}
|
|
95
101
|
],
|
|
96
102
|
tokensUsed: [
|
|
@@ -100,6 +106,32 @@ var checkboxMeta = {
|
|
|
100
106
|
"primary-foreground",
|
|
101
107
|
"ring"
|
|
102
108
|
],
|
|
109
|
+
motion: [
|
|
110
|
+
{
|
|
111
|
+
slot: "arrival",
|
|
112
|
+
recipe: "none",
|
|
113
|
+
part: "Root",
|
|
114
|
+
why: "It arrives with the form it belongs to. Animating each box in turns a settings list into a title sequence."
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
slot: "response",
|
|
118
|
+
recipe: "motion-interactive",
|
|
119
|
+
part: "Box",
|
|
120
|
+
why: "The box fills with the primary colour as it is checked; easing the fill keeps the mark and the box changing as one event rather than two frames apart."
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
slot: "transition",
|
|
124
|
+
recipe: "motion-tick",
|
|
125
|
+
part: "Mark",
|
|
126
|
+
why: "The check draws itself in from its leading stroke, so checking reads as the box being ticked rather than a glyph appearing. The dash wipes the same way. Unchecking only fades, so clearing a list is quick; under `subtle` checking fades too."
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
slot: "activity",
|
|
130
|
+
recipe: "none",
|
|
131
|
+
part: "Root",
|
|
132
|
+
why: "A checkbox commits instantly. A slow write belongs to the surrounding form's pending state, not the control."
|
|
133
|
+
}
|
|
134
|
+
],
|
|
103
135
|
a11y: [
|
|
104
136
|
"Base UI renders a hidden native <input type=checkbox> for keyboard, focus, and form semantics.",
|
|
105
137
|
"Space toggles; focus-visible draws a 2px ring in the --ring token.",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Checkbox as Base } from "@base-ui/react/checkbox";
|
|
3
3
|
import { CheckboxGroup as BaseGroup } from "@base-ui/react/checkbox-group";
|
|
4
4
|
import { cn } from "../../lib/cn.js";
|
|
5
|
+
import { motionAttribute } from "../../motion/MotionProvider.js";
|
|
5
6
|
import {
|
|
6
7
|
CHECK_BOX,
|
|
7
8
|
SELECTION_CARD_GLYPH,
|
|
@@ -52,6 +53,7 @@ function CheckboxCard({
|
|
|
52
53
|
description,
|
|
53
54
|
icon,
|
|
54
55
|
tone = "neutral",
|
|
56
|
+
motion,
|
|
55
57
|
...props
|
|
56
58
|
}) {
|
|
57
59
|
const t = checkboxCardToneClasses[tone];
|
|
@@ -59,6 +61,7 @@ function CheckboxCard({
|
|
|
59
61
|
return /* @__PURE__ */ jsxs(
|
|
60
62
|
Base.Root,
|
|
61
63
|
{
|
|
64
|
+
"data-motion": motionAttribute(motion),
|
|
62
65
|
className: cn(
|
|
63
66
|
SELECTION_CARD_SHELL,
|
|
64
67
|
icon != null ? SELECTION_CARD_LAYOUT.tile : SELECTION_CARD_LAYOUT.row,
|
|
@@ -92,6 +92,12 @@ var checkboxCardMeta = {
|
|
|
92
92
|
name: "checked / onCheckedChange / defaultChecked / disabled",
|
|
93
93
|
type: "Base UI Checkbox.Root props",
|
|
94
94
|
description: "Standard checkbox state. Without a group-owned value, each card is independent \u2014 drive checked/onCheckedChange per item for multi-select."
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "motion",
|
|
98
|
+
type: '"full" | "subtle" | "off"',
|
|
99
|
+
default: '"full"',
|
|
100
|
+
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. Under `subtle` the tick fades in instead of drawing itself."
|
|
95
101
|
}
|
|
96
102
|
],
|
|
97
103
|
subcomponents: [
|
|
@@ -124,6 +130,32 @@ var checkboxCardMeta = {
|
|
|
124
130
|
"tone-warning",
|
|
125
131
|
"tone-warning-foreground"
|
|
126
132
|
],
|
|
133
|
+
motion: [
|
|
134
|
+
{
|
|
135
|
+
slot: "arrival",
|
|
136
|
+
recipe: "none",
|
|
137
|
+
part: "Root",
|
|
138
|
+
why: "It arrives with the form it belongs to. Animating each box in turns a settings list into a title sequence."
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
slot: "response",
|
|
142
|
+
recipe: "motion-interactive",
|
|
143
|
+
part: "Box",
|
|
144
|
+
why: "The box fills with the primary colour as it is checked; easing the fill keeps the mark and the box changing as one event rather than two frames apart."
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
slot: "transition",
|
|
148
|
+
recipe: "motion-tick",
|
|
149
|
+
part: "Mark",
|
|
150
|
+
why: "The check draws itself in from its leading stroke, so checking reads as the box being ticked rather than a glyph appearing. The dash wipes the same way. Unchecking only fades, so clearing a list is quick; under `subtle` checking fades too."
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
slot: "activity",
|
|
154
|
+
recipe: "none",
|
|
155
|
+
part: "Root",
|
|
156
|
+
why: "A checkbox commits instantly. A slow write belongs to the surrounding form's pending state, not the control."
|
|
157
|
+
}
|
|
158
|
+
],
|
|
127
159
|
a11y: [
|
|
128
160
|
"Base UI provides the checkbox role, aria-checked, hidden native input, keyboard toggle, and data-[checked]/data-disabled state.",
|
|
129
161
|
"The whole card is the hit target; focus draws a 2px outline outside the card (not a ring, so the checked tone ring survives focus); Space toggles the focused card.",
|
|
@@ -7,6 +7,15 @@ import { Button } from "../button/button.js";
|
|
|
7
7
|
import { cn } from "../../lib/cn.js";
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
var CodeHighlightContext = createContext(null);
|
|
10
|
+
function CopyGlyph({ copied }) {
|
|
11
|
+
return /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "motion-icon-swap", children: [
|
|
12
|
+
/* @__PURE__ */ jsx("span", { "data-active": !copied, className: "inline-flex", children: /* @__PURE__ */ jsx(Icon, { name: "copy" }) }),
|
|
13
|
+
/* @__PURE__ */ jsx("span", { "data-active": copied, className: "inline-flex", children: /* @__PURE__ */ jsx(Icon, { name: "check" }) })
|
|
14
|
+
] });
|
|
15
|
+
}
|
|
16
|
+
function CopiedStatus({ copied }) {
|
|
17
|
+
return /* @__PURE__ */ jsx("span", { role: "status", className: "sr-only", children: copied ? "Copied" : "" });
|
|
18
|
+
}
|
|
10
19
|
function CodeHighlightProvider({ highlight, children }) {
|
|
11
20
|
return /* @__PURE__ */ jsx(CodeHighlightContext.Provider, { value: highlight, children });
|
|
12
21
|
}
|
|
@@ -39,7 +48,8 @@ function CodeBlock({
|
|
|
39
48
|
] }),
|
|
40
49
|
code
|
|
41
50
|
] }),
|
|
42
|
-
showCopy ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Copy command", onClick: copy, className: "shrink-0 text-muted-foreground", children: /* @__PURE__ */ jsx(
|
|
51
|
+
showCopy ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Copy command", onClick: copy, className: "shrink-0 text-muted-foreground", children: /* @__PURE__ */ jsx(CopyGlyph, { copied }) }) : null,
|
|
52
|
+
showCopy ? /* @__PURE__ */ jsx(CopiedStatus, { copied }) : null
|
|
43
53
|
]
|
|
44
54
|
}
|
|
45
55
|
);
|
|
@@ -57,7 +67,7 @@ function CodeBlock({
|
|
|
57
67
|
children: [
|
|
58
68
|
hasHeader ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-border px-4 py-2", children: [
|
|
59
69
|
/* @__PURE__ */ jsx("span", { className: "text-mono-caption text-muted-foreground", children: filename }),
|
|
60
|
-
showCopy ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Copy code", onClick: copy, className: "text-muted-foreground", children: /* @__PURE__ */ jsx(
|
|
70
|
+
showCopy ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Copy code", onClick: copy, className: "text-muted-foreground", children: /* @__PURE__ */ jsx(CopyGlyph, { copied }) }) : null
|
|
61
71
|
] }) : null,
|
|
62
72
|
/* @__PURE__ */ jsx(
|
|
63
73
|
"pre",
|
|
@@ -72,7 +82,8 @@ function CodeBlock({
|
|
|
72
82
|
children: /* @__PURE__ */ jsx("code", { children: highlighter ? highlighter(code) : code })
|
|
73
83
|
}
|
|
74
84
|
),
|
|
75
|
-
copyColumn ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Copy code", onClick: copy, className: "m-2 ms-0 shrink-0 text-muted-foreground", children: /* @__PURE__ */ jsx(
|
|
85
|
+
copyColumn ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Copy code", onClick: copy, className: "m-2 ms-0 shrink-0 text-muted-foreground", children: /* @__PURE__ */ jsx(CopyGlyph, { copied }) }) : null,
|
|
86
|
+
showCopy ? /* @__PURE__ */ jsx(CopiedStatus, { copied }) : null
|
|
76
87
|
]
|
|
77
88
|
}
|
|
78
89
|
);
|
|
@@ -79,9 +79,36 @@ var codeBlockMeta = {
|
|
|
79
79
|
}
|
|
80
80
|
],
|
|
81
81
|
tokensUsed: ["border", "muted", "muted-foreground", "foreground"],
|
|
82
|
+
motion: [
|
|
83
|
+
{
|
|
84
|
+
slot: "arrival",
|
|
85
|
+
recipe: "none",
|
|
86
|
+
part: "Root",
|
|
87
|
+
why: "Code is reading content and arrives with the page or message it belongs to. An entrance of its own would delay the text someone came to copy."
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
slot: "response",
|
|
91
|
+
recipe: "motion-interactive",
|
|
92
|
+
part: "Copy button",
|
|
93
|
+
why: "The copy button is a quiet ghost icon in the corner of a block. Its hover fill is what tells the pointer it is a control and not decoration."
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
slot: "transition",
|
|
97
|
+
recipe: "motion-icon-swap",
|
|
98
|
+
part: "Copy glyph",
|
|
99
|
+
why: "The copy mark turning into a check is the only proof the clipboard took the text. Snapped between frames it was easy to miss at the edge of a block; the swap reads as one mark being replaced."
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
slot: "activity",
|
|
103
|
+
recipe: "none",
|
|
104
|
+
part: "Root",
|
|
105
|
+
why: "Copying is instant. There is no in-between worth showing, and a spinner for a clipboard write would read as a slow one."
|
|
106
|
+
}
|
|
107
|
+
],
|
|
82
108
|
a11y: [
|
|
83
109
|
"Code renders inside a <pre><code> for correct semantics and screen-reader handling.",
|
|
84
|
-
"Copy is the ghost icon Button with `useCopy`, which guards clipboard access for SSR safety."
|
|
110
|
+
"Copy is the ghost icon Button with `useCopy`, which guards clipboard access for SSR safety.",
|
|
111
|
+
'A successful copy is announced as "Copied" through a polite role=status region that is always mounted; the button keeps its label, which names what it does. Both glyphs are aria-hidden.'
|
|
85
112
|
],
|
|
86
113
|
examples: [
|
|
87
114
|
{
|