@sableui/react 0.15.0 → 0.17.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 +99 -0
- package/bin/sable-lint.mjs +5 -1
- package/dist/{_contract-CkqXDVww.d.ts → _contract-CDa9-NdB.d.ts} +47 -1
- package/dist/blocks/approval-block.js +30 -48
- package/dist/blocks/approval-block.meta.js +7 -8
- package/dist/blocks/index.d.ts +1 -1
- 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/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 +1 -1
- package/dist/components/feedback/feedback.meta.js +2 -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/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/search-input/search-input.meta.js +11 -3
- package/dist/components/section-rail/index.js +7 -0
- package/dist/components/section-rail/section-rail.js +153 -0
- package/dist/components/section-rail/section-rail.meta.js +189 -0
- 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/file-hashes.json +99 -92
- package/dist/foundations/accessibility.js +5 -0
- package/dist/index.d.ts +227 -25
- package/dist/index.js +12 -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 +1 -0
- package/dist/lib/use-active-section.js +59 -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/presets/index.d.ts +13 -2
- package/dist/presets/index.js +17 -1
- package/dist/styles.css +100 -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/approval-block.md +40 -55
- 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/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 +1 -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/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/search-input.md +20 -2
- package/llms/components/section-rail.md +101 -0
- 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/foundations/accessibility.md +1 -0
- package/llms/reference/token-intent.md +22 -2
- package/llms/reference/utilities.md +5 -2
- package/llms-full.txt +367 -105
- package/llms.txt +6 -5
- package/package.json +1 -1
- package/registry.json +609 -67
- package/scripts/lint-rules.mjs +6 -3
- package/targets/ios.json +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,104 @@
|
|
|
1
1
|
# What's new
|
|
2
2
|
|
|
3
|
+
## 0.17.0
|
|
4
|
+
|
|
5
|
+
Knowing where you are: a rail that follows a long read, and an approval
|
|
6
|
+
that shows an agent's call the way agent products do.
|
|
7
|
+
|
|
8
|
+
- **New: `SectionRail`.** A slim vertical rail of dots beside a long read —
|
|
9
|
+
a case study, an essay, a long landing page — one per section. A bar marks
|
|
10
|
+
the section being read and glides to the next as the page scrolls; hovering
|
|
11
|
+
or focusing a dot names it in one tooltip that travels between dots
|
|
12
|
+
(`TooltipGroup`). Picking a dot jumps there with the page's own
|
|
13
|
+
`scroll-behavior` (instant under reduced motion), and the bar goes straight
|
|
14
|
+
to the pick rather than stepping through every section in between. Pass
|
|
15
|
+
`root` when the content scrolls inside an element: only that element moves,
|
|
16
|
+
never the page around it. Links are real `#id` anchors, so it works without
|
|
17
|
+
script. Promoted from carldecaire.com's case-study rail.
|
|
18
|
+
- **New: `useActiveSection(ids, { offset, root })`.** The scroll spy behind it,
|
|
19
|
+
exported for a contents list you draw yourself. The current section is the
|
|
20
|
+
last whose top has crossed the reading line; at the end of the scroll the
|
|
21
|
+
last one in view wins, so a short closing section is still reachable.
|
|
22
|
+
- **`ApprovalBlock` asks the way agent products do.** It wraps the call's own
|
|
23
|
+
`ToolCall` — tool name and exact input, open on arrival — that turns to
|
|
24
|
+
Allowed or Denied once answered, and offers Deny / Always allow / Allow once.
|
|
25
|
+
The details table and the "for the rest of this chat" checkbox are gone;
|
|
26
|
+
`onApprove({ remember })` is unchanged, with `remember` now true for Always
|
|
27
|
+
allow. The example is an agent asking to run a command.
|
|
28
|
+
|
|
29
|
+
## 0.16.0
|
|
30
|
+
|
|
31
|
+
Five pieces of the calm, product-chrome look that AI and B2B tools share:
|
|
32
|
+
tick-bar budgets, pastel art, filled fields, an inset frame and a dither
|
|
33
|
+
texture. Each is told through the theme, so every preset can wear it.
|
|
34
|
+
|
|
35
|
+
- **New: segmented meters.** `<Meter segments={40} />` draws the bar as a row
|
|
36
|
+
of ticks — a spend limit, credits left, a lead score, months of data — read
|
|
37
|
+
at a glance. Any reading above `min` shows at least one tick, and when the
|
|
38
|
+
value changes the fill runs through the ticks from the edge it is leaving.
|
|
39
|
+
Size the count to the track (about one tick per 8px) so a tick stays taller
|
|
40
|
+
than it is wide. `shape="ring"` ignores it.
|
|
41
|
+
- **New: `GradientArt intensity="soft"`.** The same art as a pastel of itself:
|
|
42
|
+
lifted and quieted, bright in dark mode too. It is a stage for floating
|
|
43
|
+
chips, logo tiles and card header bands, not a surface for text.
|
|
44
|
+
- **New: the field tokens, `field` and `field-border`.** Every text field —
|
|
45
|
+
Input, Textarea, Select, Combobox, SearchInput, NumberField, OTPField,
|
|
46
|
+
DatePicker, InputGroup, ChatComposer and CommandMenuTrigger — now paints
|
|
47
|
+
this pair, outlined on the card by default exactly as before. A theme fills
|
|
48
|
+
them all at once with `--theme-field: var(--muted)` and
|
|
49
|
+
`--theme-field-border: transparent`. Canvas has a matching **Fields**
|
|
50
|
+
control (Preset / Outline / Filled), and presets may set both hooks.
|
|
51
|
+
A hand-built field wearing `border-field-border` is caught by
|
|
52
|
+
`lookalike-control` like one wearing `border-input`.
|
|
53
|
+
- **New: `AppShell frame="inset"`.** From `lg`, the rail and a small gap sit on
|
|
54
|
+
one ground and main becomes a rounded, bordered panel that scrolls inside
|
|
55
|
+
itself while the rail stays put. The Sidebar in the slot drops its border
|
|
56
|
+
and fills the row, and its footer rule spans the content column rather than
|
|
57
|
+
running out into the gap; below `lg` nothing changes. The ground is a new derived
|
|
58
|
+
token, `shell-ground` — one step off the rail, so the panel reads as lifted
|
|
59
|
+
even in themes whose rail is the page colour; tune it with
|
|
60
|
+
`--theme-shell-ground`. Inset moves the desktop scroll from `window` to
|
|
61
|
+
`#main-content`, so scroll listeners (restoration, scroll-to-top, scrollspy)
|
|
62
|
+
must target `main`.
|
|
63
|
+
- **New utility: `texture-dither`.** A faint field of tiny squares that
|
|
64
|
+
dissolves, painted in `border-strong` so it follows every theme — decoration
|
|
65
|
+
for behind an empty state or a page header. `--dither-fade`,
|
|
66
|
+
`--dither-size` and `--dither-color` tune it.
|
|
67
|
+
- **Status and tone colours at graphic strength.** Meters, segmented bars,
|
|
68
|
+
status dots, chart series and the soft tints behind badges, banners and
|
|
69
|
+
notes were painted with the label fills — dark enough to carry white text,
|
|
70
|
+
so in light mode they read brown, forest green and plum. They now read new
|
|
71
|
+
`-mark` tokens (`success-mark`, `tone-warning-mark`, `destructive-mark`,
|
|
72
|
+
`tone-info-mark`, `tone-1/2/3-mark`): the same hue lifted and saturated,
|
|
73
|
+
held to a 2:1 visibility floor on the page instead of a text ratio. Every
|
|
74
|
+
bundled theme tunes its own in both modes, and keeps its character —
|
|
75
|
+
Ember's olive, Poppy's pastels and Atelier's grey tone stay theirs; a
|
|
76
|
+
preset sets `--theme-*-mark` or gets one derived from its fill. Labels are
|
|
77
|
+
unchanged: solid badges, banners and buttons keep their fills, and text
|
|
78
|
+
keeps `-text`. `markFor` (from `@sableui/react/presets`) derives a mark the
|
|
79
|
+
same way, and Canvas moves the marks with any tone it generates.
|
|
80
|
+
- **Canvas: a brand colour no longer flattens the tones.** Info is treated
|
|
81
|
+
as the status it is: it leans toward the brand within the blues instead of
|
|
82
|
+
being repainted (Brand only made it the brand, so a green brand's info read
|
|
83
|
+
as success; the schemes spun it anywhere). A status also keeps 25° off the
|
|
84
|
+
brand's own hue when its band allows, so a green brand gets a teal success
|
|
85
|
+
rather than a second copy of itself. And Brand only is a family you can tell
|
|
86
|
+
apart — a deep brand, a lighter neighbour and a muted sibling — instead of
|
|
87
|
+
one hue in four near-identical depths.
|
|
88
|
+
- **Table:** `numeric` on TableHead and TableCell is now documented.
|
|
89
|
+
- **New: search matches light up.** Combobox and CommandMenu paint the typed
|
|
90
|
+
text in each row's label, on a tint of the theme's `highlight`. It uses the CSS
|
|
91
|
+
Custom Highlight API, so rows get no `<mark>` wrappers and their accessible
|
|
92
|
+
names stay whole; browsers without it simply show no paint. Combobox lights
|
|
93
|
+
only what was typed, not a label a pick filled in. `highlightMatches={false}`
|
|
94
|
+
turns it off on either.
|
|
95
|
+
- **New hook: `useMatchHighlight(query, { selector })`.** The same paint for a
|
|
96
|
+
results list you filter yourself, such as a SearchInput over a table: it
|
|
97
|
+
returns a callback ref for the results container.
|
|
98
|
+
- **Textarea:** text you are editing no longer rewraps above the caret when
|
|
99
|
+
the field sits inside a `text-pretty` or `text-balance` block
|
|
100
|
+
(`text-wrap-style: stable`). ChatComposer gets the same fix.
|
|
101
|
+
|
|
3
102
|
## 0.15.0
|
|
4
103
|
|
|
5
104
|
Motion with some life in it: small things that turn on now land like events,
|
package/bin/sable-lint.mjs
CHANGED
|
@@ -44,7 +44,11 @@ if (!existsSync(registryPath)) {
|
|
|
44
44
|
}
|
|
45
45
|
const REGISTRY = JSON.parse(readFileSync(registryPath, "utf8"))
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
// Derived colours (`tone-warning-mark`, `field`, `shell-ground`…) are real tokens
|
|
48
|
+
// too: every theme gets them, so a utility naming one is not a typo.
|
|
49
|
+
const semanticTokens = new Set(
|
|
50
|
+
[...(REGISTRY.tokens?.semantic ?? []), ...(REGISTRY.tokens?.derived ?? [])].map((t) => (typeof t === "string" ? t : t.name)),
|
|
51
|
+
)
|
|
48
52
|
// Motion token NAMES are reassembled from the registry's durations/easings lists.
|
|
49
53
|
// A test in the package asserts this reconstruction equals _contract.ts exactly,
|
|
50
54
|
// so the consumer linter can never validate against a drifted vocabulary.
|
|
@@ -147,6 +147,52 @@ declare const ART_TOKENS: readonly ["art-1", "art-2", "art-3", "art-4", "art-5",
|
|
|
147
147
|
* 2 doubles it, for a theme that wants every colour to read as its own.
|
|
148
148
|
*/
|
|
149
149
|
declare const PALETTE_TOKENS: readonly ["palette-harmony"];
|
|
150
|
+
/**
|
|
151
|
+
* The text field's surface — derived like the edges, with a `--theme-*` hook.
|
|
152
|
+
*
|
|
153
|
+
* - `field` is the fill of every text-entry field (Input, Textarea, Select,
|
|
154
|
+
* Combobox, SearchInput, InputGroup, ChatComposer…). It defaults to `card`.
|
|
155
|
+
* - `field-border` is that field's outline. It defaults to `input`.
|
|
156
|
+
*
|
|
157
|
+
* Together they are the one choice a theme makes about how a field reads:
|
|
158
|
+
* outlined on the card (the default), or filled — `--theme-field: var(--muted)`
|
|
159
|
+
* with `--theme-field-border: transparent` — the borderless grey well some
|
|
160
|
+
* product UIs use for every input. Every field wears the pair through
|
|
161
|
+
* lib/field-shell, so the choice reaches them all at once instead of being
|
|
162
|
+
* restyled per control.
|
|
163
|
+
*/
|
|
164
|
+
declare const FIELD_TOKENS: readonly ["field", "field-border"];
|
|
165
|
+
/**
|
|
166
|
+
* The ground of an inset app frame — derived, with a `--theme-*` hook.
|
|
167
|
+
*
|
|
168
|
+
* `<AppShell frame="inset">` lifts main into a rounded panel and paints the
|
|
169
|
+
* rail and the gap around it as one surface. That surface cannot simply be
|
|
170
|
+
* `sidebar`: most themes paint the rail the page's own colour, so the panel
|
|
171
|
+
* sat on a ground of the same colour and only its hairline told them apart.
|
|
172
|
+
* `shell-ground` is one small step off the rail toward its ink, so the panel
|
|
173
|
+
* reads as lifted in every theme, and a theme sets `--theme-shell-ground` for
|
|
174
|
+
* a stronger or tinted ground.
|
|
175
|
+
*/
|
|
176
|
+
declare const APP_SHELL_TOKENS: readonly ["shell-ground"];
|
|
177
|
+
/**
|
|
178
|
+
* Status and tone colours at GRAPHIC strength — derived, with `--theme-*` hooks.
|
|
179
|
+
*
|
|
180
|
+
* Each status colour already comes in two: the fill, which carries a label
|
|
181
|
+
* (dark enough for white ink at 4.5:1 in light mode), and `-text`, the colour
|
|
182
|
+
* as ink on a surface. Neither is right for a mark that carries no text — a
|
|
183
|
+
* meter's fill, a segmented bar, a status dot, a chart series, the soft tint
|
|
184
|
+
* behind a badge. Those were painted with the label fill, so in light mode
|
|
185
|
+
* every one of them was a deep brown, forest green or plum: muddy, and 5–9:1
|
|
186
|
+
* against the page when a graphic needs far less.
|
|
187
|
+
*
|
|
188
|
+
* A mark is the same hue lifted and saturated. It is held to a VISIBILITY floor
|
|
189
|
+
* of 2:1 against `background` and `card` (NON_TEXT_HARD_FLOOR, the `graphic`
|
|
190
|
+
* tier in contrast.ts), not to a text ratio: every mark that uses it sits
|
|
191
|
+
* beside text that states the same thing (a meter's reading, a dot's label).
|
|
192
|
+
* A graphic that is the ONLY carrier of its meaning still owes WCAG 1.4.11's
|
|
193
|
+
* 3:1 — use the fill, or add the text.
|
|
194
|
+
*/
|
|
195
|
+
declare const MARK_TOKENS: readonly ["destructive-mark", "success-mark", "tone-warning-mark", "tone-info-mark", "tone-1-mark", "tone-2-mark", "tone-3-mark"];
|
|
150
196
|
/**
|
|
151
197
|
* UI type tiers (mode-independent) — the chrome typography register.
|
|
152
198
|
*
|
|
@@ -193,4 +239,4 @@ interface ThemeManifest {
|
|
|
193
239
|
};
|
|
194
240
|
}
|
|
195
241
|
|
|
196
|
-
export {
|
|
242
|
+
export { APP_SHELL_TOKENS as A, DENSITY_TOKENS as D, FIELD_TOKENS as F, MARK_TOKENS as M, PALETTE_TOKENS as P, RADIUS_TOKENS as R, SEMANTIC_TOKENS as S, type ThemeManifest as T, WORKSPACE_SURFACE_TOKENS as W, type ThemeMode as a, ART_TOKENS as b, type DensityToken as c, MOTION_TOKENS as d, type MotionToken as e, RHYTHM_TOKENS as f, type RadiusToken as g, type RhythmToken as h, type SemanticToken as i, TYPE_OVERLINE_TOKENS as j, TYPE_REFINEMENT_TOKENS as k, TYPE_SCALE_TOKENS as l, TYPE_UI_TOKENS as m, type TypeOverlineToken as n, type TypeRefinementToken as o, type TypeScaleToken as p, type TypeUiToken as q, type WorkspaceSurfaceToken as r };
|
|
@@ -1,59 +1,48 @@
|
|
|
1
1
|
// src/blocks/approval-block.tsx
|
|
2
|
-
import { useEffect,
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import { Button } from "../components/button/index.js";
|
|
4
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
5
|
import { InlineStatus } from "../components/inline-status/index.js";
|
|
8
|
-
import {
|
|
6
|
+
import { ToolCall } from "../components/tool-call/index.js";
|
|
9
7
|
import { cn } from "../lib/cn.js";
|
|
10
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
9
|
var request = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
allowed: "Allowed. Sending the email.",
|
|
21
|
-
denied: "Denied. The agent won't send it."
|
|
10
|
+
title: "Run this command?",
|
|
11
|
+
description: "The agent wants to migrate the production database in acme/api. It changes live data.",
|
|
12
|
+
tool: "run_command",
|
|
13
|
+
action: "Run a command",
|
|
14
|
+
// A command reads as the command; a structured call would pass its arguments object.
|
|
15
|
+
input: "pnpm db:migrate --env production",
|
|
16
|
+
allowed: "Allowed. Running the migration.",
|
|
17
|
+
denied: "Denied. The agent won't run it."
|
|
22
18
|
};
|
|
23
19
|
function ApprovalBlock({ className, framed = true, onApprove, onDeny }) {
|
|
24
20
|
const [decision, setDecision] = useState(null);
|
|
25
|
-
const [remember, setRemember] = useState(false);
|
|
26
|
-
const id = useId();
|
|
27
21
|
const outcome = useRef(null);
|
|
28
22
|
useEffect(() => {
|
|
29
23
|
if (decision) outcome.current?.focus();
|
|
30
24
|
}, [decision]);
|
|
25
|
+
const approve = (remember) => {
|
|
26
|
+
setDecision("allowed");
|
|
27
|
+
onApprove?.({ remember });
|
|
28
|
+
};
|
|
31
29
|
return /* @__PURE__ */ jsxs(Card, { variant: framed ? "default" : "flat", className: cn("w-full max-w-md", className), children: [
|
|
32
30
|
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
33
31
|
/* @__PURE__ */ jsx(CardTitle, { children: request.title }),
|
|
34
32
|
/* @__PURE__ */ jsx(CardDescription, { children: request.description })
|
|
35
33
|
] }),
|
|
36
|
-
/* @__PURE__ */
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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(
|
|
34
|
+
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx(
|
|
35
|
+
ToolCall,
|
|
36
|
+
{
|
|
37
|
+
name: request.tool,
|
|
38
|
+
title: request.action,
|
|
39
|
+
status: decision === "allowed" ? "complete" : decision === "denied" ? "denied" : "awaiting",
|
|
40
|
+
statusLabel: decision === "allowed" ? "Allowed" : void 0,
|
|
41
|
+
input: request.input,
|
|
42
|
+
defaultOpen: true
|
|
43
|
+
}
|
|
44
|
+
) }),
|
|
45
|
+
/* @__PURE__ */ jsx(CardFooter, { actions: true, children: decision ? /* @__PURE__ */ jsx(
|
|
57
46
|
InlineStatus,
|
|
58
47
|
{
|
|
59
48
|
ref: outcome,
|
|
@@ -67,7 +56,8 @@ function ApprovalBlock({ className, framed = true, onApprove, onDeny }) {
|
|
|
67
56
|
/* @__PURE__ */ jsx(
|
|
68
57
|
Button,
|
|
69
58
|
{
|
|
70
|
-
variant: "
|
|
59
|
+
variant: "ghost",
|
|
60
|
+
className: "me-auto",
|
|
71
61
|
onClick: () => {
|
|
72
62
|
setDecision("denied");
|
|
73
63
|
onDeny?.();
|
|
@@ -75,16 +65,8 @@ function ApprovalBlock({ className, framed = true, onApprove, onDeny }) {
|
|
|
75
65
|
children: "Deny"
|
|
76
66
|
}
|
|
77
67
|
),
|
|
78
|
-
/* @__PURE__ */ jsx(
|
|
79
|
-
|
|
80
|
-
{
|
|
81
|
-
onClick: () => {
|
|
82
|
-
setDecision("allowed");
|
|
83
|
-
onApprove?.({ remember });
|
|
84
|
-
},
|
|
85
|
-
children: "Allow"
|
|
86
|
-
}
|
|
87
|
-
)
|
|
68
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => approve(true), children: "Always allow" }),
|
|
69
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => approve(false), children: "Allow once" })
|
|
88
70
|
] }) })
|
|
89
71
|
] });
|
|
90
72
|
}
|
|
@@ -4,18 +4,17 @@ var approvalBlockMeta = {
|
|
|
4
4
|
family: "AI & agents",
|
|
5
5
|
kind: "card",
|
|
6
6
|
width: { min: "18rem", max: "28rem" },
|
|
7
|
-
summary:
|
|
8
|
-
intent:
|
|
7
|
+
summary: "An agent asking permission for one tool call: the question, what is at stake, the call itself as a ToolCall with its exact input, and Deny / Always allow / Allow once.",
|
|
8
|
+
intent: `Reach for this in a chat or agent run when a tool call waits on the person \u2014 running a command, sending, paying, deleting, publishing. It wraps the call's own ToolCall, which shows status="awaiting" until the person answers and then "complete" or "denied". A destructive action outside an agent is DestructiveActionModal.`,
|
|
9
9
|
notes: [
|
|
10
|
-
|
|
11
|
-
"The
|
|
12
|
-
"
|
|
13
|
-
"After a decision the buttons give way to one InlineStatus line saying what happened, so the thread keeps a record of the answer."
|
|
10
|
+
'The title is the question in plain words ("Run this command?"); the description says what is at stake. That sentence is what the person is deciding on.',
|
|
11
|
+
"The call is shown as the agent made it \u2014 tool name and raw input, open on arrival \u2014 because the exact arguments are what a careful person checks. Do not paraphrase them into a form.",
|
|
12
|
+
"Three answers, as agent products settle on: Deny on the start edge, then Always allow and Allow once, the primary on the end. 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, and the ToolCall shows the outcome, so the thread keeps a record of the answer."
|
|
14
14
|
],
|
|
15
15
|
a11y: [
|
|
16
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
|
|
18
|
-
"The remember Checkbox has its own Label, so the tool name inside it is part of its accessible name."
|
|
17
|
+
"The ToolCall announces its own status change, and its input is a labelled code view."
|
|
19
18
|
]
|
|
20
19
|
};
|
|
21
20
|
export {
|
package/dist/blocks/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ interface ApprovalBlockProps {
|
|
|
17
17
|
className?: string;
|
|
18
18
|
/** false drops the card's surface and border — for a chat column that already frames it. */
|
|
19
19
|
framed?: boolean;
|
|
20
|
-
/** The person allowed the call. `remember` is
|
|
20
|
+
/** The person allowed the call. `remember` is true for "Always allow". */
|
|
21
21
|
onApprove?: (options: {
|
|
22
22
|
remember: boolean;
|
|
23
23
|
}) => void;
|
|
@@ -11,6 +11,7 @@ var activity = [
|
|
|
11
11
|
{ title: "Refund", detail: "Order 1042 \xB7 Yesterday", amount: "-24.00", tone: "2" },
|
|
12
12
|
{ title: "Payout", detail: "To checking \xB7 Mon", amount: "-850.00", tone: "3" }
|
|
13
13
|
];
|
|
14
|
+
var TONE_MARK = { "1": "bg-tone-1-mark", "2": "bg-tone-2-mark", "3": "bg-tone-3-mark" };
|
|
14
15
|
function WalletBlock({ className, framed = true, onSend, onReceive }) {
|
|
15
16
|
return /* @__PURE__ */ jsxs(Card, { variant: framed ? "default" : "flat", className: cn("w-full max-w-xs", className), children: [
|
|
16
17
|
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
@@ -30,8 +31,7 @@ function WalletBlock({ className, framed = true, onSend, onReceive }) {
|
|
|
30
31
|
"span",
|
|
31
32
|
{
|
|
32
33
|
"aria-hidden": true,
|
|
33
|
-
className: "block size-8 rounded-full",
|
|
34
|
-
style: { background: `oklch(from var(--tone-${item.tone}) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h)` }
|
|
34
|
+
className: cn("block size-8 rounded-full", TONE_MARK[item.tone])
|
|
35
35
|
}
|
|
36
36
|
),
|
|
37
37
|
title: item.title,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/app-shell/app-shell-frame.tsx
|
|
4
|
+
import { createContext, useContext } from "react";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
var AppShellFrameContext = createContext(null);
|
|
7
|
+
function AppShellFrameProvider({ frame, children }) {
|
|
8
|
+
return /* @__PURE__ */ jsx(AppShellFrameContext.Provider, { value: frame, children });
|
|
9
|
+
}
|
|
10
|
+
function useAppShellFrame() {
|
|
11
|
+
return useContext(AppShellFrameContext);
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
AppShellFrameProvider,
|
|
15
|
+
useAppShellFrame
|
|
16
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/app-shell/app-shell-main.tsx
|
|
4
|
+
import { useScrollActivity } from "../../lib/use-scroll-activity.js";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
function AppShellMain({ scrolls, ...props }) {
|
|
7
|
+
const trackScroll = useScrollActivity();
|
|
8
|
+
return /* @__PURE__ */ jsx("main", { ref: scrolls ? trackScroll : void 0, ...props });
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
AppShellMain
|
|
12
|
+
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// src/components/app-shell/app-shell.tsx
|
|
2
2
|
import { cn } from "../../lib/cn.js";
|
|
3
3
|
import { AppShellDock } from "./app-shell-dock.js";
|
|
4
|
+
import { AppShellFrameProvider } from "./app-shell-frame.js";
|
|
5
|
+
import { AppShellMain } from "./app-shell-main.js";
|
|
4
6
|
import { SkipLink } from "./skip-link.js";
|
|
5
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
8
|
function AppShell({
|
|
@@ -10,18 +12,27 @@ function AppShell({
|
|
|
10
12
|
bottomNav,
|
|
11
13
|
dock,
|
|
12
14
|
overlays,
|
|
15
|
+
frame = "flush",
|
|
13
16
|
className,
|
|
14
17
|
children,
|
|
15
18
|
...rest
|
|
16
19
|
}) {
|
|
20
|
+
const inset = frame === "inset";
|
|
17
21
|
return /* @__PURE__ */ jsxs(
|
|
18
22
|
"div",
|
|
19
23
|
{
|
|
24
|
+
"data-frame": inset ? "inset" : void 0,
|
|
20
25
|
className: cn(
|
|
21
26
|
// `overflow-x-clip` (not `-hidden`) contains horizontal bleed WITHOUT
|
|
22
27
|
// establishing a scroll container — `-hidden` forces `overflow-y: auto`,
|
|
23
28
|
// which breaks `position: sticky` on a descendant Sidebar.
|
|
24
29
|
"flex min-h-screen flex-col overflow-x-clip bg-background text-foreground",
|
|
30
|
+
// Inset, from lg: the shell IS the viewport and never scrolls — main
|
|
31
|
+
// does. The ground is the rail's surface, so the rail and the gap
|
|
32
|
+
// around the panel read as one plane. A page-surface rail (Sidebar
|
|
33
|
+
// surface="page") is page-coloured, so the ground follows it and the
|
|
34
|
+
// panel's border carries the lift.
|
|
35
|
+
inset && "lg:h-dvh lg:min-h-0 lg:bg-shell-ground lg:[&:has(>[data-app-shell-body]>[data-surface=page])]:bg-background",
|
|
25
36
|
className
|
|
26
37
|
),
|
|
27
38
|
...rest,
|
|
@@ -29,16 +40,29 @@ function AppShell({
|
|
|
29
40
|
/* @__PURE__ */ jsx(SkipLink, {}),
|
|
30
41
|
banners ? /* @__PURE__ */ jsx("div", { className: "relative z-(--z-nav)", children: banners }) : null,
|
|
31
42
|
header,
|
|
32
|
-
/* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1", children: [
|
|
33
|
-
sidebar,
|
|
43
|
+
/* @__PURE__ */ jsxs("div", { "data-app-shell-body": inset ? "" : void 0, className: "flex min-h-0 flex-1", children: [
|
|
44
|
+
inset ? /* @__PURE__ */ jsx(AppShellFrameProvider, { frame: "inset", children: sidebar }) : sidebar,
|
|
34
45
|
/* @__PURE__ */ jsx(
|
|
35
|
-
|
|
46
|
+
AppShellMain,
|
|
36
47
|
{
|
|
37
48
|
id: "main-content",
|
|
49
|
+
scrolls: inset,
|
|
50
|
+
tabIndex: inset ? -1 : void 0,
|
|
38
51
|
style: { containerType: "inline-size" },
|
|
39
52
|
className: cn(
|
|
40
53
|
"min-w-0 flex-1",
|
|
41
|
-
bottomNav && "pb-[calc(var(--nav-height)+env(safe-area-inset-bottom,0px))] lg:pb-0"
|
|
54
|
+
bottomNav && "pb-[calc(var(--nav-height)+env(safe-area-inset-bottom,0px))] lg:pb-0",
|
|
55
|
+
// The inset panel. A spacing-step gap on the open sides (the rail
|
|
56
|
+
// is the leading one). Banners and the dock are in flow in the
|
|
57
|
+
// shell's column, so the bottom gap already sits above a dock.
|
|
58
|
+
// Sticky children stick to the panel's top, and the overflow
|
|
59
|
+
// clips them to its rounded corners. A resting surface: a hairline
|
|
60
|
+
// edge and the subtlest raise, never a floating shadow.
|
|
61
|
+
inset && cn(
|
|
62
|
+
"outline-none lg:m-2 lg:overflow-y-auto lg:overscroll-contain scrollbar-quiet",
|
|
63
|
+
"lg:rounded-lg lg:border lg:border-border lg:bg-background lg:shadow-sm",
|
|
64
|
+
sidebar && "lg:ms-0"
|
|
65
|
+
)
|
|
42
66
|
),
|
|
43
67
|
children
|
|
44
68
|
}
|
|
@@ -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",
|