@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
package/dist/ui/card.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
export function Card({ className, ...props }) {
|
|
4
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
5
|
+
"data-slot": "card",
|
|
6
|
+
className: cn("flex flex-col gap-4 rounded-lg border border-border bg-card p-6 text-card-foreground", className),
|
|
7
|
+
...props
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export function CardHeader({ className, ...props }) {
|
|
11
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
12
|
+
"data-slot": "card-header",
|
|
13
|
+
className: cn("flex flex-col gap-1", className),
|
|
14
|
+
...props
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export function CardTitle({ className, ...props }) {
|
|
18
|
+
return /*#__PURE__*/ _jsx("h3", {
|
|
19
|
+
"data-slot": "card-title",
|
|
20
|
+
className: cn("text-base leading-none font-semibold", className),
|
|
21
|
+
...props
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export function CardDescription({ className, ...props }) {
|
|
25
|
+
return /*#__PURE__*/ _jsx("p", {
|
|
26
|
+
"data-slot": "card-description",
|
|
27
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
28
|
+
...props
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export function CardFooter({ className, ...props }) {
|
|
32
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
33
|
+
"data-slot": "card-footer",
|
|
34
|
+
className: cn("flex items-center gap-2", className),
|
|
35
|
+
...props
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type CategoryBarDatum = {
|
|
2
|
+
readonly label: string;
|
|
3
|
+
readonly value: number;
|
|
4
|
+
/** Optional secondary value shown beside the primary (e.g. cost). */
|
|
5
|
+
readonly secondaryLabel?: string;
|
|
6
|
+
};
|
|
7
|
+
export type CategoryBarsProps = {
|
|
8
|
+
readonly title: string;
|
|
9
|
+
readonly description?: string;
|
|
10
|
+
readonly data: readonly CategoryBarDatum[];
|
|
11
|
+
readonly valueLabel?: string;
|
|
12
|
+
readonly format?: (value: number) => string;
|
|
13
|
+
readonly className?: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Dense horizontal category ranking for cost-by-model / calls-by-tool
|
|
17
|
+
* style tables. Bars share one series colour; length encodes magnitude.
|
|
18
|
+
* Absent secondary labels stay empty rather than inventing a zero.
|
|
19
|
+
*/
|
|
20
|
+
export declare function CategoryBars({ title, description, data, valueLabel, format, className, }: CategoryBarsProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { formatCompact } from "../lib/chart-geometry.js";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
import { ChartFrame } from "./chart-frame.js";
|
|
5
|
+
/**
|
|
6
|
+
* Dense horizontal category ranking for cost-by-model / calls-by-tool
|
|
7
|
+
* style tables. Bars share one series colour; length encodes magnitude.
|
|
8
|
+
* Absent secondary labels stay empty rather than inventing a zero.
|
|
9
|
+
*/ export function CategoryBars({ title, description, data, valueLabel = "Value", format = formatCompact, className }) {
|
|
10
|
+
const max = Math.max(0, ...data.map((d)=>d.value));
|
|
11
|
+
const axisMax = max === 0 ? 1 : max;
|
|
12
|
+
return /*#__PURE__*/ _jsx(ChartFrame, {
|
|
13
|
+
title: title,
|
|
14
|
+
...description === undefined ? {} : {
|
|
15
|
+
description
|
|
16
|
+
},
|
|
17
|
+
table: {
|
|
18
|
+
columns: [
|
|
19
|
+
"Category",
|
|
20
|
+
valueLabel,
|
|
21
|
+
"Detail"
|
|
22
|
+
],
|
|
23
|
+
rows: data.map((datum)=>[
|
|
24
|
+
datum.label,
|
|
25
|
+
format(datum.value),
|
|
26
|
+
datum.secondaryLabel ?? "—"
|
|
27
|
+
])
|
|
28
|
+
},
|
|
29
|
+
className: className,
|
|
30
|
+
children: /*#__PURE__*/ _jsx("div", {
|
|
31
|
+
className: "flex flex-col gap-2",
|
|
32
|
+
role: "presentation",
|
|
33
|
+
children: data.map((datum)=>{
|
|
34
|
+
const pct = datum.value / axisMax * 100;
|
|
35
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
36
|
+
className: "grid grid-cols-[minmax(6rem,10rem)_1fr_auto] items-center gap-2",
|
|
37
|
+
children: [
|
|
38
|
+
/*#__PURE__*/ _jsx("p", {
|
|
39
|
+
className: "truncate text-xs font-medium",
|
|
40
|
+
children: datum.label
|
|
41
|
+
}),
|
|
42
|
+
/*#__PURE__*/ _jsx("div", {
|
|
43
|
+
className: "h-3 w-full bg-muted",
|
|
44
|
+
children: /*#__PURE__*/ _jsx("div", {
|
|
45
|
+
className: cn("h-full bg-primary-emphasis"),
|
|
46
|
+
style: {
|
|
47
|
+
width: `${pct}%`
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
}),
|
|
51
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
52
|
+
className: "min-w-[3.5rem] text-right font-mono text-[11px] leading-tight",
|
|
53
|
+
children: [
|
|
54
|
+
/*#__PURE__*/ _jsx("p", {
|
|
55
|
+
className: "font-semibold",
|
|
56
|
+
children: format(datum.value)
|
|
57
|
+
}),
|
|
58
|
+
datum.secondaryLabel === undefined ? null : /*#__PURE__*/ _jsx("p", {
|
|
59
|
+
className: "text-muted-foreground",
|
|
60
|
+
children: datum.secondaryLabel
|
|
61
|
+
})
|
|
62
|
+
]
|
|
63
|
+
})
|
|
64
|
+
]
|
|
65
|
+
}, datum.label);
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type ChartLegendEntry = {
|
|
3
|
+
readonly label: string;
|
|
4
|
+
/** A CSS colour — normally `seriesColor(index)`. */
|
|
5
|
+
readonly color: string;
|
|
6
|
+
/**
|
|
7
|
+
* An SVG `stroke-dasharray` — normally `seriesDash(index)`. Present when the
|
|
8
|
+
* mark carries a second, non-colour channel; the swatch becomes a line
|
|
9
|
+
* segment drawn with the same pattern, so the legend cross-references both
|
|
10
|
+
* channels instead of only the hue. Omit for filled marks like bars.
|
|
11
|
+
*/
|
|
12
|
+
readonly dash?: string;
|
|
13
|
+
};
|
|
14
|
+
export type ChartTable = {
|
|
15
|
+
readonly columns: readonly string[];
|
|
16
|
+
readonly rows: readonly (readonly ReactNode[])[];
|
|
17
|
+
};
|
|
18
|
+
export type ChartFrameProps = {
|
|
19
|
+
/** Names the chart. Becomes both the visible caption and the accessible name. */
|
|
20
|
+
readonly title: string;
|
|
21
|
+
readonly description?: string;
|
|
22
|
+
/** Omit for a single series — one swatch restating the title is wasted space. */
|
|
23
|
+
readonly legend?: readonly ChartLegendEntry[];
|
|
24
|
+
/**
|
|
25
|
+
* The same numbers as a table. Required, not optional: this is what makes a
|
|
26
|
+
* chart readable to someone who cannot see it, and an escape hatch that only
|
|
27
|
+
* appears when a developer remembers it is not an escape hatch.
|
|
28
|
+
*/
|
|
29
|
+
readonly table: ChartTable;
|
|
30
|
+
/** The plot itself — an `<svg>`. */
|
|
31
|
+
readonly children: ReactNode;
|
|
32
|
+
readonly className?: string;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The frame every chart here sits in: a caption, a legend, the plot, and the
|
|
36
|
+
* same data as a table.
|
|
37
|
+
*
|
|
38
|
+
* The table is the point. A chart is a picture of numbers, and a picture is not
|
|
39
|
+
* available to a screen reader, a text browser, or anyone who needs the exact
|
|
40
|
+
* value rather than the shape — so the numbers are always present, one
|
|
41
|
+
* disclosure away, in DOM order right after the plot. `<details>` gives that a
|
|
42
|
+
* keyboard-operable, correctly-announced control for free.
|
|
43
|
+
*
|
|
44
|
+
* `<figure>`/`<figcaption>` rather than a div and an h3: the caption is bound to
|
|
45
|
+
* the figure by the element pair, which is what makes "Revenue by region" the
|
|
46
|
+
* chart's name instead of a heading that happens to sit above it.
|
|
47
|
+
*
|
|
48
|
+
* The legend appears for two or more series and never for one, because with one
|
|
49
|
+
* series the caption already says what is plotted. Each entry pairs a colour
|
|
50
|
+
* swatch with text — the text is the identity channel, the swatch is the
|
|
51
|
+
* cross-reference. Text never wears the series colour: a mid-lightness hue that
|
|
52
|
+
* is correct for a 24px bar is unreadable at 12px.
|
|
53
|
+
*/
|
|
54
|
+
export declare function ChartFrame({ title, description, legend, table, children, className }: ChartFrameProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./table.js";
|
|
4
|
+
/**
|
|
5
|
+
* The frame every chart here sits in: a caption, a legend, the plot, and the
|
|
6
|
+
* same data as a table.
|
|
7
|
+
*
|
|
8
|
+
* The table is the point. A chart is a picture of numbers, and a picture is not
|
|
9
|
+
* available to a screen reader, a text browser, or anyone who needs the exact
|
|
10
|
+
* value rather than the shape — so the numbers are always present, one
|
|
11
|
+
* disclosure away, in DOM order right after the plot. `<details>` gives that a
|
|
12
|
+
* keyboard-operable, correctly-announced control for free.
|
|
13
|
+
*
|
|
14
|
+
* `<figure>`/`<figcaption>` rather than a div and an h3: the caption is bound to
|
|
15
|
+
* the figure by the element pair, which is what makes "Revenue by region" the
|
|
16
|
+
* chart's name instead of a heading that happens to sit above it.
|
|
17
|
+
*
|
|
18
|
+
* The legend appears for two or more series and never for one, because with one
|
|
19
|
+
* series the caption already says what is plotted. Each entry pairs a colour
|
|
20
|
+
* swatch with text — the text is the identity channel, the swatch is the
|
|
21
|
+
* cross-reference. Text never wears the series colour: a mid-lightness hue that
|
|
22
|
+
* is correct for a 24px bar is unreadable at 12px.
|
|
23
|
+
*/ export function ChartFrame({ title, description, legend, table, children, className }) {
|
|
24
|
+
const showLegend = legend !== undefined && legend.length > 1;
|
|
25
|
+
return /*#__PURE__*/ _jsxs("figure", {
|
|
26
|
+
className: cn("flex flex-col gap-3", className),
|
|
27
|
+
children: [
|
|
28
|
+
/*#__PURE__*/ _jsxs("figcaption", {
|
|
29
|
+
className: "flex flex-col gap-0.5",
|
|
30
|
+
children: [
|
|
31
|
+
/*#__PURE__*/ _jsx("span", {
|
|
32
|
+
className: "text-sm font-semibold",
|
|
33
|
+
children: title
|
|
34
|
+
}),
|
|
35
|
+
description === undefined ? null : /*#__PURE__*/ _jsx("span", {
|
|
36
|
+
className: "text-xs text-muted-foreground",
|
|
37
|
+
children: description
|
|
38
|
+
})
|
|
39
|
+
]
|
|
40
|
+
}),
|
|
41
|
+
showLegend ? /*#__PURE__*/ _jsx("ul", {
|
|
42
|
+
className: "flex flex-wrap items-center gap-x-4 gap-y-1",
|
|
43
|
+
children: legend.map((entry)=>/*#__PURE__*/ _jsxs("li", {
|
|
44
|
+
className: "flex items-center gap-1.5 text-xs text-muted-foreground",
|
|
45
|
+
children: [
|
|
46
|
+
entry.dash === undefined ? /*#__PURE__*/ _jsx("span", {
|
|
47
|
+
"aria-hidden": true,
|
|
48
|
+
className: "size-2.5 shrink-0 rounded-xs",
|
|
49
|
+
style: {
|
|
50
|
+
backgroundColor: entry.color
|
|
51
|
+
}
|
|
52
|
+
}) : /*#__PURE__*/ _jsx("svg", {
|
|
53
|
+
"aria-hidden": true,
|
|
54
|
+
viewBox: "0 0 20 4",
|
|
55
|
+
className: "h-1 w-5 shrink-0 overflow-visible",
|
|
56
|
+
children: /*#__PURE__*/ _jsx("line", {
|
|
57
|
+
x1: 0,
|
|
58
|
+
y1: 2,
|
|
59
|
+
x2: 20,
|
|
60
|
+
y2: 2,
|
|
61
|
+
stroke: entry.color,
|
|
62
|
+
strokeWidth: 2.5,
|
|
63
|
+
strokeLinecap: "butt",
|
|
64
|
+
strokeDasharray: entry.dash
|
|
65
|
+
})
|
|
66
|
+
}),
|
|
67
|
+
entry.label
|
|
68
|
+
]
|
|
69
|
+
}, entry.label))
|
|
70
|
+
}) : null,
|
|
71
|
+
children,
|
|
72
|
+
/*#__PURE__*/ _jsxs("details", {
|
|
73
|
+
className: "text-xs",
|
|
74
|
+
children: [
|
|
75
|
+
/*#__PURE__*/ _jsx("summary", {
|
|
76
|
+
className: "cursor-pointer text-muted-foreground hover:text-foreground",
|
|
77
|
+
children: "Show the data"
|
|
78
|
+
}),
|
|
79
|
+
/*#__PURE__*/ _jsx("div", {
|
|
80
|
+
className: "mt-2",
|
|
81
|
+
children: /*#__PURE__*/ _jsxs(Table, {
|
|
82
|
+
"aria-label": `${title} — data`,
|
|
83
|
+
children: [
|
|
84
|
+
/*#__PURE__*/ _jsx(TableHeader, {
|
|
85
|
+
children: /*#__PURE__*/ _jsx(TableRow, {
|
|
86
|
+
children: table.columns.map((column, index)=>/*#__PURE__*/ _jsx(TableHead, {
|
|
87
|
+
scope: "col",
|
|
88
|
+
className: index === 0 ? undefined : "text-right",
|
|
89
|
+
children: column
|
|
90
|
+
}, column))
|
|
91
|
+
})
|
|
92
|
+
}),
|
|
93
|
+
/*#__PURE__*/ _jsx(TableBody, {
|
|
94
|
+
children: table.rows.map((row, rowIndex)=>/*#__PURE__*/ _jsx(TableRow, {
|
|
95
|
+
children: row.map((cell, cellIndex)=>/*#__PURE__*/ _jsx(TableCell, {
|
|
96
|
+
className: cellIndex === 0 ? undefined : "text-right font-mono tabular-nums",
|
|
97
|
+
children: cell
|
|
98
|
+
}, cellIndex))
|
|
99
|
+
}, rowIndex))
|
|
100
|
+
})
|
|
101
|
+
]
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
]
|
|
105
|
+
})
|
|
106
|
+
]
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { QuickReply } from "../lib/chat-message.js";
|
|
2
|
+
import { type ChatInputProps } from "./chat-input.js";
|
|
3
|
+
export type ChatComposerProps = Omit<ChatInputProps, "working" | "disabled" | "textareaRef"> & {
|
|
4
|
+
/** Suggestion chips shown above the input while the conversation is empty. */
|
|
5
|
+
readonly suggestions?: readonly QuickReply[];
|
|
6
|
+
readonly onSuggestionSelect?: (reply: QuickReply) => void;
|
|
7
|
+
/** True while a response is in flight — disables the input, same signal `ChatInput` calls `working`. */
|
|
8
|
+
readonly busy?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Flips true→false or false→true to (re)trigger the autofocus timer — pass
|
|
11
|
+
* the dock's `mode !== "closed"` so it fires once per open, after the
|
|
12
|
+
* panel's own entrance animation has settled (see `chat-dock-timing.ts`).
|
|
13
|
+
*/
|
|
14
|
+
readonly autoFocusOn?: boolean;
|
|
15
|
+
readonly className?: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The composer: suggestion chips above an empty conversation, otherwise just
|
|
19
|
+
* the input. Chips and input are mutually exclusive rather than stacked
|
|
20
|
+
* always-on, because a suggestion list next to ten prior turns is not a
|
|
21
|
+
* suggestion anymore — it is clutter competing with what the user already
|
|
22
|
+
* said.
|
|
23
|
+
*/
|
|
24
|
+
export declare function ChatComposer({ suggestions, onSuggestionSelect, busy, autoFocusOn, className, ...inputProps }: ChatComposerProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef } from "react";
|
|
3
|
+
import { useDelayedAutofocus } from "../hooks/use-delayed-autofocus.js";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
import { ChatInput } from "./chat-input.js";
|
|
6
|
+
import { QuickReplyChips } from "./quick-reply-chips.js";
|
|
7
|
+
/**
|
|
8
|
+
* The composer: suggestion chips above an empty conversation, otherwise just
|
|
9
|
+
* the input. Chips and input are mutually exclusive rather than stacked
|
|
10
|
+
* always-on, because a suggestion list next to ten prior turns is not a
|
|
11
|
+
* suggestion anymore — it is clutter competing with what the user already
|
|
12
|
+
* said.
|
|
13
|
+
*/ export function ChatComposer({ suggestions = [], onSuggestionSelect, busy = false, autoFocusOn = false, className, ...inputProps }) {
|
|
14
|
+
const textareaRef = useRef(null);
|
|
15
|
+
useDelayedAutofocus(textareaRef, autoFocusOn);
|
|
16
|
+
const showSuggestions = suggestions.length > 0 && onSuggestionSelect !== undefined;
|
|
17
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
18
|
+
className: cn("flex flex-col gap-2", className),
|
|
19
|
+
children: [
|
|
20
|
+
showSuggestions ? /*#__PURE__*/ _jsx(QuickReplyChips, {
|
|
21
|
+
replies: suggestions,
|
|
22
|
+
onSelect: onSuggestionSelect
|
|
23
|
+
}) : null,
|
|
24
|
+
/*#__PURE__*/ _jsx(ChatInput, {
|
|
25
|
+
...inputProps,
|
|
26
|
+
working: busy,
|
|
27
|
+
disabled: busy,
|
|
28
|
+
textareaRef: textareaRef
|
|
29
|
+
})
|
|
30
|
+
]
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dock's one source of truth for motion timing. Every duration that has
|
|
3
|
+
* to line up with another — the entrance pop, the scrim fade, the autofocus
|
|
4
|
+
* delay — lives here so fixing one never means hunting the others down.
|
|
5
|
+
*/
|
|
6
|
+
/** How long the closed→docked entrance pop takes, in milliseconds. */
|
|
7
|
+
export declare const CHAT_DOCK_ENTRANCE_MS = 280;
|
|
8
|
+
/** The scrim's own fade, coordinated to finish alongside the entrance. */
|
|
9
|
+
export declare const CHAT_DOCK_SCRIM_MS = 280;
|
|
10
|
+
/**
|
|
11
|
+
* The composer's autofocus delay. Must be `>=` the entrance duration — focus
|
|
12
|
+
* landing before the panel has finished animating steals the caret out from
|
|
13
|
+
* under a still-moving element.
|
|
14
|
+
*/
|
|
15
|
+
export declare const CHAT_DOCK_AUTOFOCUS_DELAY_MS = 280;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dock's one source of truth for motion timing. Every duration that has
|
|
3
|
+
* to line up with another — the entrance pop, the scrim fade, the autofocus
|
|
4
|
+
* delay — lives here so fixing one never means hunting the others down.
|
|
5
|
+
*/ /** How long the closed→docked entrance pop takes, in milliseconds. */ export const CHAT_DOCK_ENTRANCE_MS = 280;
|
|
6
|
+
/** The scrim's own fade, coordinated to finish alongside the entrance. */ export const CHAT_DOCK_SCRIM_MS = CHAT_DOCK_ENTRANCE_MS;
|
|
7
|
+
/**
|
|
8
|
+
* The composer's autofocus delay. Must be `>=` the entrance duration — focus
|
|
9
|
+
* landing before the panel has finished animating steals the caret out from
|
|
10
|
+
* under a still-moving element.
|
|
11
|
+
*/ export const CHAT_DOCK_AUTOFOCUS_DELAY_MS = CHAT_DOCK_ENTRANCE_MS;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type RefObject } from "react";
|
|
2
|
+
export type ChatAttachment = {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly name: string;
|
|
5
|
+
};
|
|
6
|
+
export type ChatInputProps = {
|
|
7
|
+
readonly value: string;
|
|
8
|
+
readonly onValueChange: (value: string) => void;
|
|
9
|
+
readonly onSend: () => void;
|
|
10
|
+
/** Shown while the agent is replying. Its presence turns send into stop. */
|
|
11
|
+
readonly onStop?: () => void;
|
|
12
|
+
readonly working?: boolean;
|
|
13
|
+
readonly placeholder?: string;
|
|
14
|
+
readonly disabled?: boolean;
|
|
15
|
+
readonly attachments?: readonly ChatAttachment[];
|
|
16
|
+
readonly onAttach?: (files: FileList) => void;
|
|
17
|
+
readonly onRemoveAttachment?: (attachment: ChatAttachment) => void;
|
|
18
|
+
/** Exposes the textarea node — a host that autofocuses on open needs something to focus. */
|
|
19
|
+
readonly textareaRef?: RefObject<HTMLTextAreaElement | null>;
|
|
20
|
+
readonly className?: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The composer.
|
|
24
|
+
*
|
|
25
|
+
* Enter sends and Shift+Enter breaks the line — the convention every chat app
|
|
26
|
+
* shares, and breaking it costs users a message. IME composition is excluded:
|
|
27
|
+
* mid-composition Enter commits a candidate in Japanese and Chinese input, and
|
|
28
|
+
* treating that as "send" fires a half-typed message.
|
|
29
|
+
*
|
|
30
|
+
* While the agent is replying, the send button becomes stop. Two buttons would
|
|
31
|
+
* mean one is always dead, and a stop control that appears somewhere else is one
|
|
32
|
+
* the user has to hunt for at the moment they most want it.
|
|
33
|
+
*
|
|
34
|
+
* The textarea grows with its content up to a cap, then scrolls. It is measured
|
|
35
|
+
* rather than counted in rows, because a wrapped long line takes more height
|
|
36
|
+
* than its newline count implies.
|
|
37
|
+
*/
|
|
38
|
+
export declare function ChatInput({ value, onValueChange, onSend, onStop, working, placeholder, disabled, attachments, onAttach, onRemoveAttachment, textareaRef: externalTextareaRef, className, }: ChatInputProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { ArrowUp, Paperclip, Square, X } from "lucide-react";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
const MAX_ROWS_PX = 200;
|
|
6
|
+
/**
|
|
7
|
+
* The composer.
|
|
8
|
+
*
|
|
9
|
+
* Enter sends and Shift+Enter breaks the line — the convention every chat app
|
|
10
|
+
* shares, and breaking it costs users a message. IME composition is excluded:
|
|
11
|
+
* mid-composition Enter commits a candidate in Japanese and Chinese input, and
|
|
12
|
+
* treating that as "send" fires a half-typed message.
|
|
13
|
+
*
|
|
14
|
+
* While the agent is replying, the send button becomes stop. Two buttons would
|
|
15
|
+
* mean one is always dead, and a stop control that appears somewhere else is one
|
|
16
|
+
* the user has to hunt for at the moment they most want it.
|
|
17
|
+
*
|
|
18
|
+
* The textarea grows with its content up to a cap, then scrolls. It is measured
|
|
19
|
+
* rather than counted in rows, because a wrapped long line takes more height
|
|
20
|
+
* than its newline count implies.
|
|
21
|
+
*/ export function ChatInput({ value, onValueChange, onSend, onStop, working = false, placeholder = "Send a message…", disabled = false, attachments = [], onAttach, onRemoveAttachment, textareaRef: externalTextareaRef, className }) {
|
|
22
|
+
const internalTextareaRef = useRef(null);
|
|
23
|
+
const textareaRef = externalTextareaRef ?? internalTextareaRef;
|
|
24
|
+
const fileRef = useRef(null);
|
|
25
|
+
useEffect(()=>{
|
|
26
|
+
const node = textareaRef.current;
|
|
27
|
+
if (node === null) return;
|
|
28
|
+
node.style.height = "auto";
|
|
29
|
+
node.style.height = `${Math.min(node.scrollHeight, MAX_ROWS_PX)}px`;
|
|
30
|
+
}, [
|
|
31
|
+
value
|
|
32
|
+
]);
|
|
33
|
+
const canSend = value.trim().length > 0 && !disabled;
|
|
34
|
+
const showStop = working && onStop !== undefined;
|
|
35
|
+
const submit = ()=>{
|
|
36
|
+
if (!canSend) return;
|
|
37
|
+
onSend();
|
|
38
|
+
};
|
|
39
|
+
return /*#__PURE__*/ _jsxs("form", {
|
|
40
|
+
className: cn("flex flex-col gap-2 rounded-lg border border-input bg-background p-2", className),
|
|
41
|
+
onSubmit: (event)=>{
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
submit();
|
|
44
|
+
},
|
|
45
|
+
children: [
|
|
46
|
+
attachments.length === 0 ? null : /*#__PURE__*/ _jsx("ul", {
|
|
47
|
+
"aria-label": "Attachments",
|
|
48
|
+
className: "flex flex-wrap gap-1.5",
|
|
49
|
+
children: attachments.map((attachment)=>/*#__PURE__*/ _jsxs("li", {
|
|
50
|
+
className: "flex items-center gap-1.5 rounded-md bg-muted py-1 pr-1 pl-2 text-xs",
|
|
51
|
+
children: [
|
|
52
|
+
/*#__PURE__*/ _jsx("span", {
|
|
53
|
+
className: "max-w-40 truncate",
|
|
54
|
+
children: attachment.name
|
|
55
|
+
}),
|
|
56
|
+
onRemoveAttachment === undefined ? null : /*#__PURE__*/ _jsx("button", {
|
|
57
|
+
type: "button",
|
|
58
|
+
onClick: ()=>onRemoveAttachment(attachment),
|
|
59
|
+
"aria-label": `Remove ${attachment.name}`,
|
|
60
|
+
className: "grid size-5 place-items-center rounded-sm text-muted-foreground hover:text-foreground",
|
|
61
|
+
children: /*#__PURE__*/ _jsx(X, {
|
|
62
|
+
className: "size-3",
|
|
63
|
+
"aria-hidden": true
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
]
|
|
67
|
+
}, attachment.id))
|
|
68
|
+
}),
|
|
69
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
70
|
+
className: "flex items-end gap-2",
|
|
71
|
+
children: [
|
|
72
|
+
onAttach === undefined ? null : /*#__PURE__*/ _jsxs(_Fragment, {
|
|
73
|
+
children: [
|
|
74
|
+
/*#__PURE__*/ _jsx("input", {
|
|
75
|
+
ref: fileRef,
|
|
76
|
+
type: "file",
|
|
77
|
+
multiple: true,
|
|
78
|
+
className: "sr-only",
|
|
79
|
+
onChange: (event)=>{
|
|
80
|
+
if (event.target.files !== null) onAttach(event.target.files);
|
|
81
|
+
event.target.value = "";
|
|
82
|
+
}
|
|
83
|
+
}),
|
|
84
|
+
/*#__PURE__*/ _jsx("button", {
|
|
85
|
+
type: "button",
|
|
86
|
+
onClick: ()=>fileRef.current?.click(),
|
|
87
|
+
disabled: disabled,
|
|
88
|
+
"aria-label": "Attach files",
|
|
89
|
+
className: "grid size-9 shrink-0 place-items-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50",
|
|
90
|
+
children: /*#__PURE__*/ _jsx(Paperclip, {
|
|
91
|
+
className: "size-4",
|
|
92
|
+
"aria-hidden": true
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
]
|
|
96
|
+
}),
|
|
97
|
+
/*#__PURE__*/ _jsx("textarea", {
|
|
98
|
+
ref: textareaRef,
|
|
99
|
+
value: value,
|
|
100
|
+
rows: 1,
|
|
101
|
+
disabled: disabled,
|
|
102
|
+
placeholder: placeholder,
|
|
103
|
+
"aria-label": "Message",
|
|
104
|
+
onChange: (event)=>onValueChange(event.target.value),
|
|
105
|
+
onKeyDown: (event)=>{
|
|
106
|
+
if (event.key !== "Enter" || event.shiftKey) return;
|
|
107
|
+
// `isComposing` is the IME guard — without it, committing a
|
|
108
|
+
// Japanese or Chinese candidate sends the message instead.
|
|
109
|
+
if (event.nativeEvent.isComposing) return;
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
submit();
|
|
112
|
+
},
|
|
113
|
+
className: "max-h-50 min-h-9 flex-1 resize-none bg-transparent py-2 text-sm outline-none placeholder:text-muted-foreground disabled:opacity-50"
|
|
114
|
+
}),
|
|
115
|
+
showStop ? /*#__PURE__*/ _jsx("button", {
|
|
116
|
+
type: "button",
|
|
117
|
+
onClick: onStop,
|
|
118
|
+
"aria-label": "Stop generating",
|
|
119
|
+
className: "grid size-9 shrink-0 place-items-center rounded-md bg-muted text-foreground transition-colors hover:bg-secondary",
|
|
120
|
+
children: /*#__PURE__*/ _jsx(Square, {
|
|
121
|
+
className: "size-3.5 fill-current",
|
|
122
|
+
"aria-hidden": true
|
|
123
|
+
})
|
|
124
|
+
}) : /*#__PURE__*/ _jsx("button", {
|
|
125
|
+
type: "submit",
|
|
126
|
+
disabled: !canSend,
|
|
127
|
+
"aria-label": "Send message",
|
|
128
|
+
className: "grid size-9 shrink-0 place-items-center rounded-md bg-primary text-primary-foreground transition-colors hover:bg-primary-active disabled:opacity-40",
|
|
129
|
+
children: /*#__PURE__*/ _jsx(ArrowUp, {
|
|
130
|
+
className: "size-4",
|
|
131
|
+
"aria-hidden": true
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
]
|
|
135
|
+
})
|
|
136
|
+
]
|
|
137
|
+
});
|
|
138
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { type AgentIdentity } from "../lib/chat-message.js";
|
|
3
|
+
/**
|
|
4
|
+
* The chat surface's frame: who you are talking to, the transcript, and the
|
|
5
|
+
* composer.
|
|
6
|
+
*
|
|
7
|
+
* Parts rather than a `messages`/`onSend` mega-component, because the same
|
|
8
|
+
* frame carries a full-page conversation, a docked bar and a floating panel,
|
|
9
|
+
* and each wants something different in the header — a thread switcher here, a
|
|
10
|
+
* close button there. Composition is what makes one frame serve three shells.
|
|
11
|
+
*/
|
|
12
|
+
export declare function ChatPanel({ className, ...props }: React.ComponentProps<"div">): import("react").JSX.Element;
|
|
13
|
+
export declare function ChatPanelHeader({ identity, children, className, ...props }: React.ComponentProps<"header"> & {
|
|
14
|
+
identity: AgentIdentity;
|
|
15
|
+
}): import("react").JSX.Element;
|
|
16
|
+
/** The composer area: quick replies, the input, anything else pinned to the bottom. */
|
|
17
|
+
export declare function ChatPanelFooter({ className, children }: {
|
|
18
|
+
className?: string;
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { agentInitials } from "../lib/chat-message.js";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
/**
|
|
5
|
+
* The chat surface's frame: who you are talking to, the transcript, and the
|
|
6
|
+
* composer.
|
|
7
|
+
*
|
|
8
|
+
* Parts rather than a `messages`/`onSend` mega-component, because the same
|
|
9
|
+
* frame carries a full-page conversation, a docked bar and a floating panel,
|
|
10
|
+
* and each wants something different in the header — a thread switcher here, a
|
|
11
|
+
* close button there. Composition is what makes one frame serve three shells.
|
|
12
|
+
*/ export function ChatPanel({ className, ...props }) {
|
|
13
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
14
|
+
"data-slot": "chat-panel",
|
|
15
|
+
className: cn("flex min-h-0 flex-1 flex-col bg-background text-foreground", className),
|
|
16
|
+
...props
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export function ChatPanelHeader({ identity, children, className, ...props }) {
|
|
20
|
+
return /*#__PURE__*/ _jsxs("header", {
|
|
21
|
+
"data-slot": "chat-panel-header",
|
|
22
|
+
className: cn("flex shrink-0 items-center gap-2.5 border-b border-border px-4 py-3", className),
|
|
23
|
+
...props,
|
|
24
|
+
children: [
|
|
25
|
+
/*#__PURE__*/ _jsx("span", {
|
|
26
|
+
"aria-hidden": true,
|
|
27
|
+
className: "grid size-8 shrink-0 place-items-center rounded-full bg-secondary text-[11px] font-bold text-secondary-foreground",
|
|
28
|
+
children: agentInitials(identity)
|
|
29
|
+
}),
|
|
30
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
31
|
+
className: "flex min-w-0 flex-col",
|
|
32
|
+
children: [
|
|
33
|
+
/*#__PURE__*/ _jsx("p", {
|
|
34
|
+
className: "truncate text-sm font-semibold",
|
|
35
|
+
children: identity.name
|
|
36
|
+
}),
|
|
37
|
+
identity.tagline === undefined ? null : /*#__PURE__*/ _jsx("p", {
|
|
38
|
+
className: "truncate text-xs text-muted-foreground",
|
|
39
|
+
children: identity.tagline
|
|
40
|
+
})
|
|
41
|
+
]
|
|
42
|
+
}),
|
|
43
|
+
/*#__PURE__*/ _jsx("div", {
|
|
44
|
+
className: "ml-auto flex shrink-0 items-center gap-1",
|
|
45
|
+
children: children
|
|
46
|
+
})
|
|
47
|
+
]
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** The composer area: quick replies, the input, anything else pinned to the bottom. */ export function ChatPanelFooter({ className, children }) {
|
|
51
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
52
|
+
"data-slot": "chat-panel-footer",
|
|
53
|
+
className: cn("flex shrink-0 flex-col gap-2 border-t border-border p-3", className),
|
|
54
|
+
children: children
|
|
55
|
+
});
|
|
56
|
+
}
|