@tangle-network/ui 11.49.0 → 11.50.2
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 +28 -0
- package/dist/chat/agent-timeline.js +4 -3
- package/dist/chat/speaker.d.ts +3 -3
- package/dist/chat/speaker.js +12 -18
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/lib/code-presentation.js +1 -1
- package/dist/markdown/code-block.js +3 -3
- package/dist/presentation.d.ts +12 -0
- package/dist/presentation.js +85 -1
- package/dist/primitives/metric-strip.d.ts +2 -2
- package/dist/primitives/page-shell.d.ts +6 -3
- package/dist/primitives/page-shell.js +3 -60
- package/dist/primitives/progress.d.ts +8 -0
- package/dist/primitives/progress.js +8 -2
- package/dist/primitives/settings.d.ts +28 -1
- package/dist/primitives/settings.js +78 -29
- package/dist/primitives/status-pill.js +11 -2
- package/dist/primitives.d.ts +3 -3
- package/dist/primitives.js +3 -3
- package/package.json +2 -2
- package/src/chat/agent-timeline.test.tsx +9 -0
- package/src/chat/agent-timeline.tsx +4 -3
- package/src/chat/speaker.test.tsx +3 -1
- package/src/chat/speaker.tsx +13 -14
- package/src/lib/code-presentation.ts +1 -1
- package/src/markdown/code-block.test.tsx +0 -6
- package/src/markdown/code-block.tsx +4 -3
- package/src/markdown/markdown.stories.tsx +20 -0
- package/src/presentation.ts +68 -0
- package/src/primitives/index.ts +2 -1
- package/src/primitives/metric-strip.tsx +2 -2
- package/src/primitives/page-shell.tsx +9 -58
- package/src/primitives/progress.stories.tsx +10 -0
- package/src/primitives/progress.tsx +13 -2
- package/src/primitives/settings.stories.tsx +94 -3
- package/src/primitives/settings.tsx +80 -19
- package/src/primitives/status-pill.tsx +8 -1
- package/src/styles/presentation.css +3 -3
|
@@ -6,19 +6,20 @@ import { Label } from "./label.js";
|
|
|
6
6
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "./dropdown-menu.js";
|
|
7
7
|
import { InfoTip } from "./info-tip.js";
|
|
8
8
|
import { fieldMessageId } from "./field.js";
|
|
9
|
+
import { StatusGlyph } from "./status-pill.js";
|
|
9
10
|
import * as React from "react";
|
|
10
11
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
-
import { CircleAlert, Ellipsis, Lock } from "lucide-react";
|
|
12
|
+
import { CircleAlert, Ellipsis, Lock, TriangleAlert } from "lucide-react";
|
|
12
13
|
// src/primitives/settings.tsx
|
|
13
14
|
const SCROLL_OFFSET = "scroll-mt-[calc(var(--shell-header-height)_+_1rem)]";
|
|
15
|
+
const GUTTER = "px-5 py-4 @max-[34rem]/settings-section:px-4";
|
|
14
16
|
const DIVIDED = "not-first:border-t not-first:border-[color:var(--border-subtle)]";
|
|
15
17
|
const ERROR_TEXT = "text-[color:var(--surface-danger-text)]";
|
|
16
|
-
function
|
|
18
|
+
function GlyphLine({ icon: Icon, className, children, ...props }) {
|
|
17
19
|
return /* @__PURE__ */ jsxs("div", {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
children: [/* @__PURE__ */ jsx(CircleAlert, {
|
|
20
|
+
...props,
|
|
21
|
+
className: cn("flex items-start gap-1.5 text-sm leading-5 [overflow-wrap:anywhere]", className),
|
|
22
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
22
23
|
"aria-hidden": "true",
|
|
23
24
|
className: "mt-0.5 size-4 shrink-0"
|
|
24
25
|
}), /* @__PURE__ */ jsx("div", {
|
|
@@ -27,18 +28,32 @@ function ErrorLine({ id, className, children }) {
|
|
|
27
28
|
})]
|
|
28
29
|
});
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
31
|
+
const ErrorLine = ({ className, ...props }) => /* @__PURE__ */ jsx(GlyphLine, {
|
|
32
|
+
icon: CircleAlert,
|
|
33
|
+
role: "alert",
|
|
34
|
+
className: cn(ERROR_TEXT, className),
|
|
35
|
+
...props
|
|
36
|
+
});
|
|
37
|
+
const LockLine = ({ className, ...props }) => /* @__PURE__ */ jsx(GlyphLine, {
|
|
38
|
+
icon: Lock,
|
|
39
|
+
className: cn("text-muted-foreground", className),
|
|
40
|
+
...props
|
|
41
|
+
});
|
|
42
|
+
const TOKEN_BREAK = /(?<=[/@])(?=[^/])|(?<=[\p{L}\p{N}])(?=[.\-_?#&=][\p{L}\p{N}])/u;
|
|
43
|
+
function breakTokens(node) {
|
|
44
|
+
if (typeof node === "string") {
|
|
45
|
+
if (!TOKEN_BREAK.test(node)) return node;
|
|
46
|
+
return node.split(/(\s+)/).map((word, index) => {
|
|
47
|
+
const parts = word.split(TOKEN_BREAK);
|
|
48
|
+
return parts.length < 2 ? word : /* @__PURE__ */ jsx("span", {
|
|
49
|
+
className: "inline-block max-w-full [text-decoration:inherit]",
|
|
50
|
+
children: parts.map((part, at) => /* @__PURE__ */ jsxs(React.Fragment, { children: [at > 0 && /* @__PURE__ */ jsx("wbr", {}), part] }, at))
|
|
51
|
+
}, index);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(node)) return React.Children.map(node, breakTokens);
|
|
55
|
+
if (React.isValidElement(node) && node.props.children != null && typeof node.props.children !== "function") return React.cloneElement(node, void 0, breakTokens(node.props.children));
|
|
56
|
+
return node;
|
|
42
57
|
}
|
|
43
58
|
const ROW_COLUMNS = {
|
|
44
59
|
inline: "grid-cols-[minmax(0,1fr)_minmax(13rem,20rem)] @max-[34rem]/settings-section:grid-cols-1",
|
|
@@ -73,7 +88,7 @@ function SettingsRow(props) {
|
|
|
73
88
|
return /* @__PURE__ */ jsxs("div", {
|
|
74
89
|
id,
|
|
75
90
|
"data-layout": layout,
|
|
76
|
-
className: cn("grid min-w-0 items-center gap-x-8 gap-y-3
|
|
91
|
+
className: cn("grid min-w-0 items-center gap-x-8 gap-y-3", GUTTER, SCROLL_OFFSET, DIVIDED, ROW_COLUMNS[hasValue && layout === "compact" ? "inline" : layout]),
|
|
77
92
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
78
93
|
className: "min-w-0",
|
|
79
94
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -121,7 +136,7 @@ function SettingsRow(props) {
|
|
|
121
136
|
children: [hasValue && /* @__PURE__ */ jsx("span", {
|
|
122
137
|
"data-slot": "settings-value",
|
|
123
138
|
className: cn("min-w-0 text-foreground text-sm [overflow-wrap:anywhere] @min-[34rem]/settings-section:text-right", mono && "font-mono"),
|
|
124
|
-
children: value
|
|
139
|
+
children: breakTokens(value)
|
|
125
140
|
}), control]
|
|
126
141
|
})]
|
|
127
142
|
});
|
|
@@ -142,7 +157,7 @@ function SettingsSection({ id, title, description, info, actions, tone = "defaul
|
|
|
142
157
|
className: cn("@container/settings-section min-w-0", SCROLL_OFFSET, danger && "border-[color:var(--surface-danger-border)]"),
|
|
143
158
|
children: [
|
|
144
159
|
/* @__PURE__ */ jsxs("div", {
|
|
145
|
-
className: "flex items-start justify-between gap-4
|
|
160
|
+
className: cn("flex items-start justify-between gap-4 @max-[34rem]/settings-section:flex-wrap", GUTTER),
|
|
146
161
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
147
162
|
className: "min-w-0",
|
|
148
163
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -173,6 +188,35 @@ function SettingsSection({ id, title, description, info, actions, tone = "defaul
|
|
|
173
188
|
]
|
|
174
189
|
});
|
|
175
190
|
}
|
|
191
|
+
const NOTE_TONE = {
|
|
192
|
+
default: "text-foreground",
|
|
193
|
+
muted: "text-muted-foreground",
|
|
194
|
+
warning: "text-[color:var(--surface-warning-text)]",
|
|
195
|
+
danger: ERROR_TEXT
|
|
196
|
+
};
|
|
197
|
+
const NOTE_GLYPH = {
|
|
198
|
+
warning: TriangleAlert,
|
|
199
|
+
danger: CircleAlert
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* A line or block of a {@link SettingsSection} that is not a row: an empty list, a loading line,
|
|
203
|
+
* a notice, a refusal or a figure. It takes the rows' gutters and divider, so it lines up with
|
|
204
|
+
* the rows and lists around it.
|
|
205
|
+
*/
|
|
206
|
+
function SettingsNote({ tone = "default", role, children }) {
|
|
207
|
+
const className = cn("min-w-0", GUTTER, DIVIDED, NOTE_TONE[tone]);
|
|
208
|
+
const glyph = NOTE_GLYPH[tone];
|
|
209
|
+
return glyph ? /* @__PURE__ */ jsx(GlyphLine, {
|
|
210
|
+
icon: glyph,
|
|
211
|
+
role,
|
|
212
|
+
className,
|
|
213
|
+
children
|
|
214
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
215
|
+
role,
|
|
216
|
+
className: cn("text-sm leading-5 [overflow-wrap:anywhere]", className),
|
|
217
|
+
children
|
|
218
|
+
});
|
|
219
|
+
}
|
|
176
220
|
const LIST_SLOTS = [
|
|
177
221
|
"leading",
|
|
178
222
|
"main",
|
|
@@ -251,7 +295,7 @@ const NARROW_STATUS = "@max-[34rem]/settings-section:col-start-[main] @max-[34re
|
|
|
251
295
|
* that name. Without a status column the state follows the meta, and without
|
|
252
296
|
* a menu column the ⋯ menu ends the action cell.
|
|
253
297
|
*/
|
|
254
|
-
function SettingsListItem({ leading, title, meta, status, error, action, menu }) {
|
|
298
|
+
function SettingsListItem({ leading, title, meta, status, tone, error, action, menu }) {
|
|
255
299
|
const columns = React.useContext(SettingsListContext);
|
|
256
300
|
const titleId = React.useId();
|
|
257
301
|
const reasonId = `${titleId}-reason`;
|
|
@@ -268,6 +312,11 @@ function SettingsListItem({ leading, title, meta, status, error, action, menu })
|
|
|
268
312
|
onClick: locked ? void 0 : action.onSelect,
|
|
269
313
|
children: action.label
|
|
270
314
|
});
|
|
315
|
+
const state = tone && status != null ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(StatusGlyph, { tone }), /* @__PURE__ */ jsx("span", {
|
|
316
|
+
className: "min-w-0",
|
|
317
|
+
children: status
|
|
318
|
+
})] }) : status;
|
|
319
|
+
const toned = tone && "flex items-center gap-1.5";
|
|
271
320
|
const actionCell = (actionButton || menuNode && !columns.menu) && /* @__PURE__ */ jsxs(Fragment$1, { children: [actionButton, !columns.menu && menuNode] });
|
|
272
321
|
return /* @__PURE__ */ jsxs("li", {
|
|
273
322
|
className: cn("col-span-full grid grid-cols-subgrid items-center px-5 py-3 @max-[34rem]/settings-section:px-4", DIVIDED),
|
|
@@ -282,15 +331,15 @@ function SettingsListItem({ leading, title, meta, status, error, action, menu })
|
|
|
282
331
|
/* @__PURE__ */ jsx("div", {
|
|
283
332
|
id: titleId,
|
|
284
333
|
className: "font-medium text-sm leading-5 text-foreground [overflow-wrap:anywhere] [&_a]:rounded-sm [&_a]:text-foreground [&_a]:underline-offset-4 [&_a:hover]:underline [&_a:focus-visible]:outline-2 [&_a:focus-visible]:outline-offset-2 [&_a:focus-visible]:outline-[var(--focus-border)]",
|
|
285
|
-
children: title
|
|
334
|
+
children: breakTokens(title)
|
|
286
335
|
}),
|
|
287
336
|
meta != null && /* @__PURE__ */ jsx("div", {
|
|
288
337
|
className: "text-sm leading-5 text-muted-foreground [overflow-wrap:anywhere]",
|
|
289
|
-
children: meta
|
|
338
|
+
children: breakTokens(meta)
|
|
290
339
|
}),
|
|
291
340
|
status != null && !columns.status && /* @__PURE__ */ jsx("div", {
|
|
292
|
-
className: "mt-1 text-sm leading-5 text-muted-foreground",
|
|
293
|
-
children:
|
|
341
|
+
className: cn("mt-1 text-sm leading-5 text-muted-foreground", toned),
|
|
342
|
+
children: state
|
|
294
343
|
}),
|
|
295
344
|
locked && /* @__PURE__ */ jsx(LockLine, {
|
|
296
345
|
id: reasonId,
|
|
@@ -304,8 +353,8 @@ function SettingsListItem({ leading, title, meta, status, error, action, menu })
|
|
|
304
353
|
children: error
|
|
305
354
|
}),
|
|
306
355
|
status != null && columns.status && /* @__PURE__ */ jsx("div", {
|
|
307
|
-
className: cn("col-start-[status] row-start-1 min-w-0 text-sm leading-5 text-muted-foreground", NARROW_STATUS),
|
|
308
|
-
children:
|
|
356
|
+
className: cn("col-start-[status] row-start-1 min-w-0 text-sm leading-5 text-muted-foreground", toned, NARROW_STATUS),
|
|
357
|
+
children: state
|
|
309
358
|
}),
|
|
310
359
|
actionCell && /* @__PURE__ */ jsx("div", {
|
|
311
360
|
className: "col-start-[action] row-start-1 flex min-w-0 items-center justify-end gap-1",
|
|
@@ -318,4 +367,4 @@ function SettingsListItem({ leading, title, meta, status, error, action, menu })
|
|
|
318
367
|
]
|
|
319
368
|
});
|
|
320
369
|
}
|
|
321
|
-
export { SettingsList, SettingsListItem, SettingsRow, SettingsSection };
|
|
370
|
+
export { SettingsList, SettingsListItem, SettingsNote, SettingsRow, SettingsSection };
|
|
@@ -107,15 +107,24 @@ function ToneGlyph({ tone }) {
|
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
+
const GLYPH = "inline-flex [&>svg]:size-3 [&>svg]:shrink-0";
|
|
111
|
+
/** The tone's glyph in the tone's ink, before a label that keeps its own colour, as `bare` draws it. */
|
|
112
|
+
function StatusGlyph({ tone }) {
|
|
113
|
+
return /* @__PURE__ */ jsx("span", {
|
|
114
|
+
"aria-hidden": "true",
|
|
115
|
+
className: cn(GLYPH, TONE_TEXT[tone]),
|
|
116
|
+
children: /* @__PURE__ */ jsx(ToneGlyph, { tone })
|
|
117
|
+
});
|
|
118
|
+
}
|
|
110
119
|
const StatusPill = React.forwardRef(({ className, tone, children, icon, bare = false, size = "sm", ...props }, ref) => /* @__PURE__ */ jsxs("span", {
|
|
111
120
|
ref,
|
|
112
121
|
className: cn("inline-flex items-center gap-1.5 whitespace-nowrap rounded-full font-medium", size === "sm" ? "px-2 py-0.5 text-xs" : "px-2.5 py-1 text-sm", bare ? "border border-transparent bg-transparent px-0" : cn("border", TONE_SURFACE[tone]), className),
|
|
113
122
|
...props,
|
|
114
123
|
children: [/* @__PURE__ */ jsx("span", {
|
|
115
124
|
"aria-hidden": "true",
|
|
116
|
-
className: cn(
|
|
125
|
+
className: cn(GLYPH, bare && TONE_TEXT[tone]),
|
|
117
126
|
children: icon ?? /* @__PURE__ */ jsx(ToneGlyph, { tone })
|
|
118
127
|
}), children]
|
|
119
128
|
}));
|
|
120
129
|
StatusPill.displayName = "StatusPill";
|
|
121
|
-
export { StatusPill };
|
|
130
|
+
export { StatusGlyph, StatusPill };
|
package/dist/primitives.d.ts
CHANGED
|
@@ -45,11 +45,11 @@ import { ThemeToggle, useTheme } from "./primitives/theme-toggle.js";
|
|
|
45
45
|
import { Heading, HeadingProps, HeadingVariant } from "./primitives/heading.js";
|
|
46
46
|
import { PageShell, PageShellProps } from "./primitives/page-shell.js";
|
|
47
47
|
import { Field, FieldProps, fieldMessageId } from "./primitives/field.js";
|
|
48
|
-
import {
|
|
48
|
+
import { StatusPill, StatusPillProps, StatusTone } from "./primitives/status-pill.js";
|
|
49
|
+
import { SettingsList, SettingsListAction, SettingsListColumns, SettingsListItem, SettingsListItemProps, SettingsListMenuItem, SettingsListProps, SettingsNote, SettingsNoteProps, SettingsRow, SettingsRowLayout, SettingsRowProps, SettingsSection, SettingsSectionProps } from "./primitives/settings.js";
|
|
49
50
|
import { PageHeader, PageHeaderProps } from "./primitives/page-header.js";
|
|
50
51
|
import { Alert, AlertDescription, AlertProps, AlertTitle, AlertTone } from "./primitives/alert.js";
|
|
51
52
|
import { Pagination, PaginationProps } from "./primitives/pagination.js";
|
|
52
|
-
import { StatusPill, StatusPillProps, StatusTone } from "./primitives/status-pill.js";
|
|
53
53
|
import { ComplianceBadge, ComplianceBadgeProps, ComplianceProduct, ComplianceStatus } from "./primitives/compliance-badge.js";
|
|
54
54
|
import { Tag, TagProps } from "./primitives/tag.js";
|
|
55
55
|
import { Chip, ChipProps } from "./primitives/chip.js";
|
|
@@ -62,4 +62,4 @@ import { SeriesSparkline, SeriesSparklineProps } from "./primitives/series-spark
|
|
|
62
62
|
import { ChartReferenceLine, MetricChart, MetricChartProps } from "./primitives/metric-chart.js";
|
|
63
63
|
import { FilterField, FilterFieldProps, Toolbar, ToolbarProps } from "./primitives/toolbar.js";
|
|
64
64
|
import { Logo, LogoProps, TangleKnot } from "@tangle-network/brand";
|
|
65
|
-
export { Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, type AlertTone, ArtifactPane, type ArtifactPaneProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, CATEGORY_TONES, Calendar, type CalendarDate, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type CardTitleProps, type CategoryTone, type ChartReferenceLine, Checkbox, Chip, type ChipProps, CodeBlock, type CodeBlockProps, Collapsible, CollapsibleContent, CollapsibleTrigger, ComplianceBadge, type ComplianceBadgeProps, type ComplianceProduct, type ComplianceStatus, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, DatePicker, type DatePickerProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, type DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, Field, type FieldProps, FilterField, type FilterFieldProps, Heading, type HeadingProps, type HeadingVariant, HelpText, type HelpTextProps, HoverCard, HoverCardContent, HoverCardDescription, HoverCardHeader, HoverCardTitle, HoverCardTrigger, IconTile, type IconTileProps, InlineCode, type InlineCodeProps, Input, type InputProps, Label, Logo, type LogoProps, Metric, MetricChart, type MetricChartProps, type MetricProps, MetricStrip, type MetricStripProps, PageHeader, type PageHeaderProps, PageShell, type PageShellProps, Pagination, type PaginationProps, type PeriodDelta, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeriesExtent, type SeriesPoint, type SeriesPointInput, type SeriesScale, SeriesSparkline, type SeriesSparklineProps, SettingsList, type SettingsListAction, type SettingsListColumns, SettingsListItem, type SettingsListItemProps, type SettingsListMenuItem, type SettingsListProps, SettingsRow, type SettingsRowLayout, type SettingsRowProps, SettingsSection, type SettingsSectionProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, type SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, type StatCardProps, StatusPill, type StatusPillProps, type StatusTone, Switch, TONE_CLASSES, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableProps, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, type TagProps, TangleKnot, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, type TextShimmerProps, Textarea, type TextareaProps, ThemeToggle, TimeSelect, type TimeSelectProps, type Toast, ToastContainer, ToastProvider, type Tone, type ToneClasses, Toolbar, type ToolbarProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UploadFile, UploadProgress, type UploadProgressProps, Wizard, type WizardProps, WizardStep, type WizardStepProps, badgeVariants, buttonVariants, fieldMessageId, formatCalendarDate, initialsOf, localToday, normalizeSeries, parseCalendarDate, periodDelta, seriesAreaPath, seriesExtent, seriesLinePath, seriesMean, seriesScale, toneFor, useTheme, useToast };
|
|
65
|
+
export { Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, type AlertTone, ArtifactPane, type ArtifactPaneProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, CATEGORY_TONES, Calendar, type CalendarDate, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type CardTitleProps, type CategoryTone, type ChartReferenceLine, Checkbox, Chip, type ChipProps, CodeBlock, type CodeBlockProps, Collapsible, CollapsibleContent, CollapsibleTrigger, ComplianceBadge, type ComplianceBadgeProps, type ComplianceProduct, type ComplianceStatus, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, DatePicker, type DatePickerProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, type DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, Field, type FieldProps, FilterField, type FilterFieldProps, Heading, type HeadingProps, type HeadingVariant, HelpText, type HelpTextProps, HoverCard, HoverCardContent, HoverCardDescription, HoverCardHeader, HoverCardTitle, HoverCardTrigger, IconTile, type IconTileProps, InlineCode, type InlineCodeProps, Input, type InputProps, Label, Logo, type LogoProps, Metric, MetricChart, type MetricChartProps, type MetricProps, MetricStrip, type MetricStripProps, PageHeader, type PageHeaderProps, PageShell, type PageShellProps, Pagination, type PaginationProps, type PeriodDelta, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeriesExtent, type SeriesPoint, type SeriesPointInput, type SeriesScale, SeriesSparkline, type SeriesSparklineProps, SettingsList, type SettingsListAction, type SettingsListColumns, SettingsListItem, type SettingsListItemProps, type SettingsListMenuItem, type SettingsListProps, SettingsNote, type SettingsNoteProps, SettingsRow, type SettingsRowLayout, type SettingsRowProps, SettingsSection, type SettingsSectionProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, type SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, type StatCardProps, StatusPill, type StatusPillProps, type StatusTone, Switch, TONE_CLASSES, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableProps, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, type TagProps, TangleKnot, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, type TextShimmerProps, Textarea, type TextareaProps, ThemeToggle, TimeSelect, type TimeSelectProps, type Toast, ToastContainer, ToastProvider, type Tone, type ToneClasses, Toolbar, type ToolbarProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UploadFile, UploadProgress, type UploadProgressProps, Wizard, type WizardProps, WizardStep, type WizardStepProps, badgeVariants, buttonVariants, fieldMessageId, formatCalendarDate, initialsOf, localToday, normalizeSeries, parseCalendarDate, periodDelta, seriesAreaPath, seriesExtent, seriesLinePath, seriesMean, seriesScale, toneFor, useTheme, useToast };
|
package/dist/primitives.js
CHANGED
|
@@ -43,11 +43,11 @@ import { InlineCode } from "./primitives/code-block.js";
|
|
|
43
43
|
import { ThemeToggle, useTheme } from "./primitives/theme-toggle.js";
|
|
44
44
|
import { PageShell } from "./primitives/page-shell.js";
|
|
45
45
|
import { Field, fieldMessageId } from "./primitives/field.js";
|
|
46
|
-
import {
|
|
46
|
+
import { StatusPill } from "./primitives/status-pill.js";
|
|
47
|
+
import { SettingsList, SettingsListItem, SettingsNote, SettingsRow, SettingsSection } from "./primitives/settings.js";
|
|
47
48
|
import { PageHeader } from "./primitives/page-header.js";
|
|
48
49
|
import { Alert, AlertDescription, AlertTitle } from "./primitives/alert.js";
|
|
49
50
|
import { Pagination } from "./primitives/pagination.js";
|
|
50
|
-
import { StatusPill } from "./primitives/status-pill.js";
|
|
51
51
|
import { ComplianceBadge } from "./primitives/compliance-badge.js";
|
|
52
52
|
import { CATEGORY_TONES, TONE_CLASSES, toneFor } from "./primitives/tone.js";
|
|
53
53
|
import { Tag } from "./primitives/tag.js";
|
|
@@ -62,4 +62,4 @@ import { SeriesSparkline } from "./primitives/series-sparkline.js";
|
|
|
62
62
|
import { MetricChart } from "./primitives/metric-chart.js";
|
|
63
63
|
import { FilterField, Toolbar } from "./primitives/toolbar.js";
|
|
64
64
|
import { Logo, TangleKnot } from "@tangle-network/brand";
|
|
65
|
-
export { Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, ArtifactPane, Avatar, AvatarFallback, AvatarImage, Badge, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, CATEGORY_TONES, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Chip, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, ComplianceBadge, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, DatePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, Field, FilterField, Heading, HelpText, HoverCard, HoverCardContent, HoverCardDescription, HoverCardHeader, HoverCardTitle, HoverCardTrigger, IconTile, InlineCode, Input, Label, Logo, Metric, MetricChart, MetricStrip, PageHeader, PageShell, Pagination, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, SegmentedControl, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, SeriesSparkline, SettingsList, SettingsListItem, SettingsRow, SettingsSection, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, StatusPill, Switch, TONE_CLASSES, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, TangleKnot, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, Textarea, ThemeToggle, TimeSelect, ToastContainer, ToastProvider, Toolbar, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadProgress, Wizard, WizardStep, badgeVariants, buttonVariants, fieldMessageId, formatCalendarDate, initialsOf, localToday, normalizeSeries, parseCalendarDate, periodDelta, seriesAreaPath, seriesExtent, seriesLinePath, seriesMean, seriesScale, toneFor, useTheme, useToast };
|
|
65
|
+
export { Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, ArtifactPane, Avatar, AvatarFallback, AvatarImage, Badge, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, CATEGORY_TONES, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Chip, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, ComplianceBadge, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, DatePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, Field, FilterField, Heading, HelpText, HoverCard, HoverCardContent, HoverCardDescription, HoverCardHeader, HoverCardTitle, HoverCardTrigger, IconTile, InlineCode, Input, Label, Logo, Metric, MetricChart, MetricStrip, PageHeader, PageShell, Pagination, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, SegmentedControl, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, SeriesSparkline, SettingsList, SettingsListItem, SettingsNote, SettingsRow, SettingsSection, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, StatusPill, Switch, TONE_CLASSES, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, TangleKnot, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, Textarea, ThemeToggle, TimeSelect, ToastContainer, ToastProvider, Toolbar, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadProgress, Wizard, WizardStep, badgeVariants, buttonVariants, fieldMessageId, formatCalendarDate, initialsOf, localToday, normalizeSeries, parseCalendarDate, periodDelta, seriesAreaPath, seriesExtent, seriesLinePath, seriesMean, seriesScale, toneFor, useTheme, useToast };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/ui",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.50.2",
|
|
4
4
|
"description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -166,7 +166,7 @@
|
|
|
166
166
|
"peerDependencies": {
|
|
167
167
|
"@hocuspocus/provider": ">=2.15.0 <5.0.0",
|
|
168
168
|
"@nanostores/react": ">=0.8.4 <3.0.0",
|
|
169
|
-
"@tangle-network/brand": "^1.
|
|
169
|
+
"@tangle-network/brand": "^1.31.0",
|
|
170
170
|
"@tiptap/core": ">=3.20.4 <4.0.0",
|
|
171
171
|
"@tiptap/extension-collaboration": ">=3.20.4 <4.0.0",
|
|
172
172
|
"@tiptap/extension-collaboration-caret": ">=3.7.0 <4.0.0",
|
|
@@ -307,3 +307,12 @@ describe("transcript type scale", () => {
|
|
|
307
307
|
expect(textClasses(agent)).toEqual(textClasses(user))
|
|
308
308
|
})
|
|
309
309
|
})
|
|
310
|
+
|
|
311
|
+
describe("AgentTimeline status cards", () => {
|
|
312
|
+
it("marks a failed card with the failed glyph and keeps the triangle for warnings", () => {
|
|
313
|
+
const glyph = (tone: "error" | "warning") =>
|
|
314
|
+
render(<AgentTimeline items={[{ id: tone, kind: "status", label: tone, tone }]} />).container.querySelector("svg")?.getAttribute("class")
|
|
315
|
+
expect(glyph("error")).toContain("lucide-circle-x")
|
|
316
|
+
expect(glyph("warning")).toContain("lucide-triangle-alert")
|
|
317
|
+
})
|
|
318
|
+
})
|
|
@@ -2,7 +2,6 @@ import { TRANSCRIPT_TEXT } from "./transcript-text";
|
|
|
2
2
|
import { useState, type KeyboardEvent, type ReactNode } from "react";
|
|
3
3
|
import {
|
|
4
4
|
AlertTriangle,
|
|
5
|
-
CheckCircle2,
|
|
6
5
|
CircleDot,
|
|
7
6
|
FileText,
|
|
8
7
|
Info,
|
|
@@ -16,6 +15,7 @@ import { Markdown } from "../markdown/markdown";
|
|
|
16
15
|
import { ThinkingIndicator } from "./thinking-indicator";
|
|
17
16
|
import { type ToolCallData } from "../run/tool-call-feed";
|
|
18
17
|
import { ToolCallGroup, ToolCallStep } from "../run/tool-call-step";
|
|
18
|
+
import { statusMeta } from "../run/status-meta";
|
|
19
19
|
import type { ChatAuthor } from "../types/message";
|
|
20
20
|
import type { ToolPart } from "../types/parts";
|
|
21
21
|
|
|
@@ -132,7 +132,7 @@ const TONE_STYLES: Record<AgentTimelineTone, { card: string; text: string; suppo
|
|
|
132
132
|
card: "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)]",
|
|
133
133
|
text: "text-[var(--surface-success-text)]",
|
|
134
134
|
supportingText: "text-[var(--surface-success-text)]",
|
|
135
|
-
icon:
|
|
135
|
+
icon: statusMeta("done").icon,
|
|
136
136
|
},
|
|
137
137
|
warning: {
|
|
138
138
|
card: "border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)]",
|
|
@@ -144,7 +144,8 @@ const TONE_STYLES: Record<AgentTimelineTone, { card: string; text: string; suppo
|
|
|
144
144
|
card: "border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)]",
|
|
145
145
|
text: "text-[var(--surface-danger-text)]",
|
|
146
146
|
supportingText: "text-[var(--surface-danger-text)]",
|
|
147
|
-
|
|
147
|
+
// Failed is always the failed glyph; the triangle is for warnings.
|
|
148
|
+
icon: statusMeta("failed").icon,
|
|
148
149
|
},
|
|
149
150
|
};
|
|
150
151
|
|
|
@@ -52,6 +52,7 @@ describe("SpeakerHeader", () => {
|
|
|
52
52
|
expect(screen.getByText("@writer")).toBeInTheDocument()
|
|
53
53
|
expect(screen.getByText("Answering")).toBeInTheDocument()
|
|
54
54
|
expect(container.querySelector("[data-speaker-state]")?.getAttribute("data-speaker-state")).toBe("answering")
|
|
55
|
+
expect(container.querySelector("[data-run-status]")?.getAttribute("data-run-status")).toBe("running")
|
|
55
56
|
})
|
|
56
57
|
|
|
57
58
|
it("says nothing more once the responder has ended, even while others answer", () => {
|
|
@@ -62,8 +63,9 @@ describe("SpeakerHeader", () => {
|
|
|
62
63
|
})
|
|
63
64
|
|
|
64
65
|
it("shows a failed responder as failed while the turn still runs", () => {
|
|
65
|
-
render(<SpeakerHeader speaker={{ id: "ap_research", name: "Research Lead", state: "failed" }} live />)
|
|
66
|
+
const { container } = render(<SpeakerHeader speaker={{ id: "ap_research", name: "Research Lead", state: "failed" }} live />)
|
|
66
67
|
expect(screen.getByText("Failed")).toBeInTheDocument()
|
|
68
|
+
expect(container.querySelector("[data-run-status]")?.getAttribute("data-run-status")).toBe("failed")
|
|
67
69
|
expect(screen.queryByText("Answering")).not.toBeInTheDocument()
|
|
68
70
|
})
|
|
69
71
|
|
package/src/chat/speaker.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HTMLAttributes } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { RunStatusIcon } from "../run/status-meta";
|
|
3
3
|
import { MessageAuthor } from "./message-author";
|
|
4
4
|
|
|
5
5
|
/** Where one responder is in its answer. */
|
|
@@ -79,9 +79,9 @@ export interface SpeakerHeaderProps extends Omit<HTMLAttributes<HTMLDivElement>,
|
|
|
79
79
|
* gets, then the handle a person types to address the agent, then its state.
|
|
80
80
|
*
|
|
81
81
|
* The state comes from the speaker's own end state, else from `live`. An
|
|
82
|
-
* answering responder shows the
|
|
83
|
-
* failed
|
|
84
|
-
* run row does. `data-speaker-state` carries all three.
|
|
82
|
+
* answering responder shows the running glyph with the word, a failed one the
|
|
83
|
+
* failed glyph with the word, both from `RunStatusIcon`, and a finished one
|
|
84
|
+
* nothing, as a settled run row does. `data-speaker-state` carries all three.
|
|
85
85
|
*/
|
|
86
86
|
export function SpeakerHeader({ speaker, live = false, ...props }: SpeakerHeaderProps) {
|
|
87
87
|
const state: SpeakerState = speaker.state ?? (live ? "answering" : "done");
|
|
@@ -92,19 +92,18 @@ export function SpeakerHeader({ speaker, live = false, ...props }: SpeakerHeader
|
|
|
92
92
|
{...props}
|
|
93
93
|
>
|
|
94
94
|
{speaker.handle ? (
|
|
95
|
-
|
|
95
|
+
// A person reads the name first. The handle has no base width and grows only into the space
|
|
96
|
+
// the name and state leave, up to its own width, so it gives way before the name loses a pixel.
|
|
97
|
+
<span className="min-w-0 max-w-fit flex-1 truncate text-muted-foreground text-sm" title={`@${speaker.handle}`}>
|
|
96
98
|
@{speaker.handle}
|
|
97
99
|
</span>
|
|
98
100
|
) : null}
|
|
99
|
-
{state === "
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
Failed
|
|
106
|
-
</StatusPill>
|
|
107
|
-
) : null}
|
|
101
|
+
{state === "done" ? null : (
|
|
102
|
+
<span className="inline-flex shrink-0 items-center gap-1.5 font-medium text-sm">
|
|
103
|
+
<RunStatusIcon status={state === "failed" ? "failed" : "running"} aria-hidden />
|
|
104
|
+
{state === "failed" ? "Failed" : "Answering"}
|
|
105
|
+
</span>
|
|
106
|
+
)}
|
|
108
107
|
</MessageAuthor>
|
|
109
108
|
);
|
|
110
109
|
}
|
|
@@ -142,7 +142,7 @@ export const codePresentation = {
|
|
|
142
142
|
tabs: `tangle-code-tabs ${tabsPresentation.list.underline}`,
|
|
143
143
|
tab: tabsPresentation.trigger.underline,
|
|
144
144
|
root: "group relative min-w-0 overflow-hidden rounded-lg border font-mono bg-card border-border",
|
|
145
|
-
header: "flex flex-wrap items-center justify-between gap-3 border-b border-border px-3 py-1.5 bg-[var(--code-header-bg,hsl(var(--background)))]",
|
|
145
|
+
header: "flex flex-wrap items-center justify-between gap-3 border-b border-border px-3 py-1.5 font-sans text-sm bg-[var(--code-header-bg,hsl(var(--background)))]",
|
|
146
146
|
copy: "flex items-center justify-center min-w-8 min-h-8 pointer-coarse:min-w-11 pointer-coarse:min-h-11 rounded-md bg-muted border border-border hover:border-[var(--border-strong)] transition-colors data-[copy-loading=true]:after:content-['…'] data-[copy-loading=true]:gap-1",
|
|
147
147
|
copyStatus: "sr-only data-[copy-long-work=true]:not-sr-only data-[copy-long-work=true]:text-xs data-[copy-long-work=true]:text-muted-foreground",
|
|
148
148
|
};
|
|
@@ -188,12 +188,6 @@ describe("CodeBlock", () => {
|
|
|
188
188
|
expect(screen.getByRole("button", { name: "Copied" })).toBeTruthy();
|
|
189
189
|
});
|
|
190
190
|
|
|
191
|
-
it("renders the header label at the 14px text-sm step", () => {
|
|
192
|
-
const { getByText } = render(<CodeBlock code={"x"} label="YAML" />);
|
|
193
|
-
expect(getByText("YAML").className).toContain("text-sm");
|
|
194
|
-
expect(getByText("YAML").className).not.toContain("calc(");
|
|
195
|
-
});
|
|
196
|
-
|
|
197
191
|
it("announces a failed copy", async () => {
|
|
198
192
|
const writeText = vi.fn().mockRejectedValue(new Error("denied"));
|
|
199
193
|
Object.defineProperty(navigator, "clipboard", { value: { writeText }, configurable: true });
|
|
@@ -83,13 +83,13 @@ export const CodeBlock = memo(
|
|
|
83
83
|
<div className={codePresentation.header}>
|
|
84
84
|
{metadata && <span className="tangle-api-meta">{apiMetadataText(metadata)}</span>}
|
|
85
85
|
{filename ? (
|
|
86
|
-
<span className="flex min-w-0 items-center gap-2
|
|
86
|
+
<span className="flex min-w-0 items-center gap-2">
|
|
87
87
|
<FileCode2 aria-hidden className="size-4 shrink-0 text-muted-foreground" />
|
|
88
88
|
<span className="min-w-0 truncate font-mono font-medium text-foreground" title={filename}>{filename}</span>
|
|
89
89
|
{languageName && <span className="shrink-0 text-muted-foreground">{languageName}</span>}
|
|
90
90
|
</span>
|
|
91
91
|
) : (
|
|
92
|
-
<span className="font-
|
|
92
|
+
<span className="font-medium text-muted-foreground">{headerLabel}</span>
|
|
93
93
|
)}
|
|
94
94
|
{children}
|
|
95
95
|
{examples?.length ? <CopyButton text={code}/> : null}
|
|
@@ -169,7 +169,8 @@ export const CopyButton = memo(({ text, label: actionLabel, copiedLabel="Copied"
|
|
|
169
169
|
onClick={handleCopy}
|
|
170
170
|
className={cn(
|
|
171
171
|
codePresentation.copy,
|
|
172
|
-
|
|
172
|
+
// The header's type, not the code font the block sets.
|
|
173
|
+
actionLabel && "gap-2 px-3 font-sans text-sm",
|
|
173
174
|
focusRing,
|
|
174
175
|
className,
|
|
175
176
|
)}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
2
|
import { Markdown } from './markdown'
|
|
3
3
|
import { CodeBlock, CopyButton } from './code-block'
|
|
4
|
+
import { codePresentation } from '../lib/code-presentation'
|
|
4
5
|
|
|
5
6
|
// ─── Markdown ────────────────────────────────────────────────────────────────
|
|
6
7
|
|
|
@@ -221,6 +222,25 @@ export const CodeBlockOnly: Story = {
|
|
|
221
222
|
),
|
|
222
223
|
}
|
|
223
224
|
|
|
225
|
+
export const CodeBlockLabelledCopy: Story = {
|
|
226
|
+
name: 'CodeBlock — labelled copy action',
|
|
227
|
+
render: () => (
|
|
228
|
+
<div className="w-full max-w-[680px] p-6 rounded-xl bg-card">
|
|
229
|
+
<CodeBlock code={`<script src="https://embed.tangle.tools/agent.js" data-agent="support"></script>`} language="html" label="HTML">
|
|
230
|
+
<CopyButton text={`<script src="https://embed.tangle.tools/agent.js" data-agent="support"></script>`} label="Copy embed code" />
|
|
231
|
+
</CodeBlock>
|
|
232
|
+
{/* A server-rendered host (tangle-router) builds the same header from codePresentation alone. */}
|
|
233
|
+
<div className={`${codePresentation.root} mt-4`}>
|
|
234
|
+
<div className={codePresentation.header}>
|
|
235
|
+
<span>bash</span>
|
|
236
|
+
<button type="button" className={codePresentation.copy}>Copy</button>
|
|
237
|
+
</div>
|
|
238
|
+
<pre className="p-3"><code>export TANGLE_API_KEY="sk-tan-..."</code></pre>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
),
|
|
242
|
+
}
|
|
243
|
+
|
|
224
244
|
export const CodeBlockNoLanguage: Story = {
|
|
225
245
|
name: 'CodeBlock — no language',
|
|
226
246
|
render: () => (
|
package/src/presentation.ts
CHANGED
|
@@ -110,6 +110,7 @@ export const metricPresentation = {
|
|
|
110
110
|
} as const;
|
|
111
111
|
|
|
112
112
|
export const metricLayouts = {
|
|
113
|
+
2: "grid-cols-1 sm:grid-cols-2 max-sm:[&>div+div]:border-t sm:[&>div:not(:nth-child(2n+1))]:border-l",
|
|
113
114
|
3: "grid-cols-2 sm:grid-cols-3 max-sm:[&>div:not(:nth-child(2n+1))]:border-l sm:[&>div:not(:nth-child(3n+1))]:border-l",
|
|
114
115
|
4: "grid-cols-2 sm:grid-cols-4 max-sm:[&>div:not(:nth-child(2n+1))]:border-l sm:[&>div:not(:nth-child(4n+1))]:border-l",
|
|
115
116
|
5: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 max-sm:[&>div:not(:nth-child(2n+1))]:border-l sm:max-lg:[&>div:not(:nth-child(3n+1))]:border-l lg:[&>div:not(:nth-child(5n+1))]:border-l",
|
|
@@ -180,6 +181,73 @@ export function bindDialog(dialog: HTMLDialogElement, trigger: HTMLElement): ()
|
|
|
180
181
|
return () => {trigger.removeEventListener("click",show);dialog.removeEventListener("close",close);dialog.removeEventListener("click",click);dialog.removeEventListener("keydown",key);};
|
|
181
182
|
}
|
|
182
183
|
|
|
184
|
+
/**
|
|
185
|
+
* The settings section links of PageShell `measure="settings"`, for any host: PageShell binds its
|
|
186
|
+
* nav with it, and a server-rendered host binds the same markup. On the tab strip the current link
|
|
187
|
+
* (`aria-current="page"`) is centred by scrolling the strip only, never the page. It centres when
|
|
188
|
+
* the current link changes or first becomes reachable, never on scroll or on a change that keeps
|
|
189
|
+
* the same current link, so a user who scrolled to the first or last section stays there; resize
|
|
190
|
+
* and font swaps re-centre only until the user first scrolls the strip. A plain click on a link to
|
|
191
|
+
* another document makes it current at once, while that document loads, and a page restored from
|
|
192
|
+
* the back-forward cache shows the link it left.
|
|
193
|
+
*/
|
|
194
|
+
export function bindSettingsNav(nav: HTMLElement): () => void {
|
|
195
|
+
let centredKey: string | null = null;
|
|
196
|
+
let userScrolled = false;
|
|
197
|
+
let left: { from: Element | null; to: Element } | null = null;
|
|
198
|
+
const current = () => nav.querySelector<HTMLElement>('[aria-current="page"]');
|
|
199
|
+
const reveal = (layoutOnly: boolean) => {
|
|
200
|
+
const link = current();
|
|
201
|
+
if (nav.scrollWidth <= nav.clientWidth || !link) return;
|
|
202
|
+
const key = link.getAttribute("href") ?? link.textContent ?? "";
|
|
203
|
+
if (key !== centredKey) userScrolled = false;
|
|
204
|
+
else if (!layoutOnly || userScrolled) return;
|
|
205
|
+
centredKey = key;
|
|
206
|
+
const strip = nav.getBoundingClientRect(), box = link.getBoundingClientRect();
|
|
207
|
+
nav.scrollLeft += box.left + box.width / 2 - (strip.left + strip.width / 2);
|
|
208
|
+
};
|
|
209
|
+
const mark = (from: Element | null, to: Element | null) => { from?.removeAttribute("aria-current"); to?.setAttribute("aria-current", "page"); };
|
|
210
|
+
// On window, so it runs after the page's own handlers: a client router that took the click
|
|
211
|
+
// (preventDefault) sets the current link itself.
|
|
212
|
+
const click = (event: MouseEvent) => {
|
|
213
|
+
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
214
|
+
const link = event.target instanceof Element ? event.target.closest("a[href]") : null;
|
|
215
|
+
if (!(link instanceof HTMLAnchorElement) || !nav.contains(link) || (link.target && link.target !== "_self") || link.hasAttribute("download")) return;
|
|
216
|
+
if (link.origin !== location.origin || link.pathname + link.search === location.pathname + location.search) return;
|
|
217
|
+
const from = current();
|
|
218
|
+
if (from === link) return;
|
|
219
|
+
left = { from, to: link };
|
|
220
|
+
mark(from, link);
|
|
221
|
+
};
|
|
222
|
+
const restore = (event: PageTransitionEvent) => {
|
|
223
|
+
if (!event.persisted || !left) return;
|
|
224
|
+
mark(left.to, left.from);
|
|
225
|
+
left = null;
|
|
226
|
+
};
|
|
227
|
+
const onUser = () => { userScrolled = true; };
|
|
228
|
+
const userEvents = ["pointerdown", "touchstart", "wheel", "keydown"] as const;
|
|
229
|
+
for (const type of userEvents) nav.addEventListener(type, onUser, { passive: true });
|
|
230
|
+
window.addEventListener("click", click);
|
|
231
|
+
window.addEventListener("pageshow", restore);
|
|
232
|
+
let live = true;
|
|
233
|
+
reveal(false);
|
|
234
|
+
// A web font swap widens the tabs without resizing the strip.
|
|
235
|
+
void document.fonts?.ready.then(() => { if (live) reveal(true); });
|
|
236
|
+
const resize = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => reveal(true));
|
|
237
|
+
resize?.observe(nav);
|
|
238
|
+
// Links a host replaces (sections loaded later, a new aria-current) can change the current link.
|
|
239
|
+
const links = new MutationObserver(() => reveal(false));
|
|
240
|
+
links.observe(nav, { subtree: true, childList: true, characterData: true, attributes: true, attributeFilter: ["aria-current"] });
|
|
241
|
+
return () => {
|
|
242
|
+
live = false;
|
|
243
|
+
resize?.disconnect();
|
|
244
|
+
links.disconnect();
|
|
245
|
+
for (const type of userEvents) nav.removeEventListener(type, onUser);
|
|
246
|
+
window.removeEventListener("click", click);
|
|
247
|
+
window.removeEventListener("pageshow", restore);
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
183
251
|
export const labelVariants = cva(
|
|
184
252
|
"font-medium text-[length:var(--font-size-label,0.875rem)] leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
|
185
253
|
);
|
package/src/primitives/index.ts
CHANGED
|
@@ -203,13 +203,14 @@ export type { PageShellProps } from "./page-shell";
|
|
|
203
203
|
export { Field, fieldMessageId } from "./field";
|
|
204
204
|
export type { FieldProps } from "./field";
|
|
205
205
|
|
|
206
|
-
export { SettingsList, SettingsListItem, SettingsRow, SettingsSection } from "./settings";
|
|
206
|
+
export { SettingsList, SettingsListItem, SettingsNote, SettingsRow, SettingsSection } from "./settings";
|
|
207
207
|
export type {
|
|
208
208
|
SettingsListAction,
|
|
209
209
|
SettingsListColumns,
|
|
210
210
|
SettingsListItemProps,
|
|
211
211
|
SettingsListMenuItem,
|
|
212
212
|
SettingsListProps,
|
|
213
|
+
SettingsNoteProps,
|
|
213
214
|
SettingsRowLayout,
|
|
214
215
|
SettingsRowProps,
|
|
215
216
|
SettingsSectionProps,
|
|
@@ -8,8 +8,8 @@ export interface MetricStripProps
|
|
|
8
8
|
extends React.HTMLAttributes<HTMLDListElement> {
|
|
9
9
|
children: React.ReactNode;
|
|
10
10
|
/** Desktop columns. Default retains the existing two / sm:four layout.
|
|
11
|
-
*
|
|
12
|
-
columns?: 3 | 4 | 5 | 6;
|
|
11
|
+
* Two stack below sm; five and six use three columns between sm and lg. Never inferred from data. */
|
|
12
|
+
columns?: 2 | 3 | 4 | 5 | 6;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const MetricStrip = React.forwardRef<HTMLDListElement, MetricStripProps>(
|