@corbits/react-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +501 -0
- package/README.md +125 -0
- package/dist/blocks/login/auth-layout.d.ts +38 -0
- package/dist/blocks/login/auth-layout.js +52 -0
- package/dist/blocks/login/login-form.d.ts +60 -0
- package/dist/blocks/login/login-form.js +151 -0
- package/dist/hooks/use-command-palette-navigation.d.ts +25 -0
- package/dist/hooks/use-command-palette-navigation.js +52 -0
- package/dist/hooks/use-controllable-state.d.ts +28 -0
- package/dist/hooks/use-controllable-state.js +44 -0
- package/dist/hooks/use-delayed-autofocus.d.ts +9 -0
- package/dist/hooks/use-delayed-autofocus.js +20 -0
- package/dist/hooks/use-dismissable-popover.d.ts +37 -0
- package/dist/hooks/use-dismissable-popover.js +64 -0
- package/dist/hooks/use-list-selection.d.ts +66 -0
- package/dist/hooks/use-list-selection.js +147 -0
- package/dist/hooks/use-prefers-reduced-motion.d.ts +13 -0
- package/dist/hooks/use-prefers-reduced-motion.js +28 -0
- package/dist/hooks/use-scroll-current-into-view.d.ts +19 -0
- package/dist/hooks/use-scroll-current-into-view.js +33 -0
- package/dist/index.d.ts +122 -0
- package/dist/index.js +131 -0
- package/dist/lib/artifact.d.ts +72 -0
- package/dist/lib/artifact.js +67 -0
- package/dist/lib/chart-geometry.d.ts +62 -0
- package/dist/lib/chart-geometry.js +86 -0
- package/dist/lib/chart-palette.d.ts +76 -0
- package/dist/lib/chart-palette.js +92 -0
- package/dist/lib/chat-message.d.ts +115 -0
- package/dist/lib/chat-message.js +41 -0
- package/dist/lib/chat-parts.d.ts +64 -0
- package/dist/lib/chat-parts.js +16 -0
- package/dist/lib/csv.d.ts +60 -0
- package/dist/lib/csv.js +108 -0
- package/dist/lib/metrics.d.ts +70 -0
- package/dist/lib/metrics.js +101 -0
- package/dist/lib/now-item.d.ts +65 -0
- package/dist/lib/now-item.js +59 -0
- package/dist/lib/relative-time.d.ts +9 -0
- package/dist/lib/relative-time.js +47 -0
- package/dist/lib/theme.d.ts +30 -0
- package/dist/lib/theme.js +77 -0
- package/dist/lib/url.d.ts +26 -0
- package/dist/lib/url.js +37 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +5 -0
- package/dist/lib/workflow-display-flow.d.ts +83 -0
- package/dist/lib/workflow-display-flow.js +104 -0
- package/dist/lib/workflow-registry.d.ts +90 -0
- package/dist/lib/workflow-registry.js +56 -0
- package/dist/lib/workflow-run-error.d.ts +31 -0
- package/dist/lib/workflow-run-error.js +105 -0
- package/dist/lib/workflow-run-progress.d.ts +97 -0
- package/dist/lib/workflow-run-progress.js +135 -0
- package/dist/lib/workflow-run.d.ts +43 -0
- package/dist/lib/workflow-run.js +56 -0
- package/dist/styles.css +3080 -0
- package/dist/test/render-hook.d.ts +14 -0
- package/dist/test/render-hook.js +38 -0
- package/dist/theme.css +628 -0
- package/dist/ui/agent-turn.d.ts +26 -0
- package/dist/ui/agent-turn.js +60 -0
- package/dist/ui/animated-number.d.ts +25 -0
- package/dist/ui/animated-number.js +63 -0
- package/dist/ui/approval-card.d.ts +46 -0
- package/dist/ui/approval-card.js +116 -0
- package/dist/ui/artifact-body.d.ts +26 -0
- package/dist/ui/artifact-body.js +170 -0
- package/dist/ui/artifact-notice.d.ts +21 -0
- package/dist/ui/artifact-notice.js +36 -0
- package/dist/ui/avatar.d.ts +37 -0
- package/dist/ui/avatar.js +73 -0
- package/dist/ui/badge.d.ts +10 -0
- package/dist/ui/badge.js +35 -0
- package/dist/ui/bar-chart.d.ts +38 -0
- package/dist/ui/bar-chart.js +118 -0
- package/dist/ui/block-card.d.ts +29 -0
- package/dist/ui/block-card.js +66 -0
- package/dist/ui/boot-screen.d.ts +23 -0
- package/dist/ui/boot-screen.js +49 -0
- package/dist/ui/bulk-action-bar.d.ts +37 -0
- package/dist/ui/bulk-action-bar.js +64 -0
- package/dist/ui/button.d.ts +12 -0
- package/dist/ui/button.js +73 -0
- package/dist/ui/card.d.ts +6 -0
- package/dist/ui/card.js +37 -0
- package/dist/ui/category-bars.d.ts +20 -0
- package/dist/ui/category-bars.js +69 -0
- package/dist/ui/chart-frame.d.ts +54 -0
- package/dist/ui/chart-frame.js +108 -0
- package/dist/ui/chat-composer.d.ts +24 -0
- package/dist/ui/chat-composer.js +32 -0
- package/dist/ui/chat-dock-timing.d.ts +15 -0
- package/dist/ui/chat-dock-timing.js +11 -0
- package/dist/ui/chat-input.d.ts +38 -0
- package/dist/ui/chat-input.js +138 -0
- package/dist/ui/chat-panel.d.ts +20 -0
- package/dist/ui/chat-panel.js +56 -0
- package/dist/ui/chat-thread.d.ts +28 -0
- package/dist/ui/chat-thread.js +61 -0
- package/dist/ui/checkbox.d.ts +25 -0
- package/dist/ui/checkbox.js +63 -0
- package/dist/ui/command-palette.d.ts +100 -0
- package/dist/ui/command-palette.js +341 -0
- package/dist/ui/compare-body.d.ts +52 -0
- package/dist/ui/compare-body.js +140 -0
- package/dist/ui/confirm-button.d.ts +29 -0
- package/dist/ui/confirm-button.js +41 -0
- package/dist/ui/corbits-mark.d.ts +29 -0
- package/dist/ui/corbits-mark.js +33 -0
- package/dist/ui/csv-table.d.ts +22 -0
- package/dist/ui/csv-table.js +85 -0
- package/dist/ui/dialog.d.ts +37 -0
- package/dist/ui/dialog.js +105 -0
- package/dist/ui/dictation-status-line.d.ts +20 -0
- package/dist/ui/dictation-status-line.js +38 -0
- package/dist/ui/dither-canvas.d.ts +31 -0
- package/dist/ui/dither-canvas.js +169 -0
- package/dist/ui/embed-body.d.ts +30 -0
- package/dist/ui/embed-body.js +92 -0
- package/dist/ui/empty-state.d.ts +26 -0
- package/dist/ui/empty-state.js +42 -0
- package/dist/ui/file-input.d.ts +25 -0
- package/dist/ui/file-input.js +66 -0
- package/dist/ui/filter-bar.d.ts +42 -0
- package/dist/ui/filter-bar.js +95 -0
- package/dist/ui/filter-chip.d.ts +14 -0
- package/dist/ui/filter-chip.js +23 -0
- package/dist/ui/gate-block.d.ts +16 -0
- package/dist/ui/gate-block.js +37 -0
- package/dist/ui/horizontal-stepper.d.ts +13 -0
- package/dist/ui/horizontal-stepper.js +65 -0
- package/dist/ui/input.d.ts +2 -0
- package/dist/ui/input.js +14 -0
- package/dist/ui/inspector-shell.d.ts +47 -0
- package/dist/ui/inspector-shell.js +98 -0
- package/dist/ui/intake-form.d.ts +39 -0
- package/dist/ui/intake-form.js +119 -0
- package/dist/ui/kind-card-grid.d.ts +23 -0
- package/dist/ui/kind-card-grid.js +43 -0
- package/dist/ui/library-page-header.d.ts +20 -0
- package/dist/ui/library-page-header.js +43 -0
- package/dist/ui/list-detail.d.ts +28 -0
- package/dist/ui/list-detail.js +55 -0
- package/dist/ui/live-run-banner.d.ts +13 -0
- package/dist/ui/live-run-banner.js +23 -0
- package/dist/ui/live-status-line.d.ts +22 -0
- package/dist/ui/live-status-line.js +37 -0
- package/dist/ui/menu.d.ts +18 -0
- package/dist/ui/menu.js +45 -0
- package/dist/ui/message-bubble.d.ts +24 -0
- package/dist/ui/message-bubble.js +64 -0
- package/dist/ui/mic-button.d.ts +35 -0
- package/dist/ui/mic-button.js +61 -0
- package/dist/ui/mic-permission-dialog.d.ts +20 -0
- package/dist/ui/mic-permission-dialog.js +50 -0
- package/dist/ui/notifications-bell.d.ts +28 -0
- package/dist/ui/notifications-bell.js +69 -0
- package/dist/ui/now-cards.d.ts +37 -0
- package/dist/ui/now-cards.js +116 -0
- package/dist/ui/page-panel.d.ts +37 -0
- package/dist/ui/page-panel.js +23 -0
- package/dist/ui/page-shell.d.ts +21 -0
- package/dist/ui/page-shell.js +26 -0
- package/dist/ui/parts-renderer.d.ts +22 -0
- package/dist/ui/parts-renderer.js +234 -0
- package/dist/ui/profile-card.d.ts +39 -0
- package/dist/ui/profile-card.js +124 -0
- package/dist/ui/progress-checklist.d.ts +25 -0
- package/dist/ui/progress-checklist.js +70 -0
- package/dist/ui/provider-mark.d.ts +32 -0
- package/dist/ui/provider-mark.js +31 -0
- package/dist/ui/quick-reply-chips.d.ts +14 -0
- package/dist/ui/quick-reply-chips.js +24 -0
- package/dist/ui/quote-card.d.ts +39 -0
- package/dist/ui/quote-card.js +66 -0
- package/dist/ui/reasoning-block.d.ts +35 -0
- package/dist/ui/reasoning-block.js +60 -0
- package/dist/ui/research-body.d.ts +70 -0
- package/dist/ui/research-body.js +270 -0
- package/dist/ui/rich-empty-state.d.ts +35 -0
- package/dist/ui/rich-empty-state.js +65 -0
- package/dist/ui/run-now-button.d.ts +23 -0
- package/dist/ui/run-now-button.js +42 -0
- package/dist/ui/section.d.ts +20 -0
- package/dist/ui/section.js +48 -0
- package/dist/ui/select.d.ts +2 -0
- package/dist/ui/select.js +22 -0
- package/dist/ui/selection-checkbox.d.ts +42 -0
- package/dist/ui/selection-checkbox.js +54 -0
- package/dist/ui/settings-panel.d.ts +35 -0
- package/dist/ui/settings-panel.js +68 -0
- package/dist/ui/shimmer-text.d.ts +19 -0
- package/dist/ui/shimmer-text.js +20 -0
- package/dist/ui/sidebar-item-row.d.ts +40 -0
- package/dist/ui/sidebar-item-row.js +64 -0
- package/dist/ui/sidebar-panel.d.ts +25 -0
- package/dist/ui/sidebar-panel.js +56 -0
- package/dist/ui/sidebar.d.ts +46 -0
- package/dist/ui/sidebar.js +111 -0
- package/dist/ui/skeleton.d.ts +13 -0
- package/dist/ui/skeleton.js +20 -0
- package/dist/ui/sortable-table.d.ts +43 -0
- package/dist/ui/sortable-table.js +103 -0
- package/dist/ui/sparkline.d.ts +27 -0
- package/dist/ui/sparkline.js +68 -0
- package/dist/ui/stat-grid.d.ts +55 -0
- package/dist/ui/stat-grid.js +87 -0
- package/dist/ui/status-dot.d.ts +13 -0
- package/dist/ui/status-dot.js +51 -0
- package/dist/ui/step-list.d.ts +21 -0
- package/dist/ui/step-list.js +77 -0
- package/dist/ui/switch.d.ts +23 -0
- package/dist/ui/switch.js +29 -0
- package/dist/ui/table.d.ts +8 -0
- package/dist/ui/table.js +48 -0
- package/dist/ui/tabs.d.ts +44 -0
- package/dist/ui/tabs.js +101 -0
- package/dist/ui/textarea.d.ts +8 -0
- package/dist/ui/textarea.js +32 -0
- package/dist/ui/theme-provider.d.ts +36 -0
- package/dist/ui/theme-provider.js +126 -0
- package/dist/ui/theme-toggle.d.ts +11 -0
- package/dist/ui/theme-toggle.js +32 -0
- package/dist/ui/thinking-indicator.d.ts +7 -0
- package/dist/ui/thinking-indicator.js +19 -0
- package/dist/ui/thinking-label.d.ts +19 -0
- package/dist/ui/thinking-label.js +54 -0
- package/dist/ui/thinking-mark.d.ts +10 -0
- package/dist/ui/thinking-mark.js +153 -0
- package/dist/ui/time-series-chart.d.ts +55 -0
- package/dist/ui/time-series-chart.js +249 -0
- package/dist/ui/toast.d.ts +16 -0
- package/dist/ui/toast.js +41 -0
- package/dist/ui/token-mosaic.d.ts +22 -0
- package/dist/ui/token-mosaic.js +65 -0
- package/dist/ui/tool-block.d.ts +54 -0
- package/dist/ui/tool-block.js +170 -0
- package/dist/ui/tool-narrative.d.ts +22 -0
- package/dist/ui/tool-narrative.js +77 -0
- package/dist/ui/tool-picker.d.ts +44 -0
- package/dist/ui/tool-picker.js +200 -0
- package/dist/ui/tooltip.d.ts +21 -0
- package/dist/ui/tooltip.js +48 -0
- package/dist/ui/top-bar.d.ts +53 -0
- package/dist/ui/top-bar.js +98 -0
- package/dist/ui/trace-waterfall.d.ts +38 -0
- package/dist/ui/trace-waterfall.js +121 -0
- package/dist/ui/typing-indicator.d.ts +9 -0
- package/dist/ui/typing-indicator.js +25 -0
- package/dist/ui/view-toggle.d.ts +17 -0
- package/dist/ui/view-toggle.js +46 -0
- package/dist/ui/voice-waveform.d.ts +16 -0
- package/dist/ui/voice-waveform.js +23 -0
- package/package.json +583 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { agentInitials, isTurnWorking, messageText, reasoningText, toolParts } from "../lib/chat-message.js";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
import { MessageBubble } from "./message-bubble.js";
|
|
5
|
+
import { ReasoningBlock } from "./reasoning-block.js";
|
|
6
|
+
import { ToolNarrative } from "./tool-narrative.js";
|
|
7
|
+
/**
|
|
8
|
+
* One agent reply, in the order it happened: what it thought, what it did, what
|
|
9
|
+
* it said.
|
|
10
|
+
*
|
|
11
|
+
* Parts are rendered in the order the model produced them rather than grouped
|
|
12
|
+
* by kind — a turn that thinks, calls a tool, thinks again and then answers
|
|
13
|
+
* reads as a sequence, and sorting it into "all thinking / all tools / answer"
|
|
14
|
+
* describes a turn that never happened.
|
|
15
|
+
*
|
|
16
|
+
* The avatar is `aria-hidden` and the turn is a labelled region instead: a
|
|
17
|
+
* screen reader should hear "Ada said …" once, not two initials followed by the
|
|
18
|
+
* name.
|
|
19
|
+
*/ export function AgentTurn({ message, identity, children, now, reasoningDurationLabel, className }) {
|
|
20
|
+
const working = isTurnWorking(message);
|
|
21
|
+
const answer = messageText(message);
|
|
22
|
+
const thinking = reasoningText(message);
|
|
23
|
+
const tools = toolParts(message);
|
|
24
|
+
const hasWork = thinking.length > 0 || tools.length > 0;
|
|
25
|
+
return /*#__PURE__*/ _jsxs("article", {
|
|
26
|
+
"data-slot": "agent-turn",
|
|
27
|
+
"aria-label": `${identity.name} replied`,
|
|
28
|
+
className: cn("flex gap-3", className),
|
|
29
|
+
children: [
|
|
30
|
+
/*#__PURE__*/ _jsx("span", {
|
|
31
|
+
"aria-hidden": true,
|
|
32
|
+
className: "mt-0.5 grid size-7 shrink-0 place-items-center rounded-full bg-secondary text-[10px] font-bold text-secondary-foreground",
|
|
33
|
+
children: agentInitials(identity)
|
|
34
|
+
}),
|
|
35
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
36
|
+
className: "flex min-w-0 flex-1 flex-col gap-1.5",
|
|
37
|
+
children: [
|
|
38
|
+
hasWork ? /*#__PURE__*/ _jsxs("div", {
|
|
39
|
+
className: "flex flex-col",
|
|
40
|
+
children: [
|
|
41
|
+
/*#__PURE__*/ _jsx(ReasoningBlock, {
|
|
42
|
+
text: thinking,
|
|
43
|
+
streaming: working,
|
|
44
|
+
durationLabel: reasoningDurationLabel
|
|
45
|
+
}),
|
|
46
|
+
tools.map((part)=>/*#__PURE__*/ _jsx(ToolNarrative, {
|
|
47
|
+
part: part
|
|
48
|
+
}, part.toolCallId))
|
|
49
|
+
]
|
|
50
|
+
}) : null,
|
|
51
|
+
answer.length > 0 || children !== undefined ? /*#__PURE__*/ _jsx(MessageBubble, {
|
|
52
|
+
message: message,
|
|
53
|
+
now: now,
|
|
54
|
+
children: children
|
|
55
|
+
}) : null
|
|
56
|
+
]
|
|
57
|
+
})
|
|
58
|
+
]
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type AnimatedNumberProps = {
|
|
2
|
+
readonly value: number;
|
|
3
|
+
/** Decimal places for the default formatting. Ignored when `format` is given. */
|
|
4
|
+
readonly decimals?: number;
|
|
5
|
+
readonly format?: (value: number) => string;
|
|
6
|
+
/** Duration of the count, in ms. */
|
|
7
|
+
readonly durationMs?: number;
|
|
8
|
+
readonly className?: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* A number that counts to its new value instead of snapping.
|
|
12
|
+
*
|
|
13
|
+
* Hand-rolled on `requestAnimationFrame` rather than pulling in a motion
|
|
14
|
+
* library: this registry has no animation dependency, and one component's
|
|
15
|
+
* count-up is not a good reason to add ~30kB that a consumer then owns.
|
|
16
|
+
*
|
|
17
|
+
* `tabular-nums`, so the digits do not change width mid-count and shove the
|
|
18
|
+
* layout around — a KPI row that jitters while it settles is worse than one
|
|
19
|
+
* that does not animate at all.
|
|
20
|
+
*
|
|
21
|
+
* Reduced motion is checked here rather than left to the theme's CSS override.
|
|
22
|
+
* The theme can collapse a CSS animation, but this is a JS interpolation the
|
|
23
|
+
* cascade cannot reach, so it snaps to the value instead.
|
|
24
|
+
*/
|
|
25
|
+
export declare function AnimatedNumber({ value, decimals, format, durationMs, className, }: AnimatedNumberProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { usePrefersReducedMotion } from "../hooks/use-prefers-reduced-motion.js";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
const easeOut = (t)=>1 - (1 - t) ** 3;
|
|
6
|
+
/**
|
|
7
|
+
* A number that counts to its new value instead of snapping.
|
|
8
|
+
*
|
|
9
|
+
* Hand-rolled on `requestAnimationFrame` rather than pulling in a motion
|
|
10
|
+
* library: this registry has no animation dependency, and one component's
|
|
11
|
+
* count-up is not a good reason to add ~30kB that a consumer then owns.
|
|
12
|
+
*
|
|
13
|
+
* `tabular-nums`, so the digits do not change width mid-count and shove the
|
|
14
|
+
* layout around — a KPI row that jitters while it settles is worse than one
|
|
15
|
+
* that does not animate at all.
|
|
16
|
+
*
|
|
17
|
+
* Reduced motion is checked here rather than left to the theme's CSS override.
|
|
18
|
+
* The theme can collapse a CSS animation, but this is a JS interpolation the
|
|
19
|
+
* cascade cannot reach, so it snaps to the value instead.
|
|
20
|
+
*/ export function AnimatedNumber({ value, decimals = 0, format, durationMs = 600, className }) {
|
|
21
|
+
const [display, setDisplay] = useState(value);
|
|
22
|
+
const fromRef = useRef(value);
|
|
23
|
+
const frameRef = useRef(0);
|
|
24
|
+
const reduce = usePrefersReducedMotion();
|
|
25
|
+
useEffect(()=>{
|
|
26
|
+
const from = fromRef.current;
|
|
27
|
+
if (reduce || from === value) {
|
|
28
|
+
fromRef.current = value;
|
|
29
|
+
setDisplay(value);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const start = performance.now();
|
|
33
|
+
const step = (now)=>{
|
|
34
|
+
const t = Math.min((now - start) / durationMs, 1);
|
|
35
|
+
setDisplay(from + (value - from) * easeOut(t));
|
|
36
|
+
if (t < 1) {
|
|
37
|
+
frameRef.current = requestAnimationFrame(step);
|
|
38
|
+
} else {
|
|
39
|
+
fromRef.current = value;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
frameRef.current = requestAnimationFrame(step);
|
|
43
|
+
return ()=>cancelAnimationFrame(frameRef.current);
|
|
44
|
+
}, [
|
|
45
|
+
value,
|
|
46
|
+
durationMs,
|
|
47
|
+
reduce
|
|
48
|
+
]);
|
|
49
|
+
const text = format === undefined ? display.toFixed(decimals) : format(display);
|
|
50
|
+
return /*#__PURE__*/ _jsxs("span", {
|
|
51
|
+
className: cn("tabular-nums", className),
|
|
52
|
+
children: [
|
|
53
|
+
/*#__PURE__*/ _jsx("span", {
|
|
54
|
+
"aria-hidden": true,
|
|
55
|
+
children: text
|
|
56
|
+
}),
|
|
57
|
+
/*#__PURE__*/ _jsx("span", {
|
|
58
|
+
className: "sr-only",
|
|
59
|
+
children: format === undefined ? value.toFixed(decimals) : format(value)
|
|
60
|
+
})
|
|
61
|
+
]
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
/** One argument of the action being approved, already resolved for display. */
|
|
3
|
+
export type ApprovalDetail = {
|
|
4
|
+
readonly label: string;
|
|
5
|
+
readonly value: string;
|
|
6
|
+
};
|
|
7
|
+
export type ApprovalRequest = {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
/** What will happen if approved, in plain language: "Post to #sales (Slack)". */
|
|
10
|
+
readonly headline: string;
|
|
11
|
+
/** Who or what is asking. */
|
|
12
|
+
readonly requestedBy: string;
|
|
13
|
+
readonly details?: readonly ApprovalDetail[];
|
|
14
|
+
/**
|
|
15
|
+
* A stable name for the *class* of action, so "always allow" can mean
|
|
16
|
+
* something. Without it the always-allow control is not offered at all.
|
|
17
|
+
*/
|
|
18
|
+
readonly actionKey?: string;
|
|
19
|
+
};
|
|
20
|
+
export type ApprovalState = "idle" | "approving" | "rejecting" | "allowing-always";
|
|
21
|
+
export type ApprovalCardProps = {
|
|
22
|
+
readonly request: ApprovalRequest;
|
|
23
|
+
readonly onApprove: () => void;
|
|
24
|
+
readonly onReject: () => void;
|
|
25
|
+
/** Durably allow this action class. Omit to hide the control entirely. */
|
|
26
|
+
readonly onAllowAlways?: (actionKey: string) => void;
|
|
27
|
+
readonly state?: ApprovalState;
|
|
28
|
+
readonly error?: string | null;
|
|
29
|
+
readonly className?: string;
|
|
30
|
+
readonly children?: ReactNode;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* The human decision surface: something is blocked until a person says yes or
|
|
34
|
+
* no, and this is where they say it.
|
|
35
|
+
*
|
|
36
|
+
* Approve is the primary and reject is an outline button — not because approval
|
|
37
|
+
* is the expected answer, but because a destructive-styled reject would read as
|
|
38
|
+
* the dangerous option when it is the *safe* one. Neither is pre-focused, and
|
|
39
|
+
* there is no default: a gate whose buttons can be dismissed by a stray Enter
|
|
40
|
+
* is not a gate.
|
|
41
|
+
*
|
|
42
|
+
* Every argument the action will run with is shown. A card that hides them
|
|
43
|
+
* behind "details" is asking for approval of something the user cannot see, so
|
|
44
|
+
* long values clamp rather than disappear.
|
|
45
|
+
*/
|
|
46
|
+
export declare function ApprovalCard({ request, onApprove, onReject, onAllowAlways, state, error, className, children, }: ApprovalCardProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
import { Badge } from "./badge.js";
|
|
5
|
+
import { Button } from "./button.js";
|
|
6
|
+
const LONG_VALUE = 140;
|
|
7
|
+
/**
|
|
8
|
+
* The human decision surface: something is blocked until a person says yes or
|
|
9
|
+
* no, and this is where they say it.
|
|
10
|
+
*
|
|
11
|
+
* Approve is the primary and reject is an outline button — not because approval
|
|
12
|
+
* is the expected answer, but because a destructive-styled reject would read as
|
|
13
|
+
* the dangerous option when it is the *safe* one. Neither is pre-focused, and
|
|
14
|
+
* there is no default: a gate whose buttons can be dismissed by a stray Enter
|
|
15
|
+
* is not a gate.
|
|
16
|
+
*
|
|
17
|
+
* Every argument the action will run with is shown. A card that hides them
|
|
18
|
+
* behind "details" is asking for approval of something the user cannot see, so
|
|
19
|
+
* long values clamp rather than disappear.
|
|
20
|
+
*/ export function ApprovalCard({ request, onApprove, onReject, onAllowAlways, state = "idle", error = null, className, children }) {
|
|
21
|
+
const busy = state !== "idle";
|
|
22
|
+
const canAllowAlways = onAllowAlways !== undefined && request.actionKey !== undefined;
|
|
23
|
+
return /*#__PURE__*/ _jsxs("section", {
|
|
24
|
+
"aria-label": `Approval requested: ${request.headline}`,
|
|
25
|
+
className: cn("flex flex-col gap-3 rounded-lg border border-primary-emphasis bg-card p-4", className),
|
|
26
|
+
children: [
|
|
27
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
28
|
+
className: "flex flex-wrap items-center gap-2",
|
|
29
|
+
children: [
|
|
30
|
+
/*#__PURE__*/ _jsx(Badge, {
|
|
31
|
+
tone: "accent",
|
|
32
|
+
children: "Needs your approval"
|
|
33
|
+
}),
|
|
34
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
35
|
+
className: "text-xs text-muted-foreground",
|
|
36
|
+
children: [
|
|
37
|
+
"Requested by ",
|
|
38
|
+
request.requestedBy
|
|
39
|
+
]
|
|
40
|
+
})
|
|
41
|
+
]
|
|
42
|
+
}),
|
|
43
|
+
/*#__PURE__*/ _jsx("p", {
|
|
44
|
+
className: "text-sm font-semibold",
|
|
45
|
+
children: request.headline
|
|
46
|
+
}),
|
|
47
|
+
request.details === undefined || request.details.length === 0 ? null : /*#__PURE__*/ _jsx("dl", {
|
|
48
|
+
className: "flex flex-col gap-1.5 rounded-md bg-muted p-3 text-xs",
|
|
49
|
+
children: request.details.map((detail)=>/*#__PURE__*/ _jsx(ApprovalDetailRow, {
|
|
50
|
+
detail: detail
|
|
51
|
+
}, detail.label))
|
|
52
|
+
}),
|
|
53
|
+
children,
|
|
54
|
+
error === null ? null : /*#__PURE__*/ _jsx("p", {
|
|
55
|
+
role: "alert",
|
|
56
|
+
className: "text-sm text-destructive",
|
|
57
|
+
children: error
|
|
58
|
+
}),
|
|
59
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
60
|
+
className: "flex flex-wrap items-center gap-2",
|
|
61
|
+
children: [
|
|
62
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
63
|
+
size: "sm",
|
|
64
|
+
onClick: onApprove,
|
|
65
|
+
disabled: busy,
|
|
66
|
+
children: state === "approving" ? "Approving…" : "Approve"
|
|
67
|
+
}),
|
|
68
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
69
|
+
variant: "outline",
|
|
70
|
+
size: "sm",
|
|
71
|
+
onClick: onReject,
|
|
72
|
+
disabled: busy,
|
|
73
|
+
children: state === "rejecting" ? "Rejecting…" : "Reject"
|
|
74
|
+
}),
|
|
75
|
+
canAllowAlways ? /*#__PURE__*/ _jsx(Button, {
|
|
76
|
+
variant: "ghost",
|
|
77
|
+
size: "sm",
|
|
78
|
+
onClick: ()=>onAllowAlways(request.actionKey),
|
|
79
|
+
disabled: busy,
|
|
80
|
+
className: "ml-auto",
|
|
81
|
+
children: state === "allowing-always" ? "Saving…" : "Always allow this"
|
|
82
|
+
}) : null
|
|
83
|
+
]
|
|
84
|
+
})
|
|
85
|
+
]
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function ApprovalDetailRow({ detail }) {
|
|
89
|
+
const [expanded, setExpanded] = useState(false);
|
|
90
|
+
const long = detail.value.length > LONG_VALUE;
|
|
91
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
92
|
+
className: "grid grid-cols-[minmax(0,8rem)_1fr] gap-2",
|
|
93
|
+
children: [
|
|
94
|
+
/*#__PURE__*/ _jsx("dt", {
|
|
95
|
+
className: "truncate text-muted-foreground",
|
|
96
|
+
children: detail.label
|
|
97
|
+
}),
|
|
98
|
+
/*#__PURE__*/ _jsxs("dd", {
|
|
99
|
+
className: "min-w-0",
|
|
100
|
+
children: [
|
|
101
|
+
/*#__PURE__*/ _jsx("span", {
|
|
102
|
+
className: cn("block break-words whitespace-pre-wrap", long && !expanded && "line-clamp-2"),
|
|
103
|
+
children: detail.value
|
|
104
|
+
}),
|
|
105
|
+
long ? /*#__PURE__*/ _jsx("button", {
|
|
106
|
+
type: "button",
|
|
107
|
+
onClick: ()=>setExpanded((value)=>!value),
|
|
108
|
+
"aria-expanded": expanded,
|
|
109
|
+
className: "mt-0.5 text-primary-emphasis hover:underline",
|
|
110
|
+
children: expanded ? "Show less" : "Show more"
|
|
111
|
+
}) : null
|
|
112
|
+
]
|
|
113
|
+
})
|
|
114
|
+
]
|
|
115
|
+
});
|
|
116
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type Artifact, type ArtifactForm } from "../lib/artifact.js";
|
|
2
|
+
export type ArtifactBodyProps = {
|
|
3
|
+
readonly artifact: Artifact;
|
|
4
|
+
/**
|
|
5
|
+
* Overrides the form derived from `artifact.kind`. Use it when the kind
|
|
6
|
+
* string cannot tell the whole story — a `file` that turned out to be an
|
|
7
|
+
* image, say — rather than teaching `artifactForm` about one caller's edge.
|
|
8
|
+
*/
|
|
9
|
+
readonly form?: ArtifactForm;
|
|
10
|
+
readonly className?: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* One artifact, drawn the way its kind implies.
|
|
14
|
+
*
|
|
15
|
+
* A `switch` over seven forms, not a renderer registry. The dispatch is the
|
|
16
|
+
* whole component and it fits on a screen, which is the point: you own this
|
|
17
|
+
* file after `shadcn add`, and a lookup table of injected components would mean
|
|
18
|
+
* reading two files and a type parameter to answer "what happens to a CSV". Add
|
|
19
|
+
* a case; delete the ones you do not have.
|
|
20
|
+
*
|
|
21
|
+
* Every branch that can receive an empty payload checks for one first, because
|
|
22
|
+
* "empty" is a real state here — artifacts are frequently rendered while they
|
|
23
|
+
* are still being written — and each form's own empty rendering (a table with
|
|
24
|
+
* no rows, a frame with no src) reads as a fault rather than as progress.
|
|
25
|
+
*/
|
|
26
|
+
export declare function ArtifactBody({ artifact, form, className }: ArtifactBodyProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Download } from "lucide-react";
|
|
3
|
+
import { artifactForm } from "../lib/artifact.js";
|
|
4
|
+
import { toSafeHref } from "../lib/url.js";
|
|
5
|
+
import { cn } from "../lib/utils.js";
|
|
6
|
+
import { ArtifactNotice } from "./artifact-notice.js";
|
|
7
|
+
import { buttonVariants } from "./button.js";
|
|
8
|
+
import { CompareBodyFromJson } from "./compare-body.js";
|
|
9
|
+
import { CsvTable } from "./csv-table.js";
|
|
10
|
+
import { EmbedBody } from "./embed-body.js";
|
|
11
|
+
import { ResearchBody, parseResearchArtifact } from "./research-body.js";
|
|
12
|
+
/**
|
|
13
|
+
* One artifact, drawn the way its kind implies.
|
|
14
|
+
*
|
|
15
|
+
* A `switch` over seven forms, not a renderer registry. The dispatch is the
|
|
16
|
+
* whole component and it fits on a screen, which is the point: you own this
|
|
17
|
+
* file after `shadcn add`, and a lookup table of injected components would mean
|
|
18
|
+
* reading two files and a type parameter to answer "what happens to a CSV". Add
|
|
19
|
+
* a case; delete the ones you do not have.
|
|
20
|
+
*
|
|
21
|
+
* Every branch that can receive an empty payload checks for one first, because
|
|
22
|
+
* "empty" is a real state here — artifacts are frequently rendered while they
|
|
23
|
+
* are still being written — and each form's own empty rendering (a table with
|
|
24
|
+
* no rows, a frame with no src) reads as a fault rather than as progress.
|
|
25
|
+
*/ export function ArtifactBody({ artifact, form, className }) {
|
|
26
|
+
const resolved = form ?? artifactForm(artifact.kind);
|
|
27
|
+
const content = artifact.content;
|
|
28
|
+
const blank = content.trim() === "";
|
|
29
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
30
|
+
className: cn("w-full", className),
|
|
31
|
+
children: body()
|
|
32
|
+
});
|
|
33
|
+
function body() {
|
|
34
|
+
switch(resolved){
|
|
35
|
+
case "image":
|
|
36
|
+
{
|
|
37
|
+
// The thumbnail URL, not the content: an image artifact's bytes live
|
|
38
|
+
// behind a route, and `content` for one is either empty or a caption.
|
|
39
|
+
const src = toSafeHref(artifact.thumbnailUrl ?? artifact.downloadUrl);
|
|
40
|
+
if (src === undefined) return /*#__PURE__*/ _jsx(ArtifactNotice, {
|
|
41
|
+
pending: artifact.pending
|
|
42
|
+
});
|
|
43
|
+
return /*#__PURE__*/ _jsxs("figure", {
|
|
44
|
+
className: "flex flex-col gap-3",
|
|
45
|
+
children: [
|
|
46
|
+
/*#__PURE__*/ _jsx("img", {
|
|
47
|
+
src: src,
|
|
48
|
+
alt: artifact.title,
|
|
49
|
+
className: "max-h-[480px] max-w-full rounded-lg border border-border"
|
|
50
|
+
}),
|
|
51
|
+
blank ? null : /*#__PURE__*/ _jsx("figcaption", {
|
|
52
|
+
className: "text-xs text-muted-foreground",
|
|
53
|
+
children: content
|
|
54
|
+
})
|
|
55
|
+
]
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
case "download":
|
|
59
|
+
{
|
|
60
|
+
const downloadUrl = toSafeHref(artifact.downloadUrl);
|
|
61
|
+
if (downloadUrl === undefined) {
|
|
62
|
+
return /*#__PURE__*/ _jsx(ArtifactNotice, {
|
|
63
|
+
message: "This file has no download location, so it cannot be retrieved."
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return /*#__PURE__*/ _jsxs("a", {
|
|
67
|
+
href: downloadUrl,
|
|
68
|
+
download: true,
|
|
69
|
+
className: buttonVariants({
|
|
70
|
+
variant: "outline",
|
|
71
|
+
size: "sm"
|
|
72
|
+
}),
|
|
73
|
+
children: [
|
|
74
|
+
/*#__PURE__*/ _jsx(Download, {
|
|
75
|
+
"aria-hidden": true
|
|
76
|
+
}),
|
|
77
|
+
"Download ",
|
|
78
|
+
artifact.title
|
|
79
|
+
]
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
case "embed":
|
|
83
|
+
return /*#__PURE__*/ _jsx(EmbedBody, {
|
|
84
|
+
url: content.trim(),
|
|
85
|
+
title: artifact.title,
|
|
86
|
+
...artifact.downloadUrl === undefined ? {} : {
|
|
87
|
+
downloadUrl: artifact.downloadUrl
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
case "table":
|
|
91
|
+
{
|
|
92
|
+
if (blank) return /*#__PURE__*/ _jsx(ArtifactNotice, {
|
|
93
|
+
pending: artifact.pending
|
|
94
|
+
});
|
|
95
|
+
const downloadUrl = toSafeHref(artifact.downloadUrl);
|
|
96
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
97
|
+
className: "flex flex-col gap-3",
|
|
98
|
+
children: [
|
|
99
|
+
downloadUrl === undefined ? null : /*#__PURE__*/ _jsxs("a", {
|
|
100
|
+
href: downloadUrl,
|
|
101
|
+
download: true,
|
|
102
|
+
className: cn(buttonVariants({
|
|
103
|
+
variant: "outline",
|
|
104
|
+
size: "sm"
|
|
105
|
+
}), "self-start"),
|
|
106
|
+
children: [
|
|
107
|
+
/*#__PURE__*/ _jsx(Download, {
|
|
108
|
+
"aria-hidden": true
|
|
109
|
+
}),
|
|
110
|
+
"Download"
|
|
111
|
+
]
|
|
112
|
+
}),
|
|
113
|
+
/*#__PURE__*/ _jsx(CsvTable, {
|
|
114
|
+
text: content,
|
|
115
|
+
caption: artifact.title
|
|
116
|
+
})
|
|
117
|
+
]
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
case "comparison":
|
|
121
|
+
return /*#__PURE__*/ _jsx(CompareBodyFromJson, {
|
|
122
|
+
content: content,
|
|
123
|
+
...artifact.pending === undefined ? {} : {
|
|
124
|
+
pending: artifact.pending
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
case "research":
|
|
128
|
+
{
|
|
129
|
+
if (blank) return /*#__PURE__*/ _jsx(ArtifactNotice, {
|
|
130
|
+
pending: artifact.pending
|
|
131
|
+
});
|
|
132
|
+
const parsed = parseResearchArtifact(content);
|
|
133
|
+
// Unparseable is not a failure worth an error card — the payload is
|
|
134
|
+
// still the artifact, so fall through to prose and let it be read.
|
|
135
|
+
if (parsed === null) return /*#__PURE__*/ _jsx(Prose, {
|
|
136
|
+
text: content
|
|
137
|
+
});
|
|
138
|
+
return /*#__PURE__*/ _jsx(ResearchBody, {
|
|
139
|
+
brief: parsed.brief,
|
|
140
|
+
...parsed.body === undefined ? {} : {
|
|
141
|
+
body: parsed.body
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
case "prose":
|
|
146
|
+
if (blank) return /*#__PURE__*/ _jsx(ArtifactNotice, {
|
|
147
|
+
pending: artifact.pending
|
|
148
|
+
});
|
|
149
|
+
return /*#__PURE__*/ _jsx(Prose, {
|
|
150
|
+
text: content
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Plain text, wrapped and measured.
|
|
157
|
+
*
|
|
158
|
+
* Not a Markdown renderer: this registry ships no Markdown dependency, and
|
|
159
|
+
* choosing one on a consumer's behalf — with its sanitiser, its plugin surface
|
|
160
|
+
* and its bundle — is not a decision a body component gets to make. Replace
|
|
161
|
+
* this function with your renderer; it is four lines and one import away.
|
|
162
|
+
*
|
|
163
|
+
* `max-w-prose` caps the measure at roughly 65 characters. Full-bleed body text
|
|
164
|
+
* on a wide screen is unreadable regardless of how good the type is.
|
|
165
|
+
*/ function Prose({ text }) {
|
|
166
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
167
|
+
className: "max-w-prose text-sm leading-relaxed whitespace-pre-wrap",
|
|
168
|
+
children: text
|
|
169
|
+
});
|
|
170
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one place an artifact viewer says "there is nothing to draw here".
|
|
3
|
+
*
|
|
4
|
+
* It exists as its own file, above `EmptyState` rather than beside it, for one
|
|
5
|
+
* reason: every kind renderer needs it, and `ArtifactBody` imports every kind
|
|
6
|
+
* renderer. Putting it in `artifact-body.tsx` would make each renderer import
|
|
7
|
+
* back into its own dispatcher — a cycle. One shared leaf breaks it.
|
|
8
|
+
*
|
|
9
|
+
* The distinction it encodes is the whole point. An empty payload on a *pending*
|
|
10
|
+
* artifact means the producer has not finished writing; an empty payload on a
|
|
11
|
+
* settled one means there is genuinely nothing. Same markup, opposite message,
|
|
12
|
+
* and getting it backwards makes a working system look broken.
|
|
13
|
+
*/
|
|
14
|
+
export declare function ArtifactNotice({
|
|
15
|
+
/** The producer is still writing. Changes the message, not the layout. */
|
|
16
|
+
pending,
|
|
17
|
+
/** Overrides the pending/empty wording — use for a specific failure. */
|
|
18
|
+
message, }: {
|
|
19
|
+
readonly pending?: boolean;
|
|
20
|
+
readonly message?: string;
|
|
21
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { FileWarning, Loader2 } from "lucide-react";
|
|
3
|
+
import { EmptyState } from "./empty-state.js";
|
|
4
|
+
/**
|
|
5
|
+
* The one place an artifact viewer says "there is nothing to draw here".
|
|
6
|
+
*
|
|
7
|
+
* It exists as its own file, above `EmptyState` rather than beside it, for one
|
|
8
|
+
* reason: every kind renderer needs it, and `ArtifactBody` imports every kind
|
|
9
|
+
* renderer. Putting it in `artifact-body.tsx` would make each renderer import
|
|
10
|
+
* back into its own dispatcher — a cycle. One shared leaf breaks it.
|
|
11
|
+
*
|
|
12
|
+
* The distinction it encodes is the whole point. An empty payload on a *pending*
|
|
13
|
+
* artifact means the producer has not finished writing; an empty payload on a
|
|
14
|
+
* settled one means there is genuinely nothing. Same markup, opposite message,
|
|
15
|
+
* and getting it backwards makes a working system look broken.
|
|
16
|
+
*/ export function ArtifactNotice({ /** The producer is still writing. Changes the message, not the layout. */ pending = false, /** Overrides the pending/empty wording — use for a specific failure. */ message }) {
|
|
17
|
+
if (message !== undefined) {
|
|
18
|
+
return /*#__PURE__*/ _jsx(EmptyState, {
|
|
19
|
+
icon: /*#__PURE__*/ _jsx(FileWarning, {}),
|
|
20
|
+
title: "Nothing to preview",
|
|
21
|
+
description: message
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (pending) {
|
|
25
|
+
return /*#__PURE__*/ _jsx(EmptyState, {
|
|
26
|
+
icon: /*#__PURE__*/ _jsx(Loader2, {}),
|
|
27
|
+
title: "Still being produced",
|
|
28
|
+
description: "This artifact has no content yet — check back shortly."
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return /*#__PURE__*/ _jsx(EmptyState, {
|
|
32
|
+
icon: /*#__PURE__*/ _jsx(FileWarning, {}),
|
|
33
|
+
title: "No content",
|
|
34
|
+
description: "This artifact is empty."
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type AvatarTone = "neutral" | "agent" | "agent2" | "agent3";
|
|
3
|
+
export type AvatarProps = {
|
|
4
|
+
/** One or two characters shown when no image is provided. */
|
|
5
|
+
readonly initials: string;
|
|
6
|
+
readonly label: string;
|
|
7
|
+
readonly tone?: AvatarTone;
|
|
8
|
+
readonly size?: "sm" | "md" | "lg";
|
|
9
|
+
/** Tenant monogram badge overlaid on the corner. */
|
|
10
|
+
readonly tenantMonogram?: string;
|
|
11
|
+
readonly className?: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Initials avatar with optional tenant monogram badge. Images are
|
|
15
|
+
* intentionally unsupported here: identity headers should not flash or
|
|
16
|
+
* depend on third-party avatar hosts.
|
|
17
|
+
*/
|
|
18
|
+
export declare function Avatar({ initials, label, tone, size, tenantMonogram, className, }: AvatarProps): import("react").JSX.Element;
|
|
19
|
+
export type AvatarStackItem = {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly initials: string;
|
|
22
|
+
readonly label: string;
|
|
23
|
+
readonly tone?: AvatarTone;
|
|
24
|
+
readonly tenantMonogram?: string;
|
|
25
|
+
};
|
|
26
|
+
export type AvatarStackProps = {
|
|
27
|
+
readonly items: readonly AvatarStackItem[];
|
|
28
|
+
/** Cap visible avatars; remainder becomes a +N chip. */
|
|
29
|
+
readonly max?: number;
|
|
30
|
+
readonly size?: "sm" | "md";
|
|
31
|
+
readonly className?: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Overlapping avatar stack for thread participants and channel members.
|
|
35
|
+
*/
|
|
36
|
+
export declare function AvatarStack({ items, max, size, className }: AvatarStackProps): import("react").JSX.Element;
|
|
37
|
+
export type AvatarStackSlot = ReactNode;
|