@sableui/react 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +131 -0
- package/DESIGN.md +3 -1
- package/bin/sable-lint.mjs +5 -1
- package/dist/{_contract-CkqXDVww.d.ts → _contract-CDa9-NdB.d.ts} +47 -1
- package/dist/blocks/index.d.ts +12 -1
- package/dist/blocks/index.js +4 -0
- package/dist/blocks/message-reactions-block.js +87 -0
- package/dist/blocks/message-reactions-block.meta.js +23 -0
- package/dist/blocks/wallet-block.js +2 -2
- package/dist/components/app-shell/app-shell-frame.js +16 -0
- package/dist/components/app-shell/app-shell-main.js +12 -0
- package/dist/components/app-shell/app-shell.js +28 -4
- package/dist/components/app-shell/app-shell.meta.js +88 -4
- package/dist/components/area-chart/area-chart.meta.js +6 -1
- package/dist/components/attachment/attachment.meta.js +2 -1
- package/dist/components/attachment/attachment.variants.js +1 -1
- package/dist/components/badge/badge.meta.js +8 -1
- package/dist/components/badge/badge.variants.js +7 -7
- package/dist/components/banner/banner.meta.js +6 -1
- package/dist/components/banner/banner.variants.js +5 -5
- package/dist/components/bar-chart/bar-chart.meta.js +6 -1
- 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/caret-menu/caret-menu.js +5 -6
- package/dist/components/chat-composer/chat-composer.meta.js +2 -2
- package/dist/components/chat-composer/chat-composer.variants.js +1 -1
- package/dist/components/color-field/color-field.js +6 -8
- package/dist/components/combobox/combobox.js +27 -5
- package/dist/components/combobox/combobox.meta.js +10 -2
- package/dist/components/command-menu/command-menu.js +14 -3
- package/dist/components/command-menu/command-menu.meta.js +9 -1
- package/dist/components/date-picker/date-picker.meta.js +2 -2
- package/dist/components/feedback/feedback.js +9 -2
- package/dist/components/feedback/feedback.meta.js +8 -1
- package/dist/components/gradient-art/gradient-art.js +8 -5
- package/dist/components/gradient-art/gradient-art.meta.js +35 -2
- package/dist/components/input/input.meta.js +11 -3
- package/dist/components/input-group/input-group.js +1 -1
- package/dist/components/input-group/input-group.meta.js +1 -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 +6 -1
- package/dist/components/menu/menu.js +1 -1
- package/dist/components/meter/meter.js +53 -7
- package/dist/components/meter/meter.meta.js +51 -4
- package/dist/components/meter/meter.variants.js +43 -7
- package/dist/components/note/note.meta.js +5 -1
- package/dist/components/note/note.variants.js +4 -4
- package/dist/components/number-field/number-field.meta.js +2 -2
- package/dist/components/otp-field/otp-field.meta.js +2 -2
- package/dist/components/panel-group/panel-group.js +5 -8
- package/dist/components/pie-chart/pie-chart.meta.js +6 -1
- package/dist/components/radar-chart/radar-chart.meta.js +6 -1
- package/dist/components/radial-chart/radial-chart.meta.js +6 -1
- 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/search-input/search-input.meta.js +11 -3
- package/dist/components/segmented-bar/segmented-bar.meta.js +8 -1
- package/dist/components/segmented-bar/segmented-bar.variants.js +7 -7
- package/dist/components/select/select.js +1 -1
- package/dist/components/select/select.meta.js +2 -2
- package/dist/components/sheet/sheet.js +5 -4
- package/dist/components/sidebar/sidebar.js +16 -1
- package/dist/components/sidebar/sidebar.meta.js +7 -2
- package/dist/components/sparkline/sparkline.meta.js +8 -1
- package/dist/components/status-dot/status-dot.meta.js +8 -1
- package/dist/components/status-dot/status-dot.variants.js +7 -7
- package/dist/components/stepper/stepper.meta.js +2 -1
- package/dist/components/stepper/stepper.variants.js +1 -1
- package/dist/components/table/table.meta.js +4 -4
- package/dist/components/textarea/textarea.js +5 -0
- package/dist/components/textarea/textarea.meta.js +3 -3
- package/dist/components/toast/toast.js +25 -13
- package/dist/components/toast/toast.meta.js +7 -1
- package/dist/file-hashes.json +112 -100
- package/dist/foundations/accessibility.js +5 -0
- package/dist/foundations/motion.js +21 -1
- package/dist/icon-sets/reicon.js +11 -1
- package/dist/index.d.ts +425 -84
- package/dist/index.js +11 -1
- package/dist/lib/chart/types.js +8 -8
- package/dist/lib/chart/use-arrival.js +5 -4
- package/dist/lib/field-shell.js +1 -1
- package/dist/lib/highlight-glide.js +6 -6
- package/dist/lib/scrub-gesture.js +6 -6
- package/dist/lib/taxonomy.js +2 -0
- package/dist/lib/use-caret-trigger.js +11 -18
- package/dist/lib/use-match-highlight.js +69 -0
- package/dist/lib/use-smooth-wheel.js +10 -8
- package/dist/motion/Burst.js +79 -0
- package/dist/motion/index.js +2 -0
- package/dist/presets/index.d.ts +13 -2
- package/dist/presets/index.js +17 -1
- package/dist/styles.css +241 -0
- package/dist/{theme-provider-CdSicAad.d.ts → theme-provider-BydfM5Z0.d.ts} +1 -1
- package/dist/themes/_contract.js +14 -0
- package/dist/themes/atelier/atelier.css +18 -0
- package/dist/themes/atelier/index.d.ts +1 -1
- package/dist/themes/contrast.js +48 -1
- package/dist/themes/ember/ember.css +18 -0
- package/dist/themes/ember/index.d.ts +1 -1
- package/dist/themes/index.d.ts +2 -2
- package/dist/themes/meridian/index.d.ts +1 -1
- package/dist/themes/meridian/meridian.css +18 -0
- package/dist/themes/poppy/index.d.ts +1 -1
- package/dist/themes/poppy/poppy.css +18 -0
- package/dist/themes/prism/index.d.ts +1 -1
- package/dist/themes/prism/prism.css +18 -0
- package/dist/themes/sable/index.d.ts +1 -1
- package/dist/themes/sable/sable.css +18 -0
- package/dist/themes/verdant/index.d.ts +1 -1
- package/dist/themes/verdant/verdant.css +18 -0
- package/llms/blocks/message-reactions-block.md +116 -0
- package/llms/blocks/wallet-block.md +5 -4
- package/llms/components/app-shell.md +72 -3
- package/llms/components/area-chart.md +1 -1
- package/llms/components/attachment.md +1 -1
- package/llms/components/badge.md +1 -1
- package/llms/components/banner.md +1 -1
- package/llms/components/bar-chart.md +1 -1
- package/llms/components/bubble.md +17 -7
- package/llms/components/chat-composer.md +1 -1
- package/llms/components/combobox.md +2 -1
- package/llms/components/command-menu.md +3 -2
- package/llms/components/date-picker.md +1 -1
- package/llms/components/feedback.md +2 -1
- package/llms/components/gradient-art.md +28 -1
- package/llms/components/input-group.md +1 -1
- package/llms/components/input.md +4 -2
- package/llms/components/like-button.md +91 -0
- package/llms/components/line-chart.md +1 -1
- package/llms/components/meter.md +33 -4
- package/llms/components/note.md +1 -1
- package/llms/components/number-field.md +1 -1
- package/llms/components/otp-field.md +1 -1
- package/llms/components/pie-chart.md +1 -1
- package/llms/components/radar-chart.md +1 -1
- package/llms/components/radial-chart.md +1 -1
- package/llms/components/reaction-picker.md +115 -0
- package/llms/components/search-input.md +20 -2
- package/llms/components/segmented-bar.md +1 -1
- package/llms/components/select.md +1 -1
- package/llms/components/sidebar.md +3 -2
- package/llms/components/sparkline.md +1 -1
- package/llms/components/status-dot.md +1 -1
- package/llms/components/stepper.md +1 -1
- package/llms/components/table.md +2 -2
- package/llms/components/textarea.md +2 -2
- package/llms/components/toast.md +2 -1
- package/llms/foundations/accessibility.md +1 -0
- package/llms/foundations/motion.md +18 -2
- package/llms/reference/token-intent.md +22 -2
- package/llms/reference/utilities.md +9 -3
- package/llms-full.txt +596 -61
- package/llms.txt +10 -7
- package/package.json +1 -1
- package/registry.json +968 -68
- package/scripts/lint-rules.mjs +6 -3
- package/targets/ios.json +18 -0
|
@@ -15,7 +15,12 @@ var appShellMeta = {
|
|
|
15
15
|
"dock",
|
|
16
16
|
"player bar",
|
|
17
17
|
"now playing",
|
|
18
|
-
"persistent"
|
|
18
|
+
"persistent",
|
|
19
|
+
"inset",
|
|
20
|
+
"inset frame",
|
|
21
|
+
"floating panel",
|
|
22
|
+
"rounded content",
|
|
23
|
+
"scroll container"
|
|
19
24
|
],
|
|
20
25
|
usage: {
|
|
21
26
|
description: "Use AppShell as the outer product frame for authenticated or app-like experiences. It coordinates responsive chrome slots and guarantees a skip link plus main landmark; put route content inside the children and let AppHeader, Sidebar, BottomNav, and Banner own their own semantics.",
|
|
@@ -36,10 +41,23 @@ var appShellMeta = {
|
|
|
36
41
|
guidance: "do",
|
|
37
42
|
description: "Wrap EVERY return of a route in its own Page or Region \u2014 including early returns for loading, empty and error states. AppShell's <main> deliberately carries no padding (it is a frame, and padding belongs to the region that chose the width), so a route that returns a bare spinner or error renders flush against the chrome. This is the most common conversion bug: the happy path gets a Page, the three early returns do not."
|
|
38
43
|
},
|
|
44
|
+
{
|
|
45
|
+
guidance: "do",
|
|
46
|
+
description: `Use frame="inset" for dense, multi-pane product apps \u2014 a CRM, an issue tracker, an AI workspace \u2014 where framing the content as one raised panel on the rail's ground helps it read as the workspace and the rail as chrome. Keep the Sidebar on surface="rail" so its tint is the ground. Inset moves the page scroll from the window to <main id="main-content"> on desktop: point scroll listeners, scroll restoration and scroll-to-top at that element, not window.`
|
|
47
|
+
},
|
|
39
48
|
{
|
|
40
49
|
guidance: "dont",
|
|
41
50
|
description: "Nest AppShell inside route cards or panels. It is page chrome, not local content."
|
|
42
51
|
},
|
|
52
|
+
{
|
|
53
|
+
guidance: "dont",
|
|
54
|
+
description: "Wrap a route in its own h-full overflow-y-auto box inside an inset shell. main is already the page's scroll container; a second one traps the scrollbar inside a smaller box and breaks sticky headers and the skip link's scroll target. Scroll a genuinely separate pane (a list beside a detail view) with Scroller.",
|
|
55
|
+
instead: ["Scroller"]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
guidance: "dont",
|
|
59
|
+
description: 'Combine frame="inset" with a full-width header bar above the rail. The inset ground is the rail running the full height of the window; a bar across the top cuts it off. Use the flush frame with Sidebar surface="page" for that layout.'
|
|
60
|
+
},
|
|
43
61
|
{
|
|
44
62
|
guidance: "dont",
|
|
45
63
|
description: "Put desktop-only navigation in the mobile header slot or mobile-only controls in the sidebar slot."
|
|
@@ -64,7 +82,7 @@ var appShellMeta = {
|
|
|
64
82
|
{
|
|
65
83
|
name: "Main",
|
|
66
84
|
required: true,
|
|
67
|
-
description:
|
|
85
|
+
description: 'The route content landmark with id main-content. With frame="inset", from lg it is a rounded, bordered panel that scrolls inside itself.'
|
|
68
86
|
},
|
|
69
87
|
{
|
|
70
88
|
name: "Bottom nav",
|
|
@@ -113,6 +131,12 @@ var appShellMeta = {
|
|
|
113
131
|
name: "overlays",
|
|
114
132
|
type: "ReactNode",
|
|
115
133
|
description: "Floating overlays \u2014 a message launcher, FAB, or modal mount points."
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "frame",
|
|
137
|
+
type: '"flush" | "inset"',
|
|
138
|
+
default: '"flush"',
|
|
139
|
+
description: `How the desktop frame (lg and up) holds the content; below lg both are the same mobile layout with page scroll. "flush": main sits on the page beside the rail and the document scrolls. "inset": the shell fills the viewport and paints the shell-ground token (one step off the rail's surface, tunable with --theme-shell-ground) behind the rail and around the panel; main becomes a rounded-lg panel with a hairline border and shadow-sm, inset by a spacing step from the window edges, that scrolls inside itself (scrollbar-quiet) while the rail stays put. The Sidebar in the sidebar slot drops its right border and fills the row. main stays the #main-content target (tabIndex -1, so the skip link lands focus in the scroller) and the inline-size query container; sticky children stick to the panel's top; banners and the dock stay in flow, so the panel's bottom gap sits above a dock. With Sidebar surface="page" the ground is the page colour and the panel's border alone lifts it.`
|
|
116
140
|
}
|
|
117
141
|
],
|
|
118
142
|
subcomponents: [
|
|
@@ -126,9 +150,10 @@ var appShellMeta = {
|
|
|
126
150
|
"Renders a skip link to #main-content as the first focusable element.",
|
|
127
151
|
'Wraps content in a <main id="main-content"> landmark.',
|
|
128
152
|
"Slot components own their own ARIA (nav landmarks, labels).",
|
|
129
|
-
"The dock follows <main> in DOM order, so keyboard and screen-reader users reach the player after the page content, not before it."
|
|
153
|
+
"The dock follows <main> in DOM order, so keyboard and screen-reader users reach the player after the page content, not before it.",
|
|
154
|
+
'With frame="inset", <main> is the desktop scroll container and takes tabIndex -1: the skip link moves focus into it, so PageDown and the arrow keys scroll the content, without adding a tab stop.'
|
|
130
155
|
],
|
|
131
|
-
tokensUsed: ["background", "foreground", "primary", "primary-foreground"],
|
|
156
|
+
tokensUsed: ["background", "foreground", "primary", "primary-foreground", "sidebar", "shell-ground", "border"],
|
|
132
157
|
examples: [
|
|
133
158
|
{
|
|
134
159
|
label: "Full frame",
|
|
@@ -154,6 +179,65 @@ var appShellMeta = {
|
|
|
154
179
|
<Region max="feed">
|
|
155
180
|
<PageHeader title="Home" description="Latest posts from people you follow." />
|
|
156
181
|
</Region>
|
|
182
|
+
</AppShell>`
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
label: "Inset frame",
|
|
186
|
+
code: `<AppShell
|
|
187
|
+
frame="inset"
|
|
188
|
+
header={<AppHeader title="Pipeline" />}
|
|
189
|
+
sidebar={
|
|
190
|
+
<Sidebar>
|
|
191
|
+
<SidebarHeader>
|
|
192
|
+
<SidebarBrand mark={<Icon name="gem" variant="solid" />} label="Relay" render={<Link to="/" />} />
|
|
193
|
+
</SidebarHeader>
|
|
194
|
+
<SidebarNav>
|
|
195
|
+
<SidebarItem icon={<Icon name="inbox" />} label="Inbox" render={<Link to="/inbox" />} />
|
|
196
|
+
<SidebarItem icon={<Icon name="user" />} label="People" active render={<Link to="/people" />} />
|
|
197
|
+
<SidebarItem icon={<Icon name="folder" />} label="Companies" render={<Link to="/companies" />} />
|
|
198
|
+
</SidebarNav>
|
|
199
|
+
</Sidebar>
|
|
200
|
+
}
|
|
201
|
+
bottomNav={
|
|
202
|
+
<BottomNav>
|
|
203
|
+
<BottomNavItem icon={<Icon name="inbox" />} label="Inbox" render={<Link to="/inbox" />} />
|
|
204
|
+
<BottomNavItem icon={<Icon name="user" />} label="People" active render={<Link to="/people" />} />
|
|
205
|
+
<BottomNavItem icon={<Icon name="folder" />} label="Companies" render={<Link to="/companies" />} />
|
|
206
|
+
</BottomNav>
|
|
207
|
+
}
|
|
208
|
+
>
|
|
209
|
+
<Page max="wide" gap={4} className="py-6">
|
|
210
|
+
<PageHeader
|
|
211
|
+
title="People"
|
|
212
|
+
description="Everyone in your pipeline, newest first."
|
|
213
|
+
actions={<Button><Icon name="plus" />Add person</Button>}
|
|
214
|
+
/>
|
|
215
|
+
<Card>
|
|
216
|
+
<CardContent flush>
|
|
217
|
+
<Table>
|
|
218
|
+
<TableHeader>
|
|
219
|
+
<TableRow>
|
|
220
|
+
<TableHead>Name</TableHead>
|
|
221
|
+
<TableHead>Company</TableHead>
|
|
222
|
+
<TableHead numeric>Deals</TableHead>
|
|
223
|
+
</TableRow>
|
|
224
|
+
</TableHeader>
|
|
225
|
+
<TableBody>
|
|
226
|
+
<TableRow>
|
|
227
|
+
<TableCell>Ada Lovelace</TableCell>
|
|
228
|
+
<TableCell>Northwind</TableCell>
|
|
229
|
+
<TableCell numeric>4</TableCell>
|
|
230
|
+
</TableRow>
|
|
231
|
+
<TableRow>
|
|
232
|
+
<TableCell>Grace Hopper</TableCell>
|
|
233
|
+
<TableCell>Harbor Labs</TableCell>
|
|
234
|
+
<TableCell numeric>2</TableCell>
|
|
235
|
+
</TableRow>
|
|
236
|
+
</TableBody>
|
|
237
|
+
</Table>
|
|
238
|
+
</CardContent>
|
|
239
|
+
</Card>
|
|
240
|
+
</Page>
|
|
157
241
|
</AppShell>`
|
|
158
242
|
},
|
|
159
243
|
{
|
|
@@ -176,7 +176,12 @@ var areaChartMeta = {
|
|
|
176
176
|
"popover",
|
|
177
177
|
"popover-foreground",
|
|
178
178
|
"foreground",
|
|
179
|
-
"background"
|
|
179
|
+
"background",
|
|
180
|
+
"tone-warning-mark",
|
|
181
|
+
"tone-info-mark",
|
|
182
|
+
"tone-1-mark",
|
|
183
|
+
"tone-2-mark",
|
|
184
|
+
"tone-3-mark"
|
|
180
185
|
],
|
|
181
186
|
motion: [
|
|
182
187
|
{
|
|
@@ -13,7 +13,7 @@ var attachmentVariants = cva(
|
|
|
13
13
|
status: {
|
|
14
14
|
idle: "border-border bg-card",
|
|
15
15
|
uploading: "border-border bg-card",
|
|
16
|
-
error: "border-destructive/40 bg-destructive/5"
|
|
16
|
+
error: "border-destructive-mark/40 bg-destructive-mark/5"
|
|
17
17
|
},
|
|
18
18
|
interactive: {
|
|
19
19
|
true: "hover:border-border-strong",
|
|
@@ -137,7 +137,14 @@ var badgeMeta = {
|
|
|
137
137
|
"tone-info-foreground",
|
|
138
138
|
"success-foreground",
|
|
139
139
|
"tone-warning-foreground",
|
|
140
|
-
"destructive-foreground"
|
|
140
|
+
"destructive-foreground",
|
|
141
|
+
"destructive-mark",
|
|
142
|
+
"success-mark",
|
|
143
|
+
"tone-warning-mark",
|
|
144
|
+
"tone-info-mark",
|
|
145
|
+
"tone-1-mark",
|
|
146
|
+
"tone-2-mark",
|
|
147
|
+
"tone-3-mark"
|
|
141
148
|
],
|
|
142
149
|
a11y: [
|
|
143
150
|
"Badges are static text by default; do not rely on color alone for meaning.",
|
|
@@ -21,13 +21,13 @@ var MATRIX = {
|
|
|
21
21
|
// mixed from them fell under 4.5:1 (Meridian dark destructive, 3.8:1).
|
|
22
22
|
soft: {
|
|
23
23
|
neutral: "bg-secondary text-secondary-foreground",
|
|
24
|
-
"3": "bg-tone-3/15 text-[color-mix(in_oklch,var(--tone-3)_75%,var(--foreground))]",
|
|
25
|
-
"2": "bg-tone-2/15 text-[color-mix(in_oklch,var(--tone-2)_75%,var(--foreground))]",
|
|
26
|
-
"1": "bg-tone-1/15 text-[color-mix(in_oklch,var(--tone-1)_75%,var(--foreground))]",
|
|
27
|
-
info: "bg-tone-info/15 text-[color-mix(in_oklch,var(--tone-info-text)_75%,var(--foreground))]",
|
|
28
|
-
success: "bg-success/15 text-[color-mix(in_oklch,var(--success-text)_75%,var(--foreground))]",
|
|
29
|
-
warning: "bg-tone-warning/15 text-[color-mix(in_oklch,var(--tone-warning-text)_75%,var(--foreground))]",
|
|
30
|
-
destructive: "bg-destructive/15 text-[color-mix(in_oklch,var(--destructive-text)_75%,var(--foreground))]",
|
|
24
|
+
"3": "bg-tone-3-mark/15 text-[color-mix(in_oklch,var(--tone-3)_75%,var(--foreground))]",
|
|
25
|
+
"2": "bg-tone-2-mark/15 text-[color-mix(in_oklch,var(--tone-2)_75%,var(--foreground))]",
|
|
26
|
+
"1": "bg-tone-1-mark/15 text-[color-mix(in_oklch,var(--tone-1)_75%,var(--foreground))]",
|
|
27
|
+
info: "bg-tone-info-mark/15 text-[color-mix(in_oklch,var(--tone-info-text)_75%,var(--foreground))]",
|
|
28
|
+
success: "bg-success-mark/15 text-[color-mix(in_oklch,var(--success-text)_75%,var(--foreground))]",
|
|
29
|
+
warning: "bg-tone-warning-mark/15 text-[color-mix(in_oklch,var(--tone-warning-text)_75%,var(--foreground))]",
|
|
30
|
+
destructive: "bg-destructive-mark/15 text-[color-mix(in_oklch,var(--destructive-text)_75%,var(--foreground))]",
|
|
31
31
|
...families("soft")
|
|
32
32
|
},
|
|
33
33
|
outline: {
|
|
@@ -131,7 +131,12 @@ var bannerMeta = {
|
|
|
131
131
|
"tone-1",
|
|
132
132
|
"tone-1-foreground",
|
|
133
133
|
"destructive",
|
|
134
|
-
"destructive-foreground"
|
|
134
|
+
"destructive-foreground",
|
|
135
|
+
"destructive-mark",
|
|
136
|
+
"success-mark",
|
|
137
|
+
"tone-warning-mark",
|
|
138
|
+
"tone-info-mark",
|
|
139
|
+
"tone-1-mark"
|
|
135
140
|
],
|
|
136
141
|
a11y: [
|
|
137
142
|
"Static banners render as a labelled region (role=region); set `live` to announce dynamic status bars (role=status / role=alert) when they appear or change.",
|
|
@@ -20,11 +20,11 @@ var bannerVariants = cva(
|
|
|
20
20
|
compoundVariants: [
|
|
21
21
|
// Soft — a quiet tint of the tone (the default announcement look).
|
|
22
22
|
{ tone: "neutral", appearance: "soft", class: "bg-secondary text-secondary-foreground" },
|
|
23
|
-
{ tone: "info", appearance: "soft", class: "bg-tone-info/15 text-foreground" },
|
|
24
|
-
{ tone: "success", appearance: "soft", class: "bg-success/15 text-foreground" },
|
|
25
|
-
{ tone: "warning", appearance: "soft", class: "bg-tone-warning/15 text-foreground" },
|
|
26
|
-
{ tone: "1", appearance: "soft", class: "bg-tone-1/15 text-foreground" },
|
|
27
|
-
{ tone: "destructive", appearance: "soft", class: "bg-destructive/15 text-foreground" },
|
|
23
|
+
{ tone: "info", appearance: "soft", class: "bg-tone-info-mark/15 text-foreground" },
|
|
24
|
+
{ tone: "success", appearance: "soft", class: "bg-success-mark/15 text-foreground" },
|
|
25
|
+
{ tone: "warning", appearance: "soft", class: "bg-tone-warning-mark/15 text-foreground" },
|
|
26
|
+
{ tone: "1", appearance: "soft", class: "bg-tone-1-mark/15 text-foreground" },
|
|
27
|
+
{ tone: "destructive", appearance: "soft", class: "bg-destructive-mark/15 text-foreground" },
|
|
28
28
|
// Solid — the full tone fill with its paired ink, for loud states.
|
|
29
29
|
{ tone: "neutral", appearance: "solid", class: "bg-foreground text-background" },
|
|
30
30
|
{ tone: "info", appearance: "solid", class: "bg-tone-info text-tone-info-foreground" },
|
|
@@ -199,7 +199,12 @@ var barChartMeta = {
|
|
|
199
199
|
"popover",
|
|
200
200
|
"popover-foreground",
|
|
201
201
|
"foreground",
|
|
202
|
-
"background"
|
|
202
|
+
"background",
|
|
203
|
+
"tone-warning-mark",
|
|
204
|
+
"tone-info-mark",
|
|
205
|
+
"tone-1-mark",
|
|
206
|
+
"tone-2-mark",
|
|
207
|
+
"tone-3-mark"
|
|
203
208
|
],
|
|
204
209
|
motion: [
|
|
205
210
|
{
|
|
@@ -1,7 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
// src/components/bubble/bubble.tsx
|
|
4
|
+
import {
|
|
5
|
+
Children,
|
|
6
|
+
createContext,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useState
|
|
10
|
+
} from "react";
|
|
11
|
+
import { Icon } from "../../icon/index.js";
|
|
2
12
|
import { cn } from "../../lib/cn.js";
|
|
3
13
|
import { useTypeTier } from "../../lib/density.js";
|
|
14
|
+
import { Burst } from "../../motion/Burst.js";
|
|
15
|
+
import { motionAttribute } from "../../motion/MotionProvider.js";
|
|
16
|
+
import { NumberTicker } from "../number-ticker/number-ticker.js";
|
|
4
17
|
import { Pill } from "../pill/pill.js";
|
|
18
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../popover/popover.js";
|
|
19
|
+
import { DEFAULT_REACTIONS, ReactionPicker } from "../reaction-picker/reaction-picker.js";
|
|
5
20
|
import {
|
|
6
21
|
bubbleTailVariants,
|
|
7
22
|
bubbleReactionsVariants,
|
|
@@ -60,28 +75,46 @@ function BubbleTail({ variant, align }) {
|
|
|
60
75
|
);
|
|
61
76
|
}
|
|
62
77
|
Bubble.displayName = "Bubble";
|
|
78
|
+
var ReactionRowLive = createContext(false);
|
|
63
79
|
function BubbleReactions({
|
|
64
80
|
align = "start",
|
|
65
81
|
className,
|
|
82
|
+
children,
|
|
66
83
|
...props
|
|
67
84
|
}) {
|
|
68
|
-
|
|
85
|
+
const [live, setLive] = useState(false);
|
|
86
|
+
useEffect(() => setLive(true), []);
|
|
87
|
+
return /* @__PURE__ */ jsx(ReactionRowLive.Provider, { value: live, children: /* @__PURE__ */ jsx(
|
|
69
88
|
"div",
|
|
70
89
|
{
|
|
90
|
+
hidden: Children.toArray(children).length === 0 || void 0,
|
|
71
91
|
className: cn(bubbleReactionsVariants({ align }), className),
|
|
72
|
-
...props
|
|
92
|
+
...props,
|
|
93
|
+
children
|
|
73
94
|
}
|
|
74
|
-
);
|
|
95
|
+
) });
|
|
75
96
|
}
|
|
76
97
|
BubbleReactions.displayName = "BubbleReactions";
|
|
77
98
|
function BubbleReaction({
|
|
78
99
|
emoji,
|
|
100
|
+
label,
|
|
79
101
|
count,
|
|
80
102
|
active = false,
|
|
103
|
+
motion,
|
|
81
104
|
className,
|
|
82
105
|
...props
|
|
83
106
|
}) {
|
|
84
107
|
const tier = useTypeTier();
|
|
108
|
+
const live = useContext(ReactionRowLive);
|
|
109
|
+
const [arrived] = useState(() => live && active);
|
|
110
|
+
const [shots, setShots] = useState(arrived ? 1 : 0);
|
|
111
|
+
const [wasActive, setWasActive] = useState(active);
|
|
112
|
+
if (active !== wasActive) {
|
|
113
|
+
setWasActive(active);
|
|
114
|
+
if (active) setShots((n) => n + 1);
|
|
115
|
+
}
|
|
116
|
+
const shown = count !== void 0 && count > 1 ? count : void 0;
|
|
117
|
+
const named = label === void 0 ? void 0 : shown === void 0 ? label : `${label}, ${shown}`;
|
|
85
118
|
return (
|
|
86
119
|
// Pill's own quiet chip: outline (a hairline at rest, a primary tint when
|
|
87
120
|
// it is the reader's) at the xs control height. No overrides — a reaction
|
|
@@ -92,19 +125,86 @@ function BubbleReaction({
|
|
|
92
125
|
appearance: "outline",
|
|
93
126
|
size: "sm",
|
|
94
127
|
selected: active,
|
|
128
|
+
"aria-label": named,
|
|
129
|
+
"data-motion": motionAttribute(motion),
|
|
95
130
|
className: cn(tier.secondary, className),
|
|
96
131
|
...props,
|
|
97
132
|
children: [
|
|
98
|
-
/* @__PURE__ */
|
|
99
|
-
|
|
133
|
+
/* @__PURE__ */ jsxs("span", { className: "relative inline-flex", children: [
|
|
134
|
+
/* @__PURE__ */ jsx(
|
|
135
|
+
"span",
|
|
136
|
+
{
|
|
137
|
+
"aria-hidden": "true",
|
|
138
|
+
style: { "--motion-bloom-delay": "60ms" },
|
|
139
|
+
className: cn("inline-flex text-body-lg leading-none", active && shots > 0 && "motion-bloom"),
|
|
140
|
+
children: emoji
|
|
141
|
+
},
|
|
142
|
+
shots
|
|
143
|
+
),
|
|
144
|
+
/* @__PURE__ */ jsx(
|
|
145
|
+
Burst,
|
|
146
|
+
{
|
|
147
|
+
trigger: shots,
|
|
148
|
+
playOnMount: arrived,
|
|
149
|
+
particle: emoji,
|
|
150
|
+
count: 5,
|
|
151
|
+
motion,
|
|
152
|
+
className: "[--burst-size:4em]"
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
] }),
|
|
156
|
+
shown === void 0 ? null : /* @__PURE__ */ jsx(NumberTicker, { value: shown, motion, className: "text-mono-caption tabular-nums" })
|
|
100
157
|
]
|
|
101
158
|
}
|
|
102
159
|
)
|
|
103
160
|
);
|
|
104
161
|
}
|
|
105
162
|
BubbleReaction.displayName = "BubbleReaction";
|
|
163
|
+
function BubbleReactionAdd({
|
|
164
|
+
reactions = DEFAULT_REACTIONS,
|
|
165
|
+
selected,
|
|
166
|
+
onSelect,
|
|
167
|
+
label = "Add reaction",
|
|
168
|
+
motion,
|
|
169
|
+
className,
|
|
170
|
+
...props
|
|
171
|
+
}) {
|
|
172
|
+
const [open, setOpen] = useState(false);
|
|
173
|
+
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
|
|
174
|
+
/* @__PURE__ */ jsx(
|
|
175
|
+
PopoverTrigger,
|
|
176
|
+
{
|
|
177
|
+
render: /* @__PURE__ */ jsx(
|
|
178
|
+
Pill,
|
|
179
|
+
{
|
|
180
|
+
appearance: "outline",
|
|
181
|
+
size: "sm",
|
|
182
|
+
"aria-label": label,
|
|
183
|
+
className: cn("text-muted-foreground", className),
|
|
184
|
+
...props
|
|
185
|
+
}
|
|
186
|
+
),
|
|
187
|
+
children: /* @__PURE__ */ jsx(Icon, { name: "emoji", className: "size-4" })
|
|
188
|
+
}
|
|
189
|
+
),
|
|
190
|
+
/* @__PURE__ */ jsx(PopoverContent, { side: "top", align: "start", sideOffset: 6, className: "w-auto rounded-full p-1", children: /* @__PURE__ */ jsx(
|
|
191
|
+
ReactionPicker,
|
|
192
|
+
{
|
|
193
|
+
reactions,
|
|
194
|
+
selected,
|
|
195
|
+
motion,
|
|
196
|
+
onSelect: (emoji) => {
|
|
197
|
+
setOpen(false);
|
|
198
|
+
onSelect?.(emoji);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
) })
|
|
202
|
+
] });
|
|
203
|
+
}
|
|
204
|
+
BubbleReactionAdd.displayName = "BubbleReactionAdd";
|
|
106
205
|
export {
|
|
107
206
|
Bubble,
|
|
108
207
|
BubbleReaction,
|
|
208
|
+
BubbleReactionAdd,
|
|
109
209
|
BubbleReactions
|
|
110
210
|
};
|
|
@@ -66,12 +66,17 @@ var bubbleMeta = {
|
|
|
66
66
|
{
|
|
67
67
|
name: "BubbleReaction",
|
|
68
68
|
required: false,
|
|
69
|
-
description: "One reaction pill \u2014 emoji, count (hidden at 1), and pressed state."
|
|
69
|
+
description: "One reaction pill \u2014 emoji, count (hidden at 1), and pressed state. Celebrates when it becomes the reader's own."
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "BubbleReactionAdd",
|
|
73
|
+
required: false,
|
|
74
|
+
description: "The add-reaction pill at the end of the row: opens a ReactionPicker above it."
|
|
70
75
|
}
|
|
71
76
|
]
|
|
72
77
|
},
|
|
73
78
|
related: {
|
|
74
|
-
components: ["Message", "MessageScroller", "Marker", "Attachment"]
|
|
79
|
+
components: ["Message", "MessageScroller", "Marker", "Attachment", "ReactionPicker"]
|
|
75
80
|
},
|
|
76
81
|
props: [
|
|
77
82
|
{
|
|
@@ -106,8 +111,13 @@ var bubbleMeta = {
|
|
|
106
111
|
},
|
|
107
112
|
{
|
|
108
113
|
name: "BubbleReaction",
|
|
109
|
-
type: '{ emoji: ReactNode; count?: number; active?: boolean } & ComponentPropsWithRef<"button">',
|
|
110
|
-
description: 'One reaction pill. A toggle, not a label: `active` drives `aria-pressed` as well as the styling, and `count` is hidden at 1 because the pill already says "one".'
|
|
114
|
+
type: '{ emoji: ReactNode; label?: string; count?: number; active?: boolean; motion?: MotionLevel } & ComponentPropsWithRef<"button">',
|
|
115
|
+
description: 'One reaction pill. A toggle, not a label: `active` drives `aria-pressed` as well as the styling, `label` names it ("Party, 3"), and `count` is hidden at 1 because the pill already says "one". When it turns on \u2014 pressed, or newly added already on \u2014 the emoji blooms, copies of it burst out and the count rolls; pills the page loads with stay still.'
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: "BubbleReactionAdd",
|
|
119
|
+
type: '{ reactions?: Reaction[]; selected?: string[]; onSelect?: (emoji: string) => void; label?: string; motion?: MotionLevel } & ComponentPropsWithRef<"button">',
|
|
120
|
+
description: "The add-reaction pill: a smiley that opens a ReactionPicker above it. Choosing an emoji closes the picker and calls `onSelect`; add the emoji to the row and its new pill celebrates on arrival."
|
|
111
121
|
}
|
|
112
122
|
],
|
|
113
123
|
variants: {
|
|
@@ -128,9 +138,35 @@ var bubbleMeta = {
|
|
|
128
138
|
"muted-foreground",
|
|
129
139
|
"ring"
|
|
130
140
|
],
|
|
141
|
+
motion: [
|
|
142
|
+
{
|
|
143
|
+
slot: "arrival",
|
|
144
|
+
recipe: "none",
|
|
145
|
+
part: "Bubble",
|
|
146
|
+
why: "A bubble arrives with its Message row, which owns how a new message enters the transcript. A reaction pill that the reader just added arrives with its own celebration (below)."
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
slot: "response",
|
|
150
|
+
recipe: "motion-bloom",
|
|
151
|
+
part: "BubbleReaction",
|
|
152
|
+
why: "Reacting is the reader's choice, so it is answered like a like: the emoji grows from nothing past its size and settles, while copies of it spray outward and fade (Burst with the emoji as its particle, the way chat apps answer a reaction). Plays when a pill turns on and when a pill mounts already on into a row that has rendered \u2014 never for the pills a page loads with. Pills inherit Pill's motion-interactive and motion-press."
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
slot: "transition",
|
|
156
|
+
recipe: "custom",
|
|
157
|
+
part: "BubbleReaction",
|
|
158
|
+
why: "The count rolls with NumberTicker as people react. Turning a reaction off is quiet: the tint eases out and the count rolls back, with no burst. Under `subtle` or reduced motion the bloom and the spray are dropped and the tint and count alone answer."
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
slot: "activity",
|
|
162
|
+
recipe: "none",
|
|
163
|
+
part: "Bubble",
|
|
164
|
+
why: "A bubble is static content; a message still being written is StreamingText or a Marker, not a state of the surface."
|
|
165
|
+
}
|
|
166
|
+
],
|
|
131
167
|
a11y: [
|
|
132
168
|
"The surface is a plain container with no imposed role \u2014 a bubble is presentation, and announcing one as a region would put a landmark around every line of a conversation.",
|
|
133
|
-
|
|
169
|
+
'Reaction pills are real buttons reporting `aria-pressed`, so the reader\'s own reaction is state rather than colour alone. Give each a `label`: it is named "Party, 3", with the emoji and its burst hidden from AT. The add-reaction pill is named "Add reaction" and opens a toolbar picker.',
|
|
134
170
|
"`whitespace-pre-wrap` and `break-words` keep pasted logs, long URLs, and hard-wrapped text inside the surface instead of overflowing the conversation.",
|
|
135
171
|
"Every variant meets AA against its own surface, including `primary`, which pairs the primary fill with its paired foreground token rather than a hardcoded white."
|
|
136
172
|
],
|
|
@@ -145,7 +181,7 @@ var bubbleMeta = {
|
|
|
145
181
|
},
|
|
146
182
|
{
|
|
147
183
|
label: "With reactions",
|
|
148
|
-
code: '<Message messageId="m3">\n <Bubble>Shipped 0.45.0.</Bubble>\n <BubbleReactions>\n <BubbleReaction emoji="\u{1F389}" count={3} active onClick={() => toggle("\u{1F389}")} />\n <BubbleReaction emoji="\u{1F440}" count={1} onClick={() => toggle("\u{1F440}")} />\n </BubbleReactions>\n</Message>'
|
|
184
|
+
code: '<Message messageId="m3">\n <Bubble>Shipped 0.45.0.</Bubble>\n <BubbleReactions>\n <BubbleReaction emoji="\u{1F389}" label="Party" count={3} active onClick={() => toggle("\u{1F389}")} />\n <BubbleReaction emoji="\u{1F440}" label="Eyes" count={1} onClick={() => toggle("\u{1F440}")} />\n <BubbleReactionAdd selected={mine} onSelect={toggle} />\n </BubbleReactions>\n</Message>'
|
|
149
185
|
},
|
|
150
186
|
{
|
|
151
187
|
label: "A grouped run drops the repeated tail",
|
|
@@ -28,7 +28,7 @@ var bubbleTailVariants = cva(
|
|
|
28
28
|
}
|
|
29
29
|
);
|
|
30
30
|
var bubbleVariants = cva(
|
|
31
|
-
"relative w-fit max-w-[42rem] whitespace-pre-wrap break-words rounded-lg px-3.5 py-2.5 text-body-md",
|
|
31
|
+
"relative w-fit max-w-[42rem] whitespace-pre-wrap break-words rounded-lg px-3.5 py-2.5 text-body-md outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
32
32
|
{
|
|
33
33
|
variants: {
|
|
34
34
|
variant: {
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
import {
|
|
3
3
|
Bubble,
|
|
4
4
|
BubbleReaction,
|
|
5
|
+
BubbleReactionAdd,
|
|
5
6
|
BubbleReactions
|
|
6
7
|
} from "./bubble.js";
|
|
7
8
|
import { bubbleMeta } from "./bubble.meta.js";
|
|
8
9
|
export {
|
|
9
10
|
Bubble,
|
|
10
11
|
BubbleReaction,
|
|
12
|
+
BubbleReactionAdd,
|
|
11
13
|
BubbleReactions,
|
|
12
14
|
bubbleMeta
|
|
13
15
|
};
|
|
@@ -124,12 +124,11 @@ function CaretMenu({ trigger, label, status, children, className }) {
|
|
|
124
124
|
if (popupRef.current?.contains(event.target)) return;
|
|
125
125
|
place();
|
|
126
126
|
};
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
};
|
|
127
|
+
const listeners = new AbortController();
|
|
128
|
+
const { signal } = listeners;
|
|
129
|
+
view.addEventListener("resize", place, { signal });
|
|
130
|
+
view.addEventListener("scroll", onScroll, { capture: true, signal });
|
|
131
|
+
return () => listeners.abort();
|
|
133
132
|
}, [open, field, place]);
|
|
134
133
|
const [entered, setEntered] = useState(false);
|
|
135
134
|
useEffect(() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/components/chat-composer/chat-composer.variants.ts
|
|
2
2
|
import { cva } from "class-variance-authority";
|
|
3
3
|
var chatComposerVariants = cva(
|
|
4
|
-
"flex w-full flex-col gap-2 border border-
|
|
4
|
+
"flex w-full flex-col gap-2 border border-field-border bg-field p-2 motion-interactive focus-within:ring-2 focus-within:ring-ring [--chat-composer-r:var(--chat-composer-radius,var(--card-inner-radius,calc(min(var(--button-radius,9999px),var(--control-sm)/2)+(--spacing(2))+1px)))] rounded-[var(--chat-composer-r)]",
|
|
5
5
|
{
|
|
6
6
|
variants: {
|
|
7
7
|
invalid: {
|
|
@@ -27,15 +27,13 @@ function useAreaDrag(onChange) {
|
|
|
27
27
|
);
|
|
28
28
|
};
|
|
29
29
|
emit(event.clientX, event.clientY);
|
|
30
|
+
const drag = new AbortController();
|
|
31
|
+
const { signal } = drag;
|
|
30
32
|
const onMove = (e) => emit(e.clientX, e.clientY);
|
|
31
|
-
const onUp = () =>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
};
|
|
36
|
-
window.addEventListener("pointermove", onMove);
|
|
37
|
-
window.addEventListener("pointerup", onUp);
|
|
38
|
-
window.addEventListener("pointercancel", onUp);
|
|
33
|
+
const onUp = () => drag.abort();
|
|
34
|
+
window.addEventListener("pointermove", onMove, { signal });
|
|
35
|
+
window.addEventListener("pointerup", onUp, { signal });
|
|
36
|
+
window.addEventListener("pointercancel", onUp, { signal });
|
|
39
37
|
},
|
|
40
38
|
[onChange]
|
|
41
39
|
)
|