@corbits/react-ui 0.1.0 → 0.2.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 +174 -499
- package/README.md +228 -85
- package/dist/blocks/login/auth-layout.d.ts +1 -1
- package/dist/blocks/login/auth-layout.js +3 -33
- package/dist/blocks/login/login-form.js +6 -119
- package/dist/hooks/use-command-palette-navigation.js +18 -17
- package/dist/hooks/use-controllable-state.d.ts +2 -2
- package/dist/hooks/use-controllable-state.js +23 -15
- package/dist/hooks/use-delayed-autofocus.js +10 -11
- package/dist/hooks/use-dismissable-popover.d.ts +2 -2
- package/dist/hooks/use-dismissable-popover.js +21 -29
- package/dist/hooks/use-list-selection.js +54 -85
- package/dist/hooks/use-prefers-reduced-motion.js +8 -4
- package/dist/hooks/use-scroll-current-into-view.js +5 -7
- package/dist/index.d.ts +0 -11
- package/dist/index.js +10 -19
- package/dist/lib/artifact.js +16 -35
- package/dist/lib/chart-geometry.js +33 -20
- package/dist/lib/chart-palette.js +17 -31
- package/dist/lib/chat-message.js +32 -13
- package/dist/lib/chat-parts.js +2 -1
- package/dist/lib/csv.js +42 -26
- package/dist/lib/metrics.js +30 -48
- package/dist/lib/now-item.js +26 -25
- package/dist/lib/relative-time.js +14 -29
- package/dist/lib/theme.js +32 -30
- package/dist/lib/url.js +9 -8
- package/dist/lib/workflow-display-flow.js +37 -33
- package/dist/lib/workflow-registry.d.ts +0 -41
- package/dist/lib/workflow-registry.js +13 -23
- package/dist/lib/workflow-run-error.js +16 -20
- package/dist/lib/workflow-run-progress.js +94 -52
- package/dist/lib/workflow-run.js +17 -13
- package/dist/styles.css +11 -11
- package/dist/test/render-hook.js +11 -12
- package/dist/theme.css +51 -14
- package/dist/ui/agent-turn.d.ts +1 -1
- package/dist/ui/agent-turn.js +4 -37
- package/dist/ui/animated-number.js +15 -28
- package/dist/ui/approval-card.js +5 -90
- package/dist/ui/artifact-body.js +47 -125
- package/dist/ui/artifact-notice.d.ts +1 -1
- package/dist/ui/artifact-notice.js +9 -16
- package/dist/ui/avatar.d.ts +1 -1
- package/dist/ui/avatar.js +11 -48
- package/dist/ui/badge.js +4 -12
- package/dist/ui/bar-chart.d.ts +1 -1
- package/dist/ui/bar-chart.js +22 -74
- package/dist/ui/block-card.js +7 -45
- package/dist/ui/boot-screen.d.ts +1 -1
- package/dist/ui/boot-screen.js +4 -33
- package/dist/ui/bulk-action-bar.d.ts +1 -1
- package/dist/ui/bulk-action-bar.js +14 -33
- package/dist/ui/button.js +10 -24
- package/dist/ui/card.js +5 -25
- package/dist/ui/category-bars.js +14 -59
- package/dist/ui/chart-frame.d.ts +2 -2
- package/dist/ui/chart-frame.js +6 -85
- package/dist/ui/chat-composer.js +4 -16
- package/dist/ui/chat-dock-timing.js +7 -3
- package/dist/ui/chat-input.js +20 -100
- package/dist/ui/chat-panel.d.ts +1 -1
- package/dist/ui/chat-panel.js +7 -41
- package/dist/ui/chat-thread.js +13 -31
- package/dist/ui/checkbox.js +10 -52
- package/dist/ui/command-palette.js +71 -246
- package/dist/ui/compare-body.d.ts +1 -1
- package/dist/ui/compare-body.js +28 -98
- package/dist/ui/confirm-button.js +8 -15
- package/dist/ui/corbits-mark.js +7 -16
- package/dist/ui/csv-table.d.ts +1 -1
- package/dist/ui/csv-table.js +8 -50
- package/dist/ui/dialog.d.ts +1 -1
- package/dist/ui/dialog.js +32 -62
- package/dist/ui/dictation-status-line.js +7 -25
- package/dist/ui/dither-canvas.d.ts +1 -1
- package/dist/ui/dither-canvas.js +31 -56
- package/dist/ui/embed-body.d.ts +1 -1
- package/dist/ui/embed-body.js +7 -56
- package/dist/ui/empty-state.d.ts +1 -1
- package/dist/ui/empty-state.js +3 -26
- package/dist/ui/file-input.js +17 -37
- package/dist/ui/filter-bar.d.ts +1 -1
- package/dist/ui/filter-bar.js +11 -71
- package/dist/ui/filter-chip.d.ts +1 -1
- package/dist/ui/filter-chip.js +7 -15
- package/dist/ui/gate-block.d.ts +2 -1
- package/dist/ui/gate-block.js +4 -29
- package/dist/ui/horizontal-stepper.d.ts +1 -1
- package/dist/ui/horizontal-stepper.js +18 -45
- package/dist/ui/input.js +1 -6
- package/dist/ui/inspector-shell.d.ts +5 -5
- package/dist/ui/inspector-shell.js +14 -81
- package/dist/ui/intake-form.d.ts +1 -1
- package/dist/ui/intake-form.js +21 -97
- package/dist/ui/kind-card-grid.js +9 -31
- package/dist/ui/library-page-header.js +4 -32
- package/dist/ui/list-detail.js +5 -35
- package/dist/ui/live-run-banner.d.ts +1 -1
- package/dist/ui/live-run-banner.js +5 -15
- package/dist/ui/live-status-line.js +6 -24
- package/dist/ui/menu.js +7 -24
- package/dist/ui/message-bubble.d.ts +3 -3
- package/dist/ui/message-bubble.js +5 -44
- package/dist/ui/mic-button.d.ts +1 -1
- package/dist/ui/mic-button.js +15 -21
- package/dist/ui/mic-permission-dialog.js +5 -41
- package/dist/ui/notifications-bell.js +9 -43
- package/dist/ui/now-cards.d.ts +2 -2
- package/dist/ui/now-cards.js +16 -94
- package/dist/ui/page-panel.js +11 -11
- package/dist/ui/page-shell.js +4 -11
- package/dist/ui/parts-renderer.js +51 -149
- package/dist/ui/profile-card.js +7 -111
- package/dist/ui/progress-checklist.js +11 -49
- package/dist/ui/provider-mark.d.ts +2 -2
- package/dist/ui/provider-mark.js +9 -16
- package/dist/ui/quick-reply-chips.js +6 -14
- package/dist/ui/quote-card.js +12 -32
- package/dist/ui/reasoning-block.d.ts +1 -1
- package/dist/ui/reasoning-block.js +14 -33
- package/dist/ui/research-body.js +32 -213
- package/dist/ui/rich-empty-state.js +6 -48
- package/dist/ui/run-now-button.js +14 -15
- package/dist/ui/section.d.ts +1 -1
- package/dist/ui/section.js +4 -40
- package/dist/ui/select.js +1 -15
- package/dist/ui/selection-checkbox.d.ts +1 -1
- package/dist/ui/selection-checkbox.js +11 -29
- package/dist/ui/settings-panel.js +7 -50
- package/dist/ui/shimmer-text.js +3 -6
- package/dist/ui/sidebar-item-row.d.ts +1 -1
- package/dist/ui/sidebar-item-row.js +9 -39
- package/dist/ui/sidebar-panel.js +15 -40
- package/dist/ui/sidebar.js +15 -76
- package/dist/ui/skeleton.js +3 -7
- package/dist/ui/sortable-table.js +29 -72
- package/dist/ui/sparkline.js +14 -38
- package/dist/ui/stat-grid.js +17 -52
- package/dist/ui/status-dot.d.ts +1 -1
- package/dist/ui/status-dot.js +8 -20
- package/dist/ui/step-list.d.ts +1 -0
- package/dist/ui/step-list.js +11 -50
- package/dist/ui/switch.js +4 -16
- package/dist/ui/table.js +7 -32
- package/dist/ui/tabs.js +39 -54
- package/dist/ui/textarea.js +10 -19
- package/dist/ui/theme-provider.js +54 -78
- package/dist/ui/theme-toggle.js +13 -16
- package/dist/ui/thinking-indicator.js +3 -15
- package/dist/ui/thinking-label.js +16 -35
- package/dist/ui/thinking-mark.d.ts +1 -1
- package/dist/ui/thinking-mark.js +4 -145
- package/dist/ui/time-series-chart.js +43 -174
- package/dist/ui/toast.js +12 -20
- package/dist/ui/token-mosaic.js +5 -43
- package/dist/ui/tool-block.js +18 -70
- package/dist/ui/tool-narrative.d.ts +1 -1
- package/dist/ui/tool-narrative.js +10 -57
- package/dist/ui/tool-picker.js +57 -145
- package/dist/ui/tooltip.d.ts +1 -1
- package/dist/ui/tooltip.js +4 -33
- package/dist/ui/top-bar.js +14 -72
- package/dist/ui/trace-waterfall.d.ts +1 -1
- package/dist/ui/trace-waterfall.js +21 -93
- package/dist/ui/typing-indicator.d.ts +1 -1
- package/dist/ui/typing-indicator.js +4 -18
- package/dist/ui/view-toggle.js +10 -29
- package/dist/ui/voice-waveform.js +3 -11
- package/package.json +94 -535
package/dist/ui/tool-block.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { ChevronRight } from "lucide-react";
|
|
3
4
|
import { useControllableState } from "../hooks/use-controllable-state.js";
|
|
@@ -9,7 +10,7 @@ const STATE_TEXT = {
|
|
|
9
10
|
"output-available": "Done",
|
|
10
11
|
error: "Failed",
|
|
11
12
|
"approval-requested": "Needs approval",
|
|
12
|
-
"output-denied": "Denied"
|
|
13
|
+
"output-denied": "Denied",
|
|
13
14
|
};
|
|
14
15
|
/**
|
|
15
16
|
* Orange (`emphasis`) is reserved for `approval-requested` alone: it is the
|
|
@@ -18,25 +19,27 @@ const STATE_TEXT = {
|
|
|
18
19
|
* through the dot's own pulse animation (`StatusDot`'s `live` prop) — giving
|
|
19
20
|
* it orange too would stack two "look here" signals on a state that isn't
|
|
20
21
|
* actually asking for anything.
|
|
21
|
-
*/
|
|
22
|
+
*/
|
|
23
|
+
const STATE_TONE = {
|
|
22
24
|
pending: "neutral",
|
|
23
25
|
running: "neutral",
|
|
24
26
|
"output-available": "neutral",
|
|
25
27
|
error: "danger",
|
|
26
28
|
"approval-requested": "emphasis",
|
|
27
|
-
"output-denied": "danger"
|
|
29
|
+
"output-denied": "danger",
|
|
28
30
|
};
|
|
29
|
-
/** True when the state carries no `error`/`running` weight in a warning sense but still opens by default. */
|
|
31
|
+
/** True when the state carries no `error`/`running` weight in a warning sense but still opens by default. */
|
|
32
|
+
const OPENS_BY_DEFAULT = {
|
|
30
33
|
pending: false,
|
|
31
34
|
running: false,
|
|
32
35
|
"output-available": false,
|
|
33
36
|
error: true,
|
|
34
37
|
"approval-requested": true,
|
|
35
|
-
"output-denied": true
|
|
38
|
+
"output-denied": true,
|
|
36
39
|
};
|
|
37
40
|
function humaniseToolName(name) {
|
|
38
41
|
const [provider, ...rest] = name.split("__");
|
|
39
|
-
const humanise = (value)=>{
|
|
42
|
+
const humanise = (value) => {
|
|
40
43
|
const words = value.replace(/[_-]+/g, " ").trim();
|
|
41
44
|
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
42
45
|
};
|
|
@@ -46,7 +49,7 @@ function humaniseToolName(name) {
|
|
|
46
49
|
return humanise(name);
|
|
47
50
|
}
|
|
48
51
|
function stateDetailText(state) {
|
|
49
|
-
switch(state.status){
|
|
52
|
+
switch (state.status) {
|
|
50
53
|
case "output-available":
|
|
51
54
|
return state.output;
|
|
52
55
|
case "error":
|
|
@@ -60,7 +63,7 @@ function stateDetailText(state) {
|
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
function stateDetailTitle(state) {
|
|
63
|
-
switch(state.status){
|
|
66
|
+
switch (state.status) {
|
|
64
67
|
case "error":
|
|
65
68
|
return "Error";
|
|
66
69
|
case "approval-requested":
|
|
@@ -84,87 +87,32 @@ function stateDetailTitle(state) {
|
|
|
84
87
|
*
|
|
85
88
|
* Uncontrolled by default, seeded from `defaultOpen` (or the state's own
|
|
86
89
|
* default). Pass `open`/`onOpenChange` to drive it from a parent.
|
|
87
|
-
*/
|
|
90
|
+
*/
|
|
91
|
+
export function ToolBlock({ name, label, state, input, defaultOpen, open: openProp, onOpenChange, className, }) {
|
|
88
92
|
const [open, setOpen] = useControllableState({
|
|
89
93
|
value: openProp,
|
|
90
94
|
defaultValue: defaultOpen ?? OPENS_BY_DEFAULT[state.status],
|
|
91
95
|
onChange: onOpenChange,
|
|
92
|
-
name: "ToolBlock"
|
|
96
|
+
name: "ToolBlock",
|
|
93
97
|
});
|
|
94
98
|
const detailText = stateDetailText(state);
|
|
95
99
|
const hasDetail = input !== undefined || detailText !== undefined;
|
|
96
100
|
const displayLabel = label !== undefined && label.length > 0 ? label : humaniseToolName(name);
|
|
97
|
-
return
|
|
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,
|
|
101
|
+
return (_jsxs("div", { "data-slot": "tool-block", "data-status": state.status, className: cn("text-xs", className), children: [_jsxs("button", { type: "button", onClick: () => setOpen((value) => !value), "aria-expanded": open, disabled: !hasDetail,
|
|
107
102
|
// `relative` plus the `::after` pseudo-element below extends the
|
|
108
103
|
// button's effective (vertical) hit area to 40px without inflating
|
|
109
104
|
// its own padding/density — see design-engineering.md's hit-area
|
|
110
105
|
// pattern. The row is already full-width, so only height needs
|
|
111
106
|
// 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",
|
|
107
|
+
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", children: [_jsx("span", { "data-slot": "tool-block-status",
|
|
116
108
|
// A pending approval is human-blocking: it needs to reach assistive
|
|
117
109
|
// tech the instant it appears, not only when a reader happens to
|
|
118
110
|
// tab onto this button. Scoped to this span alone (not the whole
|
|
119
111
|
// block) so every other state change in a busy transcript stays
|
|
120
112
|
// silent — announcing every "Working" -> "Done" tick here would be
|
|
121
113
|
// 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
|
-
});
|
|
114
|
+
role: state.status === "approval-requested" ? "status" : undefined, "aria-live": state.status === "approval-requested" ? "polite" : undefined, children: _jsx(StatusDot, { label: STATE_TEXT[state.status], live: state.status === "running", tone: STATE_TONE[state.status] }) }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: displayLabel }), hasDetail ? (_jsx(ChevronRight, { className: cn("size-3.5 shrink-0 transition-transform duration-200 ease-out", open && "rotate-90"), "aria-hidden": true })) : null] }), open && hasDetail ? (_jsxs("div", { 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]", children: [input === undefined ? null : (_jsx(Detail, { title: "Input", children: JSON.stringify(input, null, 2) })), detailText === undefined ? null : (_jsx(Detail, { title: stateDetailTitle(state), children: detailText }))] })) : null] }));
|
|
155
115
|
}
|
|
156
116
|
function Detail({ title, children }) {
|
|
157
|
-
return
|
|
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
|
-
});
|
|
117
|
+
return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("p", { className: "text-[10px] font-semibold tracking-[0.1em] text-muted-foreground uppercase", children: title }), _jsx("pre", { className: "max-h-48 overflow-auto rounded-md bg-muted p-2 font-mono text-[11px] whitespace-pre-wrap", children: children })] }));
|
|
170
118
|
}
|
|
@@ -19,4 +19,4 @@ export type ToolNarrativeProps = {
|
|
|
19
19
|
readonly onOpenChange?: (open: boolean) => void;
|
|
20
20
|
readonly className?: string;
|
|
21
21
|
};
|
|
22
|
-
export declare function ToolNarrative({ part, open: openProp, onOpenChange, className }: ToolNarrativeProps): import("react").JSX.Element;
|
|
22
|
+
export declare function ToolNarrative({ part, open: openProp, onOpenChange, className, }: ToolNarrativeProps): import("react").JSX.Element;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { ChevronRight } from "lucide-react";
|
|
3
4
|
import { useControllableState } from "../hooks/use-controllable-state.js";
|
|
@@ -7,71 +8,23 @@ import { StatusDot } from "./status-dot.js";
|
|
|
7
8
|
const STATE_TEXT = {
|
|
8
9
|
running: "Working",
|
|
9
10
|
done: "Done",
|
|
10
|
-
error: "Failed"
|
|
11
|
+
error: "Failed",
|
|
11
12
|
};
|
|
12
|
-
export function ToolNarrative({ part, open: openProp, onOpenChange, className }) {
|
|
13
|
+
export function ToolNarrative({ part, open: openProp, onOpenChange, className, }) {
|
|
13
14
|
const [open, setOpen] = useControllableState({
|
|
14
15
|
value: openProp,
|
|
15
16
|
defaultValue: part.state === "error",
|
|
16
17
|
onChange: onOpenChange,
|
|
17
|
-
name: "ToolNarrative"
|
|
18
|
+
name: "ToolNarrative",
|
|
18
19
|
});
|
|
19
20
|
const label = toolLabel(part);
|
|
20
21
|
const hasDetail = part.output !== undefined || part.input !== undefined;
|
|
21
|
-
return
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
});
|
|
22
|
+
return (_jsxs("div", { "data-slot": "tool-narrative", className: cn("text-xs", className), children: [_jsxs("button", { type: "button", onClick: () => setOpen((value) => !value), "aria-expanded": open, disabled: !hasDetail, 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", children: [_jsx(StatusDot, { label: STATE_TEXT[part.state], live: part.state === "running", tone: part.state === "error"
|
|
23
|
+
? "danger"
|
|
24
|
+
: part.state === "running"
|
|
25
|
+
? "emphasis"
|
|
26
|
+
: "neutral" }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: label }), hasDetail ? (_jsx(ChevronRight, { className: cn("size-3.5 shrink-0 transition-transform", open && "rotate-90"), "aria-hidden": true })) : null] }), open && hasDetail ? (_jsxs("div", { className: "mt-1 ml-4 flex flex-col gap-2 border-l border-border pl-3", children: [part.input === undefined ? null : (_jsx(Detail, { title: "Input", children: JSON.stringify(part.input, null, 2) })), part.output === undefined ? null : (_jsx(Detail, { title: part.state === "error" ? "Error" : "Output", children: part.output }))] })) : null] }));
|
|
62
27
|
}
|
|
63
28
|
function Detail({ title, children }) {
|
|
64
|
-
return
|
|
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
|
-
});
|
|
29
|
+
return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("p", { className: "text-[10px] font-semibold tracking-[0.1em] text-muted-foreground uppercase", children: title }), _jsx("pre", { className: "max-h-48 overflow-auto rounded-md bg-muted p-2 font-mono text-[11px] whitespace-pre-wrap", children: children })] }));
|
|
77
30
|
}
|
package/dist/ui/tool-picker.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useEffect, useId, useMemo, useRef, useState } from "react";
|
|
3
4
|
import { useCommandPaletteNavigation } from "../hooks/use-command-palette-navigation.js";
|
|
@@ -7,32 +8,37 @@ import { EmptyState } from "./empty-state.js";
|
|
|
7
8
|
import { Input } from "./input.js";
|
|
8
9
|
const STATUS_LABEL = {
|
|
9
10
|
pending: "Pending",
|
|
10
|
-
disabled: "Disabled"
|
|
11
|
+
disabled: "Disabled",
|
|
11
12
|
};
|
|
12
13
|
function matches(item, query) {
|
|
13
|
-
if (query.length === 0)
|
|
14
|
+
if (query.length === 0)
|
|
15
|
+
return true;
|
|
14
16
|
const haystack = `${item.name} ${item.package} ${item.description ?? ""}`.toLowerCase();
|
|
15
17
|
return haystack.includes(query.toLowerCase());
|
|
16
18
|
}
|
|
17
19
|
function groupByPackage(items) {
|
|
18
20
|
const groups = new Map();
|
|
19
|
-
for (const item of items){
|
|
21
|
+
for (const item of items) {
|
|
20
22
|
const group = groups.get(item.package);
|
|
21
|
-
if (group === undefined)
|
|
22
|
-
item
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
if (group === undefined)
|
|
24
|
+
groups.set(item.package, [item]);
|
|
25
|
+
else
|
|
26
|
+
group.push(item);
|
|
25
27
|
}
|
|
26
|
-
return [
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
items: groupItems
|
|
31
|
-
}));
|
|
28
|
+
return [...groups.entries()].map(([id, groupItems]) => ({
|
|
29
|
+
id,
|
|
30
|
+
items: groupItems,
|
|
31
|
+
}));
|
|
32
32
|
}
|
|
33
|
-
/** Visible group title: a display name if the catalog already sent one, else a humanized slug. */
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
/** Visible group title: a display name if the catalog already sent one, else a humanized slug. */
|
|
34
|
+
function groupHeading(id) {
|
|
35
|
+
if (/[A-Z]/.test(id) || /\s/.test(id))
|
|
36
|
+
return id;
|
|
37
|
+
return id
|
|
38
|
+
.split(/[-_./]+/)
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
41
|
+
.join(" ");
|
|
36
42
|
}
|
|
37
43
|
/**
|
|
38
44
|
* Browse, search and select tools from a caller-supplied catalog, grouped by
|
|
@@ -43,52 +49,42 @@ function groupByPackage(items) {
|
|
|
43
49
|
* Disabled items stay in the list rather than disappearing — a tool a
|
|
44
50
|
* caller cannot pick right now is still information ("already attached
|
|
45
51
|
* elsewhere", "needs a credential") that vanishing would hide.
|
|
46
|
-
*/
|
|
52
|
+
*/
|
|
53
|
+
export function ToolPicker({ items, value, onChange, multiple = false, loading = false, empty, searchPlaceholder = "Search tools…", searchLabel = "Search tools", className, }) {
|
|
47
54
|
const baseId = useId();
|
|
48
55
|
const [query, setQuery] = useState("");
|
|
49
56
|
const listRef = useRef(null);
|
|
50
|
-
const filtered = useMemo(()=>items.filter((item)=>matches(item, query)), [
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
]);
|
|
57
|
-
const selectedIds = useMemo(()=>new Set(value), [
|
|
58
|
-
value
|
|
59
|
-
]);
|
|
60
|
-
const toggle = (item)=>{
|
|
61
|
-
if (item.status === "disabled") return;
|
|
57
|
+
const filtered = useMemo(() => items.filter((item) => matches(item, query)), [items, query]);
|
|
58
|
+
const groups = useMemo(() => groupByPackage(filtered), [filtered]);
|
|
59
|
+
const selectedIds = useMemo(() => new Set(value), [value]);
|
|
60
|
+
const toggle = (item) => {
|
|
61
|
+
if (item.status === "disabled")
|
|
62
|
+
return;
|
|
62
63
|
const isSelected = selectedIds.has(item.id);
|
|
63
64
|
if (!multiple) {
|
|
64
|
-
onChange(isSelected ? [] : [
|
|
65
|
-
item.id
|
|
66
|
-
]);
|
|
65
|
+
onChange(isSelected ? [] : [item.id]);
|
|
67
66
|
return;
|
|
68
67
|
}
|
|
69
|
-
onChange(isSelected ? value.filter((id)=>id !== item.id) : [
|
|
70
|
-
...value,
|
|
71
|
-
item.id
|
|
72
|
-
]);
|
|
68
|
+
onChange(isSelected ? value.filter((id) => id !== item.id) : [...value, item.id]);
|
|
73
69
|
};
|
|
74
70
|
const navigation = useCommandPaletteNavigation({
|
|
75
71
|
items: filtered,
|
|
76
|
-
onSelect: (id)=>{
|
|
77
|
-
const item = filtered.find((candidate)=>candidate.id === id);
|
|
78
|
-
if (item !== undefined)
|
|
72
|
+
onSelect: (id) => {
|
|
73
|
+
const item = filtered.find((candidate) => candidate.id === id);
|
|
74
|
+
if (item !== undefined)
|
|
75
|
+
toggle(item);
|
|
76
|
+
},
|
|
77
|
+
onClose: () => {
|
|
78
|
+
/* Escape is handled below so an empty query does not swallow the key. */
|
|
79
79
|
},
|
|
80
|
-
onClose: ()=>{
|
|
81
|
-
/* Escape is handled below so an empty query does not swallow the key. */ }
|
|
82
80
|
});
|
|
83
|
-
useEffect(()=>{
|
|
84
|
-
listRef.current
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}, [
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const optionId = (id)=>`${baseId}-option-${id}`;
|
|
91
|
-
const onKeyDown = (event)=>{
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
listRef.current
|
|
83
|
+
?.querySelector('[data-active="true"]')
|
|
84
|
+
?.scrollIntoView({ block: "nearest" });
|
|
85
|
+
}, [navigation.activeId]);
|
|
86
|
+
const optionId = (id) => `${baseId}-option-${id}`;
|
|
87
|
+
const onKeyDown = (event) => {
|
|
92
88
|
if (event.key === "Escape") {
|
|
93
89
|
if (query.length > 0) {
|
|
94
90
|
event.preventDefault();
|
|
@@ -100,101 +96,17 @@ function groupByPackage(items) {
|
|
|
100
96
|
};
|
|
101
97
|
const emptyCatalog = items.length === 0;
|
|
102
98
|
const noMatches = !emptyCatalog && filtered.length === 0;
|
|
103
|
-
return
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
});
|
|
99
|
+
return (_jsxs("div", { "data-slot": "tool-picker", className: cn("flex min-h-0 flex-col gap-2", className), children: [_jsx(Input, { value: query, onChange: (event) => setQuery(event.target.value), onKeyDown: onKeyDown, placeholder: searchPlaceholder, "aria-label": searchLabel, role: "combobox", "aria-expanded": true, "aria-autocomplete": "list", "aria-controls": `${baseId}-list`, "aria-activedescendant": navigation.activeId === undefined
|
|
100
|
+
? undefined
|
|
101
|
+
: optionId(navigation.activeId), "aria-busy": loading, autoComplete: "off", spellCheck: false }), _jsx("div", { ref: listRef, id: `${baseId}-list`, role: "listbox", "aria-label": "Tools", "aria-multiselectable": multiple, className: "flex min-h-0 flex-1 flex-col gap-3 overflow-auto", children: loading ? (_jsx("p", { role: "presentation", className: "px-2 py-8 text-center text-sm text-muted-foreground", children: "Loading\u2026" })) : emptyCatalog ? (_jsx("div", { role: "presentation", children: empty ?? (_jsx(EmptyState, { title: "No tools", description: "Nothing in the catalog yet." })) })) : noMatches ? (_jsx("p", { role: "presentation", className: "px-2 py-8 text-center text-sm text-muted-foreground", children: "No tools match \u2014 try a different search." })) : (groups.map((group) => (_jsxs("div", { role: "presentation", className: "flex flex-col gap-1", children: [_jsx("p", { className: "px-3.5 py-1.5 text-[11px] font-semibold uppercase tracking-[0.1em] text-muted-foreground", children: groupHeading(group.id) }), _jsx("div", { className: "flex flex-col gap-1", children: group.items.map((item) => (_jsx(ToolPickerOption, { id: optionId(item.id), item: item, selected: selectedIds.has(item.id), active: navigation.activeId === item.id, onSelect: () => toggle(item), onPointerMove: () => navigation.setActiveId(item.id) }, item.id))) })] }, group.id)))) })] }));
|
|
167
102
|
}
|
|
168
|
-
function ToolPickerOption({ id, item, selected, active, onSelect, onPointerMove }) {
|
|
103
|
+
function ToolPickerOption({ id, item, selected, active, onSelect, onPointerMove, }) {
|
|
169
104
|
const disabled = item.status === "disabled";
|
|
170
|
-
return
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
});
|
|
105
|
+
return (_jsxs("div", { id: id, role: "option", "aria-selected": selected, "aria-disabled": disabled, "data-active": active, onPointerMove: disabled ? undefined : onPointerMove, onClick: disabled ? undefined : onSelect, className: cn("flex w-full flex-col gap-1 rounded-lg border px-3.5 py-3 text-left transition-colors ease-out", disabled
|
|
106
|
+
? "cursor-not-allowed opacity-50"
|
|
107
|
+
: "cursor-pointer hover:bg-muted active:brightness-95", selected
|
|
108
|
+
? "border-primary-emphasis bg-primary/10"
|
|
109
|
+
: "border-border bg-card",
|
|
110
|
+
// The keyboard cursor must stay visible on disabled rows — aria-activedescendant can land there.
|
|
111
|
+
active && "bg-muted"), children: [_jsxs("span", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm font-semibold", children: item.name }), item.status === undefined ? null : (_jsx(Badge, { tone: "neutral", children: STATUS_LABEL[item.status] }))] }), item.description === undefined ? null : (_jsx("span", { className: "text-xs leading-snug text-muted-foreground", children: item.description }))] }));
|
|
200
112
|
}
|
package/dist/ui/tooltip.d.ts
CHANGED
|
@@ -18,4 +18,4 @@ export type InfoTooltipProps = {
|
|
|
18
18
|
* it is in the tab order, Radix associates the note by id, and tapping it
|
|
19
19
|
* opens the tooltip on touch devices.
|
|
20
20
|
*/
|
|
21
|
-
export declare function InfoTooltip({ label, triggerLabel, defaultOpen, className }: InfoTooltipProps): import("react").JSX.Element;
|
|
21
|
+
export declare function InfoTooltip({ label, triggerLabel, defaultOpen, className, }: InfoTooltipProps): import("react").JSX.Element;
|
package/dist/ui/tooltip.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
3
4
|
import { InfoIcon } from "lucide-react";
|
|
@@ -12,37 +13,7 @@ import { cn } from "../lib/utils.js";
|
|
|
12
13
|
* reader, and never fires on touch. A `<button>` trigger fixes all three:
|
|
13
14
|
* it is in the tab order, Radix associates the note by id, and tapping it
|
|
14
15
|
* opens the tooltip on touch devices.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
});
|
|
16
|
+
*/
|
|
17
|
+
export function InfoTooltip({ label, triggerLabel, defaultOpen, className, }) {
|
|
18
|
+
return (_jsx(TooltipPrimitive.Provider, { delayDuration: 200, children: _jsxs(TooltipPrimitive.Root, { defaultOpen: defaultOpen ?? false, children: [_jsx(TooltipPrimitive.Trigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": triggerLabel ?? label, 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), children: _jsx(InfoIcon, { className: "size-full" }) }) }), _jsx(TooltipPrimitive.Portal, { children: _jsxs(TooltipPrimitive.Content, { "data-slot": "tooltip-content", sideOffset: 6, 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]"), children: [label, _jsx(TooltipPrimitive.Arrow, { className: "fill-popover" })] }) })] }) }));
|
|
48
19
|
}
|