@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,170 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ChevronRight } from "lucide-react";
|
|
3
|
+
import { useControllableState } from "../hooks/use-controllable-state.js";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
import { StatusDot } from "./status-dot.js";
|
|
6
|
+
const STATE_TEXT = {
|
|
7
|
+
pending: "Queued",
|
|
8
|
+
running: "Working",
|
|
9
|
+
"output-available": "Done",
|
|
10
|
+
error: "Failed",
|
|
11
|
+
"approval-requested": "Needs approval",
|
|
12
|
+
"output-denied": "Denied"
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Orange (`emphasis`) is reserved for `approval-requested` alone: it is the
|
|
16
|
+
* one state that needs a human to act, so it is the one state that earns the
|
|
17
|
+
* brand's single emphasis color. `running` already signals "in progress"
|
|
18
|
+
* through the dot's own pulse animation (`StatusDot`'s `live` prop) — giving
|
|
19
|
+
* it orange too would stack two "look here" signals on a state that isn't
|
|
20
|
+
* actually asking for anything.
|
|
21
|
+
*/ const STATE_TONE = {
|
|
22
|
+
pending: "neutral",
|
|
23
|
+
running: "neutral",
|
|
24
|
+
"output-available": "neutral",
|
|
25
|
+
error: "danger",
|
|
26
|
+
"approval-requested": "emphasis",
|
|
27
|
+
"output-denied": "danger"
|
|
28
|
+
};
|
|
29
|
+
/** True when the state carries no `error`/`running` weight in a warning sense but still opens by default. */ const OPENS_BY_DEFAULT = {
|
|
30
|
+
pending: false,
|
|
31
|
+
running: false,
|
|
32
|
+
"output-available": false,
|
|
33
|
+
error: true,
|
|
34
|
+
"approval-requested": true,
|
|
35
|
+
"output-denied": true
|
|
36
|
+
};
|
|
37
|
+
function humaniseToolName(name) {
|
|
38
|
+
const [provider, ...rest] = name.split("__");
|
|
39
|
+
const humanise = (value)=>{
|
|
40
|
+
const words = value.replace(/[_-]+/g, " ").trim();
|
|
41
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
42
|
+
};
|
|
43
|
+
if (rest.length > 0 && provider !== undefined) {
|
|
44
|
+
return `${humanise(rest.join("__"))} (${humanise(provider)})`;
|
|
45
|
+
}
|
|
46
|
+
return humanise(name);
|
|
47
|
+
}
|
|
48
|
+
function stateDetailText(state) {
|
|
49
|
+
switch(state.status){
|
|
50
|
+
case "output-available":
|
|
51
|
+
return state.output;
|
|
52
|
+
case "error":
|
|
53
|
+
return state.message;
|
|
54
|
+
case "approval-requested":
|
|
55
|
+
case "output-denied":
|
|
56
|
+
return state.reason;
|
|
57
|
+
case "pending":
|
|
58
|
+
case "running":
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function stateDetailTitle(state) {
|
|
63
|
+
switch(state.status){
|
|
64
|
+
case "error":
|
|
65
|
+
return "Error";
|
|
66
|
+
case "approval-requested":
|
|
67
|
+
return "Reason";
|
|
68
|
+
case "output-denied":
|
|
69
|
+
return "Denied because";
|
|
70
|
+
case "output-available":
|
|
71
|
+
case "pending":
|
|
72
|
+
case "running":
|
|
73
|
+
return "Output";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* What a tool call did, as one line the transcript can open.
|
|
78
|
+
*
|
|
79
|
+
* Collapsed by default and collapsed even while running: a transcript where
|
|
80
|
+
* every tool call expands itself pushes the answer off screen, and the
|
|
81
|
+
* answer is what the user is waiting for. A failed call, a denied approval
|
|
82
|
+
* and a pending approval request all open by default — those are the three
|
|
83
|
+
* states where the detail is the point, not an afterthought.
|
|
84
|
+
*
|
|
85
|
+
* Uncontrolled by default, seeded from `defaultOpen` (or the state's own
|
|
86
|
+
* default). Pass `open`/`onOpenChange` to drive it from a parent.
|
|
87
|
+
*/ export function ToolBlock({ name, label, state, input, defaultOpen, open: openProp, onOpenChange, className }) {
|
|
88
|
+
const [open, setOpen] = useControllableState({
|
|
89
|
+
value: openProp,
|
|
90
|
+
defaultValue: defaultOpen ?? OPENS_BY_DEFAULT[state.status],
|
|
91
|
+
onChange: onOpenChange,
|
|
92
|
+
name: "ToolBlock"
|
|
93
|
+
});
|
|
94
|
+
const detailText = stateDetailText(state);
|
|
95
|
+
const hasDetail = input !== undefined || detailText !== undefined;
|
|
96
|
+
const displayLabel = label !== undefined && label.length > 0 ? label : humaniseToolName(name);
|
|
97
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
98
|
+
"data-slot": "tool-block",
|
|
99
|
+
"data-status": state.status,
|
|
100
|
+
className: cn("text-xs", className),
|
|
101
|
+
children: [
|
|
102
|
+
/*#__PURE__*/ _jsxs("button", {
|
|
103
|
+
type: "button",
|
|
104
|
+
onClick: ()=>setOpen((value)=>!value),
|
|
105
|
+
"aria-expanded": open,
|
|
106
|
+
disabled: !hasDetail,
|
|
107
|
+
// `relative` plus the `::after` pseudo-element below extends the
|
|
108
|
+
// button's effective (vertical) hit area to 40px without inflating
|
|
109
|
+
// its own padding/density — see design-engineering.md's hit-area
|
|
110
|
+
// pattern. The row is already full-width, so only height needs
|
|
111
|
+
// extending.
|
|
112
|
+
className: "relative flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-muted-foreground transition-colors after:absolute after:inset-x-0 after:top-1/2 after:h-10 after:-translate-y-1/2 hover:bg-muted hover:text-foreground disabled:hover:bg-transparent",
|
|
113
|
+
children: [
|
|
114
|
+
/*#__PURE__*/ _jsx("span", {
|
|
115
|
+
"data-slot": "tool-block-status",
|
|
116
|
+
// A pending approval is human-blocking: it needs to reach assistive
|
|
117
|
+
// tech the instant it appears, not only when a reader happens to
|
|
118
|
+
// tab onto this button. Scoped to this span alone (not the whole
|
|
119
|
+
// block) so every other state change in a busy transcript stays
|
|
120
|
+
// silent — announcing every "Working" -> "Done" tick here would be
|
|
121
|
+
// spam.
|
|
122
|
+
role: state.status === "approval-requested" ? "status" : undefined,
|
|
123
|
+
"aria-live": state.status === "approval-requested" ? "polite" : undefined,
|
|
124
|
+
children: /*#__PURE__*/ _jsx(StatusDot, {
|
|
125
|
+
label: STATE_TEXT[state.status],
|
|
126
|
+
live: state.status === "running",
|
|
127
|
+
tone: STATE_TONE[state.status]
|
|
128
|
+
})
|
|
129
|
+
}),
|
|
130
|
+
/*#__PURE__*/ _jsx("span", {
|
|
131
|
+
className: "min-w-0 flex-1 truncate",
|
|
132
|
+
children: displayLabel
|
|
133
|
+
}),
|
|
134
|
+
hasDetail ? /*#__PURE__*/ _jsx(ChevronRight, {
|
|
135
|
+
className: cn("size-3.5 shrink-0 transition-transform duration-200 ease-out", open && "rotate-90"),
|
|
136
|
+
"aria-hidden": true
|
|
137
|
+
}) : null
|
|
138
|
+
]
|
|
139
|
+
}),
|
|
140
|
+
open && hasDetail ? /*#__PURE__*/ _jsxs("div", {
|
|
141
|
+
className: "mt-1 ml-4 flex flex-col gap-2 border-l border-border pl-3 [animation:corbits-rail-block-in_200ms_var(--ease-out)_both]",
|
|
142
|
+
children: [
|
|
143
|
+
input === undefined ? null : /*#__PURE__*/ _jsx(Detail, {
|
|
144
|
+
title: "Input",
|
|
145
|
+
children: JSON.stringify(input, null, 2)
|
|
146
|
+
}),
|
|
147
|
+
detailText === undefined ? null : /*#__PURE__*/ _jsx(Detail, {
|
|
148
|
+
title: stateDetailTitle(state),
|
|
149
|
+
children: detailText
|
|
150
|
+
})
|
|
151
|
+
]
|
|
152
|
+
}) : null
|
|
153
|
+
]
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function Detail({ title, children }) {
|
|
157
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
158
|
+
className: "flex flex-col gap-1",
|
|
159
|
+
children: [
|
|
160
|
+
/*#__PURE__*/ _jsx("p", {
|
|
161
|
+
className: "text-[10px] font-semibold tracking-[0.1em] text-muted-foreground uppercase",
|
|
162
|
+
children: title
|
|
163
|
+
}),
|
|
164
|
+
/*#__PURE__*/ _jsx("pre", {
|
|
165
|
+
className: "max-h-48 overflow-auto rounded-md bg-muted p-2 font-mono text-[11px] whitespace-pre-wrap",
|
|
166
|
+
children: children
|
|
167
|
+
})
|
|
168
|
+
]
|
|
169
|
+
});
|
|
170
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ToolPart } from "../lib/chat-message.js";
|
|
2
|
+
/**
|
|
3
|
+
* What the agent did, as one line the user can open.
|
|
4
|
+
*
|
|
5
|
+
* Collapsed by default and collapsed even while running: a transcript where
|
|
6
|
+
* every tool call expands itself pushes the answer off screen, and the answer
|
|
7
|
+
* is what the user is waiting for. The line still says which tool and how it
|
|
8
|
+
* ended, so nothing is hidden — only folded.
|
|
9
|
+
*
|
|
10
|
+
* A failed call opens by default. That is the one case where the detail is the
|
|
11
|
+
* point.
|
|
12
|
+
*
|
|
13
|
+
* Uncontrolled by default. Pass `open`/`onOpenChange` to drive it from a parent.
|
|
14
|
+
*/
|
|
15
|
+
export type ToolNarrativeProps = {
|
|
16
|
+
readonly part: ToolPart;
|
|
17
|
+
/** Controlled open state. Pair with `onOpenChange` to lift it to a parent. */
|
|
18
|
+
readonly open?: boolean;
|
|
19
|
+
readonly onOpenChange?: (open: boolean) => void;
|
|
20
|
+
readonly className?: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function ToolNarrative({ part, open: openProp, onOpenChange, className }: ToolNarrativeProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ChevronRight } from "lucide-react";
|
|
3
|
+
import { useControllableState } from "../hooks/use-controllable-state.js";
|
|
4
|
+
import { toolLabel } from "../lib/chat-message.js";
|
|
5
|
+
import { cn } from "../lib/utils.js";
|
|
6
|
+
import { StatusDot } from "./status-dot.js";
|
|
7
|
+
const STATE_TEXT = {
|
|
8
|
+
running: "Working",
|
|
9
|
+
done: "Done",
|
|
10
|
+
error: "Failed"
|
|
11
|
+
};
|
|
12
|
+
export function ToolNarrative({ part, open: openProp, onOpenChange, className }) {
|
|
13
|
+
const [open, setOpen] = useControllableState({
|
|
14
|
+
value: openProp,
|
|
15
|
+
defaultValue: part.state === "error",
|
|
16
|
+
onChange: onOpenChange,
|
|
17
|
+
name: "ToolNarrative"
|
|
18
|
+
});
|
|
19
|
+
const label = toolLabel(part);
|
|
20
|
+
const hasDetail = part.output !== undefined || part.input !== undefined;
|
|
21
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
22
|
+
"data-slot": "tool-narrative",
|
|
23
|
+
className: cn("text-xs", className),
|
|
24
|
+
children: [
|
|
25
|
+
/*#__PURE__*/ _jsxs("button", {
|
|
26
|
+
type: "button",
|
|
27
|
+
onClick: ()=>setOpen((value)=>!value),
|
|
28
|
+
"aria-expanded": open,
|
|
29
|
+
disabled: !hasDetail,
|
|
30
|
+
className: "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:hover:bg-transparent",
|
|
31
|
+
children: [
|
|
32
|
+
/*#__PURE__*/ _jsx(StatusDot, {
|
|
33
|
+
label: STATE_TEXT[part.state],
|
|
34
|
+
live: part.state === "running",
|
|
35
|
+
tone: part.state === "error" ? "danger" : part.state === "running" ? "emphasis" : "neutral"
|
|
36
|
+
}),
|
|
37
|
+
/*#__PURE__*/ _jsx("span", {
|
|
38
|
+
className: "min-w-0 flex-1 truncate",
|
|
39
|
+
children: label
|
|
40
|
+
}),
|
|
41
|
+
hasDetail ? /*#__PURE__*/ _jsx(ChevronRight, {
|
|
42
|
+
className: cn("size-3.5 shrink-0 transition-transform", open && "rotate-90"),
|
|
43
|
+
"aria-hidden": true
|
|
44
|
+
}) : null
|
|
45
|
+
]
|
|
46
|
+
}),
|
|
47
|
+
open && hasDetail ? /*#__PURE__*/ _jsxs("div", {
|
|
48
|
+
className: "mt-1 ml-4 flex flex-col gap-2 border-l border-border pl-3",
|
|
49
|
+
children: [
|
|
50
|
+
part.input === undefined ? null : /*#__PURE__*/ _jsx(Detail, {
|
|
51
|
+
title: "Input",
|
|
52
|
+
children: JSON.stringify(part.input, null, 2)
|
|
53
|
+
}),
|
|
54
|
+
part.output === undefined ? null : /*#__PURE__*/ _jsx(Detail, {
|
|
55
|
+
title: part.state === "error" ? "Error" : "Output",
|
|
56
|
+
children: part.output
|
|
57
|
+
})
|
|
58
|
+
]
|
|
59
|
+
}) : null
|
|
60
|
+
]
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function Detail({ title, children }) {
|
|
64
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
65
|
+
className: "flex flex-col gap-1",
|
|
66
|
+
children: [
|
|
67
|
+
/*#__PURE__*/ _jsx("p", {
|
|
68
|
+
className: "text-[10px] font-semibold tracking-[0.1em] text-muted-foreground uppercase",
|
|
69
|
+
children: title
|
|
70
|
+
}),
|
|
71
|
+
/*#__PURE__*/ _jsx("pre", {
|
|
72
|
+
className: "max-h-48 overflow-auto rounded-md bg-muted p-2 font-mono text-[11px] whitespace-pre-wrap",
|
|
73
|
+
children: children
|
|
74
|
+
})
|
|
75
|
+
]
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export type ToolPickerStatus = "pending" | "disabled";
|
|
3
|
+
/**
|
|
4
|
+
* One entry in the caller-supplied catalog — a tool the picker can list,
|
|
5
|
+
* search and select. Shaped like a `DataTable` row: plain data in, no
|
|
6
|
+
* behaviour, so the same catalog can back a table elsewhere without a
|
|
7
|
+
* reshape.
|
|
8
|
+
*/
|
|
9
|
+
export type ToolPickerItem = {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly name: string;
|
|
12
|
+
/** Groups the list — the picker's one piece of structure over the flat catalog. */
|
|
13
|
+
readonly package: string;
|
|
14
|
+
readonly description?: string;
|
|
15
|
+
/** Absent means available. `disabled` blocks selection; `pending` allows it. */
|
|
16
|
+
readonly status?: ToolPickerStatus;
|
|
17
|
+
};
|
|
18
|
+
export type ToolPickerProps = {
|
|
19
|
+
readonly items: readonly ToolPickerItem[];
|
|
20
|
+
/** Selected ids. A single-select picker still carries at most one entry here. */
|
|
21
|
+
readonly value: readonly string[];
|
|
22
|
+
readonly onChange: (value: readonly string[]) => void;
|
|
23
|
+
/** Defaults to single-select: a new pick replaces the current one. */
|
|
24
|
+
readonly multiple?: boolean;
|
|
25
|
+
/** First load — the catalog has not arrived yet, distinct from an empty catalog. */
|
|
26
|
+
readonly loading?: boolean;
|
|
27
|
+
/** Shown when the catalog itself is empty. Failed searches use a separate default. */
|
|
28
|
+
readonly empty?: ReactNode;
|
|
29
|
+
readonly searchPlaceholder?: string;
|
|
30
|
+
/** Accessible name for the search input — set it if this isn't picking tools. */
|
|
31
|
+
readonly searchLabel?: string;
|
|
32
|
+
readonly className?: string;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Browse, search and select tools from a caller-supplied catalog, grouped by
|
|
36
|
+
* package. Controlled like a form field (`value`/`onChange`) rather than
|
|
37
|
+
* owning selection itself — a caller wiring this into a bigger form needs
|
|
38
|
+
* the same value it would get from any other control.
|
|
39
|
+
*
|
|
40
|
+
* Disabled items stay in the list rather than disappearing — a tool a
|
|
41
|
+
* caller cannot pick right now is still information ("already attached
|
|
42
|
+
* elsewhere", "needs a credential") that vanishing would hide.
|
|
43
|
+
*/
|
|
44
|
+
export declare function ToolPicker({ items, value, onChange, multiple, loading, empty, searchPlaceholder, searchLabel, className, }: ToolPickerProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { useCommandPaletteNavigation } from "../hooks/use-command-palette-navigation.js";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
import { Badge } from "./badge.js";
|
|
6
|
+
import { EmptyState } from "./empty-state.js";
|
|
7
|
+
import { Input } from "./input.js";
|
|
8
|
+
const STATUS_LABEL = {
|
|
9
|
+
pending: "Pending",
|
|
10
|
+
disabled: "Disabled"
|
|
11
|
+
};
|
|
12
|
+
function matches(item, query) {
|
|
13
|
+
if (query.length === 0) return true;
|
|
14
|
+
const haystack = `${item.name} ${item.package} ${item.description ?? ""}`.toLowerCase();
|
|
15
|
+
return haystack.includes(query.toLowerCase());
|
|
16
|
+
}
|
|
17
|
+
function groupByPackage(items) {
|
|
18
|
+
const groups = new Map();
|
|
19
|
+
for (const item of items){
|
|
20
|
+
const group = groups.get(item.package);
|
|
21
|
+
if (group === undefined) groups.set(item.package, [
|
|
22
|
+
item
|
|
23
|
+
]);
|
|
24
|
+
else group.push(item);
|
|
25
|
+
}
|
|
26
|
+
return [
|
|
27
|
+
...groups.entries()
|
|
28
|
+
].map(([id, groupItems])=>({
|
|
29
|
+
id,
|
|
30
|
+
items: groupItems
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
/** Visible group title: a display name if the catalog already sent one, else a humanized slug. */ function groupHeading(id) {
|
|
34
|
+
if (/[A-Z]/.test(id) || /\s/.test(id)) return id;
|
|
35
|
+
return id.split(/[-_./]+/).filter(Boolean).map((part)=>part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Browse, search and select tools from a caller-supplied catalog, grouped by
|
|
39
|
+
* package. Controlled like a form field (`value`/`onChange`) rather than
|
|
40
|
+
* owning selection itself — a caller wiring this into a bigger form needs
|
|
41
|
+
* the same value it would get from any other control.
|
|
42
|
+
*
|
|
43
|
+
* Disabled items stay in the list rather than disappearing — a tool a
|
|
44
|
+
* caller cannot pick right now is still information ("already attached
|
|
45
|
+
* elsewhere", "needs a credential") that vanishing would hide.
|
|
46
|
+
*/ export function ToolPicker({ items, value, onChange, multiple = false, loading = false, empty, searchPlaceholder = "Search tools…", searchLabel = "Search tools", className }) {
|
|
47
|
+
const baseId = useId();
|
|
48
|
+
const [query, setQuery] = useState("");
|
|
49
|
+
const listRef = useRef(null);
|
|
50
|
+
const filtered = useMemo(()=>items.filter((item)=>matches(item, query)), [
|
|
51
|
+
items,
|
|
52
|
+
query
|
|
53
|
+
]);
|
|
54
|
+
const groups = useMemo(()=>groupByPackage(filtered), [
|
|
55
|
+
filtered
|
|
56
|
+
]);
|
|
57
|
+
const selectedIds = useMemo(()=>new Set(value), [
|
|
58
|
+
value
|
|
59
|
+
]);
|
|
60
|
+
const toggle = (item)=>{
|
|
61
|
+
if (item.status === "disabled") return;
|
|
62
|
+
const isSelected = selectedIds.has(item.id);
|
|
63
|
+
if (!multiple) {
|
|
64
|
+
onChange(isSelected ? [] : [
|
|
65
|
+
item.id
|
|
66
|
+
]);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
onChange(isSelected ? value.filter((id)=>id !== item.id) : [
|
|
70
|
+
...value,
|
|
71
|
+
item.id
|
|
72
|
+
]);
|
|
73
|
+
};
|
|
74
|
+
const navigation = useCommandPaletteNavigation({
|
|
75
|
+
items: filtered,
|
|
76
|
+
onSelect: (id)=>{
|
|
77
|
+
const item = filtered.find((candidate)=>candidate.id === id);
|
|
78
|
+
if (item !== undefined) toggle(item);
|
|
79
|
+
},
|
|
80
|
+
onClose: ()=>{
|
|
81
|
+
/* Escape is handled below so an empty query does not swallow the key. */ }
|
|
82
|
+
});
|
|
83
|
+
useEffect(()=>{
|
|
84
|
+
listRef.current?.querySelector('[data-active="true"]')?.scrollIntoView({
|
|
85
|
+
block: "nearest"
|
|
86
|
+
});
|
|
87
|
+
}, [
|
|
88
|
+
navigation.activeId
|
|
89
|
+
]);
|
|
90
|
+
const optionId = (id)=>`${baseId}-option-${id}`;
|
|
91
|
+
const onKeyDown = (event)=>{
|
|
92
|
+
if (event.key === "Escape") {
|
|
93
|
+
if (query.length > 0) {
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
setQuery("");
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
navigation.onKeyDown(event);
|
|
100
|
+
};
|
|
101
|
+
const emptyCatalog = items.length === 0;
|
|
102
|
+
const noMatches = !emptyCatalog && filtered.length === 0;
|
|
103
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
104
|
+
"data-slot": "tool-picker",
|
|
105
|
+
className: cn("flex min-h-0 flex-col gap-2", className),
|
|
106
|
+
children: [
|
|
107
|
+
/*#__PURE__*/ _jsx(Input, {
|
|
108
|
+
value: query,
|
|
109
|
+
onChange: (event)=>setQuery(event.target.value),
|
|
110
|
+
onKeyDown: onKeyDown,
|
|
111
|
+
placeholder: searchPlaceholder,
|
|
112
|
+
"aria-label": searchLabel,
|
|
113
|
+
role: "combobox",
|
|
114
|
+
"aria-expanded": true,
|
|
115
|
+
"aria-autocomplete": "list",
|
|
116
|
+
"aria-controls": `${baseId}-list`,
|
|
117
|
+
"aria-activedescendant": navigation.activeId === undefined ? undefined : optionId(navigation.activeId),
|
|
118
|
+
"aria-busy": loading,
|
|
119
|
+
autoComplete: "off",
|
|
120
|
+
spellCheck: false
|
|
121
|
+
}),
|
|
122
|
+
/*#__PURE__*/ _jsx("div", {
|
|
123
|
+
ref: listRef,
|
|
124
|
+
id: `${baseId}-list`,
|
|
125
|
+
role: "listbox",
|
|
126
|
+
"aria-label": "Tools",
|
|
127
|
+
"aria-multiselectable": multiple,
|
|
128
|
+
className: "flex min-h-0 flex-1 flex-col gap-3 overflow-auto",
|
|
129
|
+
children: loading ? /*#__PURE__*/ _jsx("p", {
|
|
130
|
+
role: "presentation",
|
|
131
|
+
className: "px-2 py-8 text-center text-sm text-muted-foreground",
|
|
132
|
+
children: "Loading…"
|
|
133
|
+
}) : emptyCatalog ? /*#__PURE__*/ _jsx("div", {
|
|
134
|
+
role: "presentation",
|
|
135
|
+
children: empty ?? /*#__PURE__*/ _jsx(EmptyState, {
|
|
136
|
+
title: "No tools",
|
|
137
|
+
description: "Nothing in the catalog yet."
|
|
138
|
+
})
|
|
139
|
+
}) : noMatches ? /*#__PURE__*/ _jsx("p", {
|
|
140
|
+
role: "presentation",
|
|
141
|
+
className: "px-2 py-8 text-center text-sm text-muted-foreground",
|
|
142
|
+
children: "No tools match — try a different search."
|
|
143
|
+
}) : groups.map((group)=>/*#__PURE__*/ _jsxs("div", {
|
|
144
|
+
role: "presentation",
|
|
145
|
+
className: "flex flex-col gap-1",
|
|
146
|
+
children: [
|
|
147
|
+
/*#__PURE__*/ _jsx("p", {
|
|
148
|
+
className: "px-3.5 py-1.5 text-[11px] font-semibold uppercase tracking-[0.1em] text-muted-foreground",
|
|
149
|
+
children: groupHeading(group.id)
|
|
150
|
+
}),
|
|
151
|
+
/*#__PURE__*/ _jsx("div", {
|
|
152
|
+
className: "flex flex-col gap-1",
|
|
153
|
+
children: group.items.map((item)=>/*#__PURE__*/ _jsx(ToolPickerOption, {
|
|
154
|
+
id: optionId(item.id),
|
|
155
|
+
item: item,
|
|
156
|
+
selected: selectedIds.has(item.id),
|
|
157
|
+
active: navigation.activeId === item.id,
|
|
158
|
+
onSelect: ()=>toggle(item),
|
|
159
|
+
onPointerMove: ()=>navigation.setActiveId(item.id)
|
|
160
|
+
}, item.id))
|
|
161
|
+
})
|
|
162
|
+
]
|
|
163
|
+
}, group.id))
|
|
164
|
+
})
|
|
165
|
+
]
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function ToolPickerOption({ id, item, selected, active, onSelect, onPointerMove }) {
|
|
169
|
+
const disabled = item.status === "disabled";
|
|
170
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
171
|
+
id: id,
|
|
172
|
+
role: "option",
|
|
173
|
+
"aria-selected": selected,
|
|
174
|
+
"aria-disabled": disabled,
|
|
175
|
+
"data-active": active,
|
|
176
|
+
onPointerMove: disabled ? undefined : onPointerMove,
|
|
177
|
+
onClick: disabled ? undefined : onSelect,
|
|
178
|
+
className: cn("flex w-full flex-col gap-1 rounded-lg border px-3.5 py-3 text-left transition-colors ease-out", disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer hover:bg-muted active:brightness-95", selected ? "border-primary-emphasis bg-primary/10" : "border-border bg-card", // The keyboard cursor must stay visible on disabled rows — aria-activedescendant can land there.
|
|
179
|
+
active && "bg-muted"),
|
|
180
|
+
children: [
|
|
181
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
182
|
+
className: "flex items-center gap-2",
|
|
183
|
+
children: [
|
|
184
|
+
/*#__PURE__*/ _jsx("span", {
|
|
185
|
+
className: "text-sm font-semibold",
|
|
186
|
+
children: item.name
|
|
187
|
+
}),
|
|
188
|
+
item.status === undefined ? null : /*#__PURE__*/ _jsx(Badge, {
|
|
189
|
+
tone: "neutral",
|
|
190
|
+
children: STATUS_LABEL[item.status]
|
|
191
|
+
})
|
|
192
|
+
]
|
|
193
|
+
}),
|
|
194
|
+
item.description === undefined ? null : /*#__PURE__*/ _jsx("span", {
|
|
195
|
+
className: "text-xs leading-snug text-muted-foreground",
|
|
196
|
+
children: item.description
|
|
197
|
+
})
|
|
198
|
+
]
|
|
199
|
+
});
|
|
200
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type InfoTooltipProps = {
|
|
2
|
+
/** The note shown in the tooltip and bound to the trigger via `aria-describedby`. */
|
|
3
|
+
readonly label: string;
|
|
4
|
+
/** Accessible name for the trigger button. Defaults to `label`. */
|
|
5
|
+
readonly triggerLabel?: string;
|
|
6
|
+
/** Render already open — uncontrolled, for stories and screenshots. */
|
|
7
|
+
readonly defaultOpen?: boolean;
|
|
8
|
+
readonly className?: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* A focusable info glyph that discloses a short note on hover or focus.
|
|
12
|
+
*
|
|
13
|
+
* Radix owns the `aria-describedby` wiring, the open-on-focus/hover
|
|
14
|
+
* behaviour, and Escape-to-dismiss; this layer only paints. Built for notes
|
|
15
|
+
* that today get bolted onto a non-interactive element via a `title`
|
|
16
|
+
* attribute — that reaches a mouse only, is never announced by a screen
|
|
17
|
+
* reader, and never fires on touch. A `<button>` trigger fixes all three:
|
|
18
|
+
* it is in the tab order, Radix associates the note by id, and tapping it
|
|
19
|
+
* opens the tooltip on touch devices.
|
|
20
|
+
*/
|
|
21
|
+
export declare function InfoTooltip({ label, triggerLabel, defaultOpen, className }: InfoTooltipProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
3
|
+
import { InfoIcon } from "lucide-react";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
/**
|
|
6
|
+
* A focusable info glyph that discloses a short note on hover or focus.
|
|
7
|
+
*
|
|
8
|
+
* Radix owns the `aria-describedby` wiring, the open-on-focus/hover
|
|
9
|
+
* behaviour, and Escape-to-dismiss; this layer only paints. Built for notes
|
|
10
|
+
* that today get bolted onto a non-interactive element via a `title`
|
|
11
|
+
* attribute — that reaches a mouse only, is never announced by a screen
|
|
12
|
+
* reader, and never fires on touch. A `<button>` trigger fixes all three:
|
|
13
|
+
* it is in the tab order, Radix associates the note by id, and tapping it
|
|
14
|
+
* opens the tooltip on touch devices.
|
|
15
|
+
*/ export function InfoTooltip({ label, triggerLabel, defaultOpen, className }) {
|
|
16
|
+
return /*#__PURE__*/ _jsx(TooltipPrimitive.Provider, {
|
|
17
|
+
delayDuration: 200,
|
|
18
|
+
children: /*#__PURE__*/ _jsxs(TooltipPrimitive.Root, {
|
|
19
|
+
defaultOpen: defaultOpen,
|
|
20
|
+
children: [
|
|
21
|
+
/*#__PURE__*/ _jsx(TooltipPrimitive.Trigger, {
|
|
22
|
+
asChild: true,
|
|
23
|
+
children: /*#__PURE__*/ _jsx("button", {
|
|
24
|
+
type: "button",
|
|
25
|
+
"aria-label": triggerLabel ?? label,
|
|
26
|
+
className: cn("inline-flex size-4 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors", "hover:text-foreground focus-visible:text-foreground focus-visible:outline-none", "focus-visible:shadow-[0_0_0_3px_color-mix(in_srgb,var(--primary)_22%,transparent)]", className),
|
|
27
|
+
children: /*#__PURE__*/ _jsx(InfoIcon, {
|
|
28
|
+
className: "size-full"
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
}),
|
|
32
|
+
/*#__PURE__*/ _jsx(TooltipPrimitive.Portal, {
|
|
33
|
+
children: /*#__PURE__*/ _jsxs(TooltipPrimitive.Content, {
|
|
34
|
+
"data-slot": "tooltip-content",
|
|
35
|
+
sideOffset: 6,
|
|
36
|
+
className: cn("z-50 max-w-64 rounded-md border border-border bg-popover px-2.5 py-1.5 text-xs text-popover-foreground shadow-lg", "data-[state=delayed-open]:animate-[corbits-fade-in_150ms_ease-out] data-[state=closed]:animate-[corbits-fade-out_120ms_ease-in]"),
|
|
37
|
+
children: [
|
|
38
|
+
label,
|
|
39
|
+
/*#__PURE__*/ _jsx(TooltipPrimitive.Arrow, {
|
|
40
|
+
className: "fill-popover"
|
|
41
|
+
})
|
|
42
|
+
]
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
]
|
|
46
|
+
})
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* The row above the page. It owns no content of its own — a title or a
|
|
4
|
+
* breadcrumb trail on the left, whatever the page contributes on the right.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately a `<header>` with parts rather than a `title`/`actions` prop
|
|
7
|
+
* pair: pages need a search box, a filter row or a context strip in that space,
|
|
8
|
+
* and a fixed prop list would push every one of them through a `ReactNode` prop
|
|
9
|
+
* that does the same job as a child.
|
|
10
|
+
*/
|
|
11
|
+
export declare function TopBar({ className, ...props }: React.ComponentProps<"header">): React.JSX.Element;
|
|
12
|
+
export declare function TopBarTitle({ count, subtitle, className, children, ...props }: React.ComponentProps<"div"> & {
|
|
13
|
+
count?: number;
|
|
14
|
+
subtitle?: string;
|
|
15
|
+
}): React.JSX.Element;
|
|
16
|
+
export type Crumb = {
|
|
17
|
+
readonly label: string;
|
|
18
|
+
/** Omit on the last crumb — the current page is not a link. */
|
|
19
|
+
readonly href?: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Props a breadcrumb link receives, whether it renders as a plain `<a>` or a
|
|
23
|
+
* caller-supplied component. Only non-last crumbs render through this — see
|
|
24
|
+
* `TopBarBreadcrumbs` for why the last crumb is never a link.
|
|
25
|
+
*/
|
|
26
|
+
export type BreadcrumbLinkProps = {
|
|
27
|
+
readonly href: string;
|
|
28
|
+
readonly className: string;
|
|
29
|
+
readonly children: React.ReactNode;
|
|
30
|
+
};
|
|
31
|
+
export type TopBarBreadcrumbsProps = Omit<React.ComponentProps<"nav">, "children"> & {
|
|
32
|
+
crumbs: readonly Crumb[];
|
|
33
|
+
/**
|
|
34
|
+
* Renders every non-last crumb; defaults to a plain `<a>`. Must be a
|
|
35
|
+
* stable, module-scope reference — an inline arrow here creates a new
|
|
36
|
+
* component type on every render, which remounts every crumb. A router
|
|
37
|
+
* `Link` (e.g. React Router) that takes `to` instead of `href` needs a
|
|
38
|
+
* one-line wrapper: `({ href, ...rest }) => <Link to={href} {...rest} />`.
|
|
39
|
+
*/
|
|
40
|
+
linkComponent?: React.ComponentType<BreadcrumbLinkProps>;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Breadcrumb trail. The last crumb is the current page: it is rendered as text
|
|
44
|
+
* with `aria-current="page"` even if a caller passes an href, because linking
|
|
45
|
+
* to where you already are is a known screen-reader annoyance.
|
|
46
|
+
*
|
|
47
|
+
* Every other crumb renders through `linkComponent`, defaulting to a plain
|
|
48
|
+
* `<a>`. A consumer routing through a SPA router passes its own `Link` so a
|
|
49
|
+
* breadcrumb click does not force a full page reload.
|
|
50
|
+
*/
|
|
51
|
+
export declare function TopBarBreadcrumbs({ crumbs, linkComponent: LinkComponent, className, ...props }: TopBarBreadcrumbsProps): React.JSX.Element;
|
|
52
|
+
/** Trailing controls. Pushes itself to the right; put it last. */
|
|
53
|
+
export declare function TopBarActions({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|