@tangle-network/ui 11.49.0 → 11.50.1

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.
@@ -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 ErrorLine({ id, className, children }) {
18
+ function GlyphLine({ icon: Icon, className, children, ...props }) {
17
19
  return /* @__PURE__ */ jsxs("div", {
18
- id,
19
- role: "alert",
20
- className: cn("flex items-start gap-1.5 text-sm leading-5 [overflow-wrap:anywhere]", ERROR_TEXT, className),
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
- function LockLine({ id, className, children }) {
31
- return /* @__PURE__ */ jsxs("div", {
32
- id,
33
- className: cn("flex items-start gap-1.5 text-sm leading-5 text-muted-foreground [overflow-wrap:anywhere]", className),
34
- children: [/* @__PURE__ */ jsx(Lock, {
35
- "aria-hidden": "true",
36
- className: "mt-0.5 size-4 shrink-0"
37
- }), /* @__PURE__ */ jsx("div", {
38
- className: "min-w-0",
39
- children
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 px-5 py-4 @max-[34rem]/settings-section:px-4", SCROLL_OFFSET, DIVIDED, ROW_COLUMNS[hasValue && layout === "compact" ? "inline" : layout]),
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 px-5 py-4 @max-[34rem]/settings-section:flex-wrap @max-[34rem]/settings-section:px-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: status
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: status
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("inline-flex [&>svg]:size-3 [&>svg]:shrink-0", bare && TONE_TEXT[tone]),
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 };
@@ -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 { SettingsList, SettingsListAction, SettingsListColumns, SettingsListItem, SettingsListItemProps, SettingsListMenuItem, SettingsListProps, SettingsRow, SettingsRowLayout, SettingsRowProps, SettingsSection, SettingsSectionProps } from "./primitives/settings.js";
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 };
@@ -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 { SettingsList, SettingsListItem, SettingsRow, SettingsSection } from "./primitives/settings.js";
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.49.0",
3
+ "version": "11.50.1",
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.30.0",
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: CheckCircle2,
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
- icon: AlertTriangle,
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
 
@@ -1,5 +1,5 @@
1
1
  import type { HTMLAttributes } from "react";
2
- import { StatusPill } from "../primitives/status-pill";
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 status grammar's open ring with the word, a
83
- * failed one its cross with the word, and a finished one nothing, as a settled
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
- <span className="min-w-0 truncate text-muted-foreground text-sm" title={`@${speaker.handle}`}>
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 === "answering" ? (
100
- <StatusPill tone="running" bare size="md" className="shrink-0 py-0">
101
- Answering
102
- </StatusPill>
103
- ) : state === "failed" ? (
104
- <StatusPill tone="danger" bare size="md" className="shrink-0 py-0">
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
  }
@@ -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
- actionLabel && "gap-2 px-3",
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
  )}
@@ -221,6 +221,17 @@ export const CodeBlockOnly: Story = {
221
221
  ),
222
222
  }
223
223
 
224
+ export const CodeBlockLabelledCopy: Story = {
225
+ name: 'CodeBlock — labelled copy action',
226
+ render: () => (
227
+ <div className="w-full max-w-[680px] p-6 rounded-xl bg-card">
228
+ <CodeBlock code={`<script src="https://embed.tangle.tools/agent.js" data-agent="support"></script>`} language="html" label="HTML">
229
+ <CopyButton text={`<script src="https://embed.tangle.tools/agent.js" data-agent="support"></script>`} label="Copy embed code" />
230
+ </CodeBlock>
231
+ </div>
232
+ ),
233
+ }
234
+
224
235
  export const CodeBlockNoLanguage: Story = {
225
236
  name: 'CodeBlock — no language',
226
237
  render: () => (
@@ -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
  );
@@ -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
- * Five and six use three columns between sm and lg. Never inferred from data. */
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>(