@robr0/design-system 0.4.0 → 0.5.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/README.md +2 -2
- package/components/AgentStatus/AgentStatus.css +82 -22
- package/components/AgentStatus/AgentStatus.d.ts +6 -2
- package/components/AgentStatus/AgentStatus.js +38 -6
- package/components/AiButton/AiButton.css +163 -0
- package/components/AiButton/AiButton.d.ts +36 -0
- package/components/AiButton/AiButton.js +58 -0
- package/components/ChatHeader/ChatHeader.css +35 -0
- package/components/ChatHeader/ChatHeader.d.ts +30 -0
- package/components/ChatHeader/ChatHeader.js +17 -0
- package/components/ChatMarker/ChatMarker.css +52 -0
- package/components/ChatMarker/ChatMarker.d.ts +22 -0
- package/components/ChatMarker/ChatMarker.js +18 -0
- package/components/ChatMessage/ChatMessage.css +304 -0
- package/components/ChatMessage/ChatMessage.d.ts +68 -0
- package/components/ChatMessage/ChatMessage.js +69 -0
- package/components/ChatThread/ChatThread.css +171 -0
- package/components/ChatThread/ChatThread.d.ts +41 -0
- package/components/ChatThread/ChatThread.js +174 -0
- package/components/Chip/Chip.css +27 -0
- package/components/Chip/Chip.d.ts +2 -2
- package/components/CircularButton/CircularButton.d.ts +23 -8
- package/components/CircularButton/CircularButton.js +54 -52
- package/components/CodeBlock/CodeBlock.css +2 -2
- package/components/Composer/Composer.css +164 -0
- package/components/Composer/Composer.d.ts +67 -0
- package/components/Composer/Composer.js +120 -0
- package/components/DocumentChip/DocumentChip.css +181 -0
- package/components/DocumentChip/DocumentChip.d.ts +41 -0
- package/components/DocumentChip/DocumentChip.js +78 -0
- package/components/InterruptCard/InterruptCard.css +164 -0
- package/components/InterruptCard/InterruptCard.d.ts +55 -0
- package/components/InterruptCard/InterruptCard.js +57 -0
- package/components/MessageActions/MessageActions.css +76 -0
- package/components/MessageActions/MessageActions.d.ts +38 -0
- package/components/MessageActions/MessageActions.js +33 -0
- package/components/MessageCard/MessageCard.css +112 -0
- package/components/MessageCard/MessageCard.d.ts +35 -0
- package/components/MessageCard/MessageCard.js +28 -0
- package/components/NavList/NavList.css +155 -0
- package/components/NavList/NavList.d.ts +56 -0
- package/components/NavList/NavList.js +99 -0
- package/components/PromptSuggestions/PromptSuggestions.css +87 -0
- package/components/PromptSuggestions/PromptSuggestions.d.ts +51 -0
- package/components/PromptSuggestions/PromptSuggestions.js +34 -0
- package/components/Prose/Prose.css +252 -0
- package/components/Prose/Prose.d.ts +21 -0
- package/components/Prose/Prose.js +14 -0
- package/components/Reasoning/Reasoning.css +149 -25
- package/components/Reasoning/Reasoning.d.ts +19 -0
- package/components/Reasoning/Reasoning.js +32 -3
- package/components/SourceChip/SourceChip.css +102 -0
- package/components/SourceChip/SourceChip.d.ts +32 -0
- package/components/SourceChip/SourceChip.js +31 -0
- package/components/Stat/Stat.css +2 -2
- package/components/ToolCall/ToolCall.css +3 -3
- package/components/registry.json +113 -1
- package/components/registry.json.d.ts +113 -1
- package/components/registry.json.js +1 -1
- package/index.d.ts +14 -0
- package/index.js +28 -0
- package/package.json +1 -1
- package/tokens/registry.json +12 -0
- package/tokens/registry.json.d.ts +12 -0
- package/tokens/registry.json.js +1 -1
- package/tokens/tokens-dark.css +13 -0
- package/tokens/tokens-light.css +42 -0
- package/tokens/tokens-typography.css +24 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import React, { useRef, useState, useLayoutEffect } from "react";
|
|
3
|
+
import { CircularButton } from "../CircularButton/CircularButton.js";
|
|
4
|
+
import "./Composer.css";
|
|
5
|
+
const Composer = React.forwardRef(
|
|
6
|
+
({
|
|
7
|
+
value,
|
|
8
|
+
defaultValue,
|
|
9
|
+
onValueChange,
|
|
10
|
+
onSubmit,
|
|
11
|
+
streaming = false,
|
|
12
|
+
onStop,
|
|
13
|
+
maxRows = 8,
|
|
14
|
+
attachments,
|
|
15
|
+
actions,
|
|
16
|
+
trailingActions,
|
|
17
|
+
sendLabel = "Send message",
|
|
18
|
+
stopLabel = "Stop generating",
|
|
19
|
+
className = "",
|
|
20
|
+
onChange,
|
|
21
|
+
onKeyDown,
|
|
22
|
+
...rest
|
|
23
|
+
}, ref) => {
|
|
24
|
+
const baseClass = "ds-composer";
|
|
25
|
+
const textareaRef = useRef(null);
|
|
26
|
+
const isControlled = value !== void 0;
|
|
27
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue ?? "");
|
|
28
|
+
const currentValue = isControlled ? value : uncontrolledValue;
|
|
29
|
+
const disabled = Boolean(rest.disabled);
|
|
30
|
+
const canSend = !disabled && currentValue.trim() !== "";
|
|
31
|
+
const setTextareaRef = (node) => {
|
|
32
|
+
textareaRef.current = node;
|
|
33
|
+
if (typeof ref === "function") ref(node);
|
|
34
|
+
else if (ref) ref.current = node;
|
|
35
|
+
};
|
|
36
|
+
useLayoutEffect(() => {
|
|
37
|
+
const node = textareaRef.current;
|
|
38
|
+
if (!node) return;
|
|
39
|
+
if (typeof CSS !== "undefined" && CSS.supports("field-sizing", "content")) return;
|
|
40
|
+
node.style.height = "auto";
|
|
41
|
+
node.style.height = `${node.scrollHeight}px`;
|
|
42
|
+
}, [currentValue]);
|
|
43
|
+
const submit = () => {
|
|
44
|
+
if (streaming || !canSend) return;
|
|
45
|
+
onSubmit?.(currentValue);
|
|
46
|
+
};
|
|
47
|
+
const handleChange = (e) => {
|
|
48
|
+
if (!isControlled) setUncontrolledValue(e.target.value);
|
|
49
|
+
onChange?.(e);
|
|
50
|
+
onValueChange?.(e.target.value);
|
|
51
|
+
};
|
|
52
|
+
const handleKeyDown = (e) => {
|
|
53
|
+
onKeyDown?.(e);
|
|
54
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
submit();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const handleShellClick = (e) => {
|
|
60
|
+
const target = e.target;
|
|
61
|
+
if (target.closest('button, a, textarea, input, select, [role="button"]')) return;
|
|
62
|
+
textareaRef.current?.focus();
|
|
63
|
+
};
|
|
64
|
+
const classes = [baseClass, disabled ? `${baseClass}--disabled` : "", className].filter(Boolean).join(" ");
|
|
65
|
+
const ariaLabel = rest["aria-label"] ?? (rest["aria-labelledby"] !== void 0 ? void 0 : "Message");
|
|
66
|
+
return /* @__PURE__ */ jsxs(
|
|
67
|
+
"div",
|
|
68
|
+
{
|
|
69
|
+
className: classes,
|
|
70
|
+
style: { "--ds-composer-max-rows": maxRows },
|
|
71
|
+
onClick: handleShellClick,
|
|
72
|
+
children: [
|
|
73
|
+
attachments && /* @__PURE__ */ jsx("div", { className: `${baseClass}__attachments`, children: attachments }),
|
|
74
|
+
/* @__PURE__ */ jsx("div", { className: `${baseClass}__content`, children: /* @__PURE__ */ jsx(
|
|
75
|
+
"textarea",
|
|
76
|
+
{
|
|
77
|
+
...rest,
|
|
78
|
+
ref: setTextareaRef,
|
|
79
|
+
className: `${baseClass}__textarea`,
|
|
80
|
+
rows: 1,
|
|
81
|
+
value: currentValue,
|
|
82
|
+
"aria-label": ariaLabel,
|
|
83
|
+
onChange: handleChange,
|
|
84
|
+
onKeyDown: handleKeyDown
|
|
85
|
+
}
|
|
86
|
+
) }),
|
|
87
|
+
/* @__PURE__ */ jsxs("div", { className: `${baseClass}__footer`, children: [
|
|
88
|
+
actions && /* @__PURE__ */ jsx("div", { className: `${baseClass}__actions`, children: actions }),
|
|
89
|
+
/* @__PURE__ */ jsxs("div", { className: `${baseClass}__trailing`, children: [
|
|
90
|
+
trailingActions,
|
|
91
|
+
streaming ? /* @__PURE__ */ jsx(
|
|
92
|
+
CircularButton,
|
|
93
|
+
{
|
|
94
|
+
icon: "stop",
|
|
95
|
+
variant: "primary",
|
|
96
|
+
ariaLabel: stopLabel,
|
|
97
|
+
disabled,
|
|
98
|
+
onClick: onStop
|
|
99
|
+
}
|
|
100
|
+
) : /* @__PURE__ */ jsx(
|
|
101
|
+
CircularButton,
|
|
102
|
+
{
|
|
103
|
+
icon: "arrow_upward",
|
|
104
|
+
variant: "primary",
|
|
105
|
+
ariaLabel: sendLabel,
|
|
106
|
+
disabled: !canSend,
|
|
107
|
+
onClick: submit
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
] })
|
|
111
|
+
] })
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
Composer.displayName = "Composer";
|
|
118
|
+
export {
|
|
119
|
+
Composer
|
|
120
|
+
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
DOCUMENT CHIP COMPONENT
|
|
3
|
+
Compact file reference tile: type icon, name,
|
|
4
|
+
metadata line, upload progress, optional remove.
|
|
5
|
+
============================================ */
|
|
6
|
+
|
|
7
|
+
.ds-document-chip {
|
|
8
|
+
/* Accent indirection — the error modifier repoints one variable
|
|
9
|
+
instead of restyling each part */
|
|
10
|
+
--ds-document-chip-border: var(--color-bg-container-border);
|
|
11
|
+
|
|
12
|
+
display: inline-flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: var(--gap-sm);
|
|
15
|
+
|
|
16
|
+
padding: var(--padding-xs) var(--padding-sm-md);
|
|
17
|
+
|
|
18
|
+
border: var(--border-xs) solid var(--ds-document-chip-border);
|
|
19
|
+
border-radius: var(--radius-md);
|
|
20
|
+
background-color: var(--color-bg-container-primary);
|
|
21
|
+
|
|
22
|
+
transition: border-color var(--motion-duration-base) var(--motion-ease-standard);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* ============================================
|
|
26
|
+
SIZE — COMPACT (single line for composer rows)
|
|
27
|
+
============================================ */
|
|
28
|
+
|
|
29
|
+
.ds-document-chip--compact {
|
|
30
|
+
gap: var(--gap-xs);
|
|
31
|
+
padding: var(--padding-xxs) var(--padding-sm);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* ============================================
|
|
35
|
+
ERROR — repoint the border accent
|
|
36
|
+
============================================ */
|
|
37
|
+
|
|
38
|
+
.ds-document-chip--error {
|
|
39
|
+
--ds-document-chip-border: var(--color-status-error-border);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* ============================================
|
|
43
|
+
MAIN — body button when the chip is clickable
|
|
44
|
+
============================================ */
|
|
45
|
+
|
|
46
|
+
.ds-document-chip__main {
|
|
47
|
+
display: inline-flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
gap: inherit;
|
|
50
|
+
margin: 0;
|
|
51
|
+
padding: 0;
|
|
52
|
+
border: none;
|
|
53
|
+
background: none;
|
|
54
|
+
border-radius: var(--radius-sm);
|
|
55
|
+
font: inherit;
|
|
56
|
+
letter-spacing: inherit;
|
|
57
|
+
color: inherit;
|
|
58
|
+
text-align: left;
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
transition: background-color var(--motion-duration-base) var(--motion-ease-standard);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.ds-document-chip__main:hover {
|
|
64
|
+
background-color: var(--color-bg-container-secondary);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* ============================================
|
|
68
|
+
TYPE ICON
|
|
69
|
+
============================================ */
|
|
70
|
+
|
|
71
|
+
.ds-document-chip__icon {
|
|
72
|
+
display: inline-flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
flex-shrink: 0;
|
|
75
|
+
color: var(--color-icon-primary);
|
|
76
|
+
transition: color var(--motion-duration-base) var(--motion-ease-standard);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.ds-document-chip__icon .material-symbols-rounded {
|
|
80
|
+
--icon-size: var(--icon-size-md);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.ds-document-chip--compact .ds-document-chip__icon .material-symbols-rounded {
|
|
84
|
+
--icon-size: var(--icon-size-sm);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.ds-document-chip--error .ds-document-chip__icon {
|
|
88
|
+
color: var(--color-status-error-text);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* ============================================
|
|
92
|
+
TEXT BLOCK — name over one detail line
|
|
93
|
+
============================================ */
|
|
94
|
+
|
|
95
|
+
.ds-document-chip__text {
|
|
96
|
+
display: flex;
|
|
97
|
+
flex-direction: column;
|
|
98
|
+
align-items: flex-start;
|
|
99
|
+
gap: var(--gap-xxs);
|
|
100
|
+
min-width: 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.ds-document-chip__name {
|
|
104
|
+
/* Consumer-overridable truncation width */
|
|
105
|
+
max-width: var(--ds-document-chip-max-width, 28ch);
|
|
106
|
+
overflow: hidden;
|
|
107
|
+
text-overflow: ellipsis;
|
|
108
|
+
white-space: nowrap;
|
|
109
|
+
|
|
110
|
+
font-family: var(--font-paragraph-sm-em-family);
|
|
111
|
+
font-size: var(--font-paragraph-sm-em-size);
|
|
112
|
+
font-weight: var(--font-paragraph-sm-em-weight);
|
|
113
|
+
line-height: var(--font-paragraph-sm-em-line-height);
|
|
114
|
+
letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
|
|
115
|
+
color: var(--color-text-primary);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.ds-document-chip__meta,
|
|
119
|
+
.ds-document-chip__error {
|
|
120
|
+
font-family: var(--font-paragraph-sm-family);
|
|
121
|
+
font-size: var(--font-paragraph-sm-size);
|
|
122
|
+
font-weight: var(--font-paragraph-sm-weight);
|
|
123
|
+
line-height: var(--font-paragraph-sm-line-height);
|
|
124
|
+
letter-spacing: var(--font-paragraph-sm-letter-spacing);
|
|
125
|
+
white-space: nowrap;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.ds-document-chip__meta {
|
|
129
|
+
color: var(--color-text-tertiary);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.ds-document-chip__error {
|
|
133
|
+
color: var(--color-status-error-text);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* Composed ProgressBar replacing the metadata line */
|
|
137
|
+
.ds-document-chip__progress {
|
|
138
|
+
align-self: stretch;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* ============================================
|
|
142
|
+
REMOVE BUTTON
|
|
143
|
+
============================================ */
|
|
144
|
+
|
|
145
|
+
.ds-document-chip__remove {
|
|
146
|
+
display: inline-flex;
|
|
147
|
+
align-items: center;
|
|
148
|
+
justify-content: center;
|
|
149
|
+
flex-shrink: 0;
|
|
150
|
+
margin: 0;
|
|
151
|
+
padding: 0;
|
|
152
|
+
border: none;
|
|
153
|
+
background: none;
|
|
154
|
+
border-radius: var(--radius-full);
|
|
155
|
+
color: var(--color-icon-primary);
|
|
156
|
+
cursor: pointer;
|
|
157
|
+
transition: color var(--motion-duration-base) var(--motion-ease-standard);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.ds-document-chip__remove .material-symbols-rounded {
|
|
161
|
+
--icon-size: var(--icon-size-sm);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.ds-document-chip__remove:not(:disabled):hover {
|
|
165
|
+
color: var(--color-text-primary);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.ds-document-chip__remove:disabled {
|
|
169
|
+
opacity: 0.4;
|
|
170
|
+
cursor: not-allowed;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* ============================================
|
|
174
|
+
FOCUS
|
|
175
|
+
============================================ */
|
|
176
|
+
|
|
177
|
+
.ds-document-chip__main:focus-visible,
|
|
178
|
+
.ds-document-chip__remove:focus-visible {
|
|
179
|
+
outline: 2px solid var(--color-action-primary-bg);
|
|
180
|
+
outline-offset: -2px;
|
|
181
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
export type DocumentChipFileType = 'pdf' | 'doc' | 'sheet' | 'slide' | 'image' | 'code' | 'archive' | 'generic';
|
|
3
|
+
/** Props owned by DocumentChip itself — everything else falls through to the root element. */
|
|
4
|
+
type DocumentChipOwnProps = {
|
|
5
|
+
/** File name, truncated with an ellipsis when it outgrows the tile. */
|
|
6
|
+
name: string;
|
|
7
|
+
/** Document type — picks the leading Material Symbol. */
|
|
8
|
+
fileType?: DocumentChipFileType;
|
|
9
|
+
/** Override the type icon — a Material Symbol name, or any custom element. */
|
|
10
|
+
icon?: string | React.ReactNode;
|
|
11
|
+
/** Free-text metadata line, e.g. "1.2 MB" or "12 pages" — callers keep their own formatting. */
|
|
12
|
+
meta?: string;
|
|
13
|
+
/** Upload progress, 0–100. While set, replaces the metadata line with a progress bar. */
|
|
14
|
+
progress?: number;
|
|
15
|
+
/** Error message — replaces the metadata line and colours the tile with the error pair. */
|
|
16
|
+
error?: string;
|
|
17
|
+
/** Tile size. Compact drops the metadata line — name only — for dense composer rows. */
|
|
18
|
+
size?: 'default' | 'compact';
|
|
19
|
+
/** Click handler — presence makes the chip body (icon + text) an interactive button. */
|
|
20
|
+
onClick?: () => void;
|
|
21
|
+
/** Remove handler — renders a trailing close button. */
|
|
22
|
+
onRemove?: () => void;
|
|
23
|
+
/** Accessible label for the remove button. */
|
|
24
|
+
removeLabel?: string;
|
|
25
|
+
/** Additional CSS classes */
|
|
26
|
+
className?: string;
|
|
27
|
+
};
|
|
28
|
+
export interface DocumentChipProps extends DocumentChipOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof DocumentChipOwnProps> {
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* DocumentChip is a compact file reference: a two-line tile with a type
|
|
32
|
+
* icon, name, metadata line, upload progress, and an optional remove
|
|
33
|
+
* button. It references documents attached to chat messages or queued
|
|
34
|
+
* above a composer — Chip stays the one-line pill for attributes, and
|
|
35
|
+
* FileInput stays the form control that owns selection.
|
|
36
|
+
*
|
|
37
|
+
* The root is always a `<div>`; passing `onClick` turns the body into a
|
|
38
|
+
* `<button>` so click and remove coexist without nesting controls.
|
|
39
|
+
*/
|
|
40
|
+
export declare const DocumentChip: React.ForwardRefExoticComponent<DocumentChipProps & React.RefAttributes<HTMLDivElement>>;
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { ProgressBar } from "../ProgressBar/ProgressBar.js";
|
|
4
|
+
import "./DocumentChip.css";
|
|
5
|
+
import "../../fonts/material-symbols.css";
|
|
6
|
+
const FILE_TYPE_ICONS = {
|
|
7
|
+
pdf: "picture_as_pdf",
|
|
8
|
+
doc: "description",
|
|
9
|
+
sheet: "table",
|
|
10
|
+
slide: "co_present",
|
|
11
|
+
image: "image",
|
|
12
|
+
code: "code",
|
|
13
|
+
archive: "folder_zip",
|
|
14
|
+
generic: "draft"
|
|
15
|
+
};
|
|
16
|
+
const DocumentChip = React.forwardRef(
|
|
17
|
+
({
|
|
18
|
+
name,
|
|
19
|
+
fileType = "generic",
|
|
20
|
+
icon,
|
|
21
|
+
meta,
|
|
22
|
+
progress,
|
|
23
|
+
error,
|
|
24
|
+
size = "default",
|
|
25
|
+
onClick,
|
|
26
|
+
onRemove,
|
|
27
|
+
removeLabel = "Remove file",
|
|
28
|
+
className = "",
|
|
29
|
+
...rest
|
|
30
|
+
}, ref) => {
|
|
31
|
+
const baseClass = "ds-document-chip";
|
|
32
|
+
const classes = [
|
|
33
|
+
baseClass,
|
|
34
|
+
`${baseClass}--${size}`,
|
|
35
|
+
error ? `${baseClass}--error` : "",
|
|
36
|
+
onClick ? `${baseClass}--clickable` : "",
|
|
37
|
+
onRemove ? `${baseClass}--removable` : "",
|
|
38
|
+
className
|
|
39
|
+
].filter(Boolean).join(" ");
|
|
40
|
+
const typeIcon = icon ?? FILE_TYPE_ICONS[fileType];
|
|
41
|
+
const showDetail = size !== "compact";
|
|
42
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
43
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__icon`, "aria-hidden": "true", children: typeof typeIcon === "string" ? /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", children: typeIcon }) : typeIcon }),
|
|
44
|
+
/* @__PURE__ */ jsxs("span", { className: `${baseClass}__text`, children: [
|
|
45
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__name`, children: name }),
|
|
46
|
+
showDetail && error && /* @__PURE__ */ jsx("span", { className: `${baseClass}__error`, children: error }),
|
|
47
|
+
showDetail && !error && progress !== void 0 && /* @__PURE__ */ jsx(
|
|
48
|
+
ProgressBar,
|
|
49
|
+
{
|
|
50
|
+
className: `${baseClass}__progress`,
|
|
51
|
+
size: "compact",
|
|
52
|
+
value: progress,
|
|
53
|
+
showLabel: true,
|
|
54
|
+
ariaLabel: `Uploading ${name}`
|
|
55
|
+
}
|
|
56
|
+
),
|
|
57
|
+
showDetail && !error && progress === void 0 && meta && /* @__PURE__ */ jsx("span", { className: `${baseClass}__meta`, children: meta })
|
|
58
|
+
] })
|
|
59
|
+
] });
|
|
60
|
+
return /* @__PURE__ */ jsxs("div", { ...rest, ref, className: classes, children: [
|
|
61
|
+
onClick ? /* @__PURE__ */ jsx("button", { type: "button", className: `${baseClass}__main`, onClick, children: body }) : body,
|
|
62
|
+
onRemove && /* @__PURE__ */ jsx(
|
|
63
|
+
"button",
|
|
64
|
+
{
|
|
65
|
+
type: "button",
|
|
66
|
+
className: `${baseClass}__remove`,
|
|
67
|
+
onClick: onRemove,
|
|
68
|
+
"aria-label": removeLabel,
|
|
69
|
+
children: /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", "aria-hidden": "true", children: "close" })
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
] });
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
DocumentChip.displayName = "DocumentChip";
|
|
76
|
+
export {
|
|
77
|
+
DocumentChip
|
|
78
|
+
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
INTERRUPT CARD COMPONENT
|
|
3
|
+
A human-in-the-loop checkpoint: a question
|
|
4
|
+
from the agent, a detail slot, and option
|
|
5
|
+
buttons in a footer.
|
|
6
|
+
|
|
7
|
+
ToolCall's pending language promoted to a
|
|
8
|
+
card — the warning pair on icon and border
|
|
9
|
+
while a decision is owed, routed through a
|
|
10
|
+
local custom-property pair so the answered
|
|
11
|
+
state repoints two variables and the whole
|
|
12
|
+
card drops to monochrome.
|
|
13
|
+
============================================ */
|
|
14
|
+
|
|
15
|
+
.ds-interrupt-card {
|
|
16
|
+
--ds-interrupt-card-color: var(--color-status-warning-text);
|
|
17
|
+
--ds-interrupt-card-border: var(--color-status-warning-border);
|
|
18
|
+
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
width: 100%;
|
|
22
|
+
background-color: var(--color-bg-container-primary);
|
|
23
|
+
border: var(--border-xs) solid var(--ds-interrupt-card-border);
|
|
24
|
+
border-radius: var(--radius-md);
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Decision made — no signal needed */
|
|
29
|
+
.ds-interrupt-card--answered {
|
|
30
|
+
--ds-interrupt-card-color: var(--color-text-secondary);
|
|
31
|
+
--ds-interrupt-card-border: var(--color-bg-container-border);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* ============================================
|
|
35
|
+
HEADER
|
|
36
|
+
============================================ */
|
|
37
|
+
|
|
38
|
+
.ds-interrupt-card__header {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: flex-start;
|
|
41
|
+
gap: var(--gap-sm);
|
|
42
|
+
padding: var(--padding-sm) var(--padding-sm-md);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.ds-interrupt-card__icon {
|
|
46
|
+
display: inline-flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
flex: none;
|
|
49
|
+
color: var(--ds-interrupt-card-color);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.ds-interrupt-card__icon .material-symbols-rounded {
|
|
53
|
+
--icon-size: var(--icon-size-sm);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.ds-interrupt-card__heading {
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
gap: var(--gap-xxs);
|
|
60
|
+
min-width: 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* The question is the card's one heading, so it takes the title tier
|
|
64
|
+
rather than an emphasised paragraph */
|
|
65
|
+
.ds-interrupt-card__title {
|
|
66
|
+
font-family: var(--font-title-body-family);
|
|
67
|
+
font-size: var(--font-title-body-size);
|
|
68
|
+
font-weight: var(--font-title-body-weight);
|
|
69
|
+
line-height: var(--font-title-body-line-height);
|
|
70
|
+
letter-spacing: var(--font-title-body-letter-spacing);
|
|
71
|
+
color: var(--color-text-primary);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.ds-interrupt-card__description {
|
|
75
|
+
margin: 0;
|
|
76
|
+
font-family: var(--font-paragraph-sm-family);
|
|
77
|
+
font-size: var(--font-paragraph-sm-size);
|
|
78
|
+
font-weight: var(--font-paragraph-sm-weight);
|
|
79
|
+
line-height: var(--font-paragraph-sm-line-height);
|
|
80
|
+
letter-spacing: var(--font-paragraph-sm-letter-spacing);
|
|
81
|
+
color: var(--color-text-secondary);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* ============================================
|
|
85
|
+
DETAIL SLOT
|
|
86
|
+
Typically a pending ToolCall showing what
|
|
87
|
+
wants to run.
|
|
88
|
+
============================================ */
|
|
89
|
+
|
|
90
|
+
.ds-interrupt-card__detail {
|
|
91
|
+
padding: 0 var(--padding-sm-md) var(--padding-sm);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.ds-interrupt-card__detail > :first-child {
|
|
95
|
+
margin-top: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.ds-interrupt-card__detail > :last-child {
|
|
99
|
+
margin-bottom: 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* ============================================
|
|
103
|
+
OPTIONS
|
|
104
|
+
Mirrors ToolCall's actions footer exactly, so
|
|
105
|
+
a decision on a card and an approval on a
|
|
106
|
+
call read as the same row.
|
|
107
|
+
============================================ */
|
|
108
|
+
|
|
109
|
+
.ds-interrupt-card__options {
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
justify-content: flex-end;
|
|
113
|
+
gap: var(--gap-sm);
|
|
114
|
+
padding: var(--padding-sm) var(--padding-sm-md);
|
|
115
|
+
border-top: var(--border-xs) solid var(--color-divider);
|
|
116
|
+
background-color: var(--color-bg-container-secondary);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* ============================================
|
|
120
|
+
ANSWERED ECHO
|
|
121
|
+
Same footer geometry, quiet text where the
|
|
122
|
+
buttons were.
|
|
123
|
+
============================================ */
|
|
124
|
+
|
|
125
|
+
.ds-interrupt-card__answer {
|
|
126
|
+
display: flex;
|
|
127
|
+
align-items: center;
|
|
128
|
+
justify-content: flex-end;
|
|
129
|
+
gap: var(--gap-xs);
|
|
130
|
+
padding: var(--padding-sm) var(--padding-sm-md);
|
|
131
|
+
border-top: var(--border-xs) solid var(--color-divider);
|
|
132
|
+
background-color: var(--color-bg-container-secondary);
|
|
133
|
+
font-family: var(--font-paragraph-sm-family);
|
|
134
|
+
font-size: var(--font-paragraph-sm-size);
|
|
135
|
+
font-weight: var(--font-paragraph-sm-weight);
|
|
136
|
+
line-height: var(--font-paragraph-sm-line-height);
|
|
137
|
+
letter-spacing: var(--font-paragraph-sm-letter-spacing);
|
|
138
|
+
color: var(--color-text-secondary);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.ds-interrupt-card__answer-icon {
|
|
142
|
+
--icon-size: var(--icon-size-sm);
|
|
143
|
+
|
|
144
|
+
flex: none;
|
|
145
|
+
color: var(--ds-interrupt-card-color);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* ============================================
|
|
149
|
+
VISUALLY HIDDEN
|
|
150
|
+
The waiting announcement is for screen
|
|
151
|
+
readers only.
|
|
152
|
+
============================================ */
|
|
153
|
+
|
|
154
|
+
.ds-interrupt-card__sr-only {
|
|
155
|
+
position: absolute;
|
|
156
|
+
width: 1px;
|
|
157
|
+
height: 1px;
|
|
158
|
+
padding: 0;
|
|
159
|
+
margin: -1px;
|
|
160
|
+
overflow: hidden;
|
|
161
|
+
clip: rect(0, 0, 0, 0);
|
|
162
|
+
white-space: nowrap;
|
|
163
|
+
border: 0;
|
|
164
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/** One choice the card offers, rendered as a Button in the options footer. */
|
|
3
|
+
export interface InterruptCardOption {
|
|
4
|
+
/** Value passed to `onValueChange` when this option is chosen. */
|
|
5
|
+
value: string;
|
|
6
|
+
/** Button text, e.g. "Allow once". */
|
|
7
|
+
label: string;
|
|
8
|
+
/** Visual weight. `primary` for the recommended choice, `danger` for destructive approvals. */
|
|
9
|
+
variant?: 'primary' | 'secondary' | 'danger';
|
|
10
|
+
}
|
|
11
|
+
/** Props owned by InterruptCard itself — everything else falls through to the root element. */
|
|
12
|
+
type InterruptCardOwnProps = {
|
|
13
|
+
/** The question, e.g. "Allow file edit?". */
|
|
14
|
+
title: string;
|
|
15
|
+
/** One line of context under the title. */
|
|
16
|
+
description?: string;
|
|
17
|
+
/** Leading Material Symbol name, or any custom element. */
|
|
18
|
+
icon?: string | React.ReactNode;
|
|
19
|
+
/** The choices, rendered as buttons left to right. */
|
|
20
|
+
options?: InterruptCardOption[];
|
|
21
|
+
/** Fires with the chosen option's value. */
|
|
22
|
+
onValueChange?: (value: string) => void;
|
|
23
|
+
/**
|
|
24
|
+
* The already-chosen value. When set, the card renders its answered state:
|
|
25
|
+
* the warning signal drops to monochrome and the options are replaced by an
|
|
26
|
+
* echo of the chosen label.
|
|
27
|
+
*/
|
|
28
|
+
value?: string;
|
|
29
|
+
/** Override the echoed text in the answered state (default: the chosen option's label). */
|
|
30
|
+
answeredLabel?: string;
|
|
31
|
+
/** Additional CSS classes */
|
|
32
|
+
className?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Detail slot between the description and the options — typically a
|
|
35
|
+
* `<ToolCall status="pending">` showing what wants to run.
|
|
36
|
+
*/
|
|
37
|
+
children?: React.ReactNode;
|
|
38
|
+
};
|
|
39
|
+
export interface InterruptCardProps extends InterruptCardOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof InterruptCardOwnProps> {
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* InterruptCard is a human-in-the-loop checkpoint: a question from the agent
|
|
43
|
+
* with option buttons for the person to decide. "Allow this file edit?" —
|
|
44
|
+
* allow once, always allow, deny.
|
|
45
|
+
*
|
|
46
|
+
* Unanswered it wears ToolCall's pending language promoted to a card: the
|
|
47
|
+
* warning pair on icon and border, options in a footer outside the content.
|
|
48
|
+
* Once `value` is set the decision is made, so the signal drops to monochrome
|
|
49
|
+
* and the footer echoes the chosen label.
|
|
50
|
+
*
|
|
51
|
+
* Fully controlled: the card never stores the answer itself — `onValueChange`
|
|
52
|
+
* reports the choice and `value` renders it.
|
|
53
|
+
*/
|
|
54
|
+
export declare const InterruptCard: React.ForwardRefExoticComponent<InterruptCardProps & React.RefAttributes<HTMLDivElement>>;
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Button } from "../Button/Button.js";
|
|
4
|
+
import "./InterruptCard.css";
|
|
5
|
+
import "../../fonts/material-symbols.css";
|
|
6
|
+
const OPTION_VARIANTS = {
|
|
7
|
+
primary: "primary",
|
|
8
|
+
secondary: "secondary",
|
|
9
|
+
danger: "destructive"
|
|
10
|
+
};
|
|
11
|
+
const InterruptCard = React.forwardRef(
|
|
12
|
+
({
|
|
13
|
+
title,
|
|
14
|
+
description,
|
|
15
|
+
icon = "pause_circle",
|
|
16
|
+
options,
|
|
17
|
+
onValueChange,
|
|
18
|
+
value,
|
|
19
|
+
answeredLabel,
|
|
20
|
+
className = "",
|
|
21
|
+
children,
|
|
22
|
+
...rest
|
|
23
|
+
}, ref) => {
|
|
24
|
+
const baseClass = "ds-interrupt-card";
|
|
25
|
+
const answered = value !== void 0;
|
|
26
|
+
const classes = [baseClass, answered ? `${baseClass}--answered` : "", className].filter(Boolean).join(" ");
|
|
27
|
+
const echoedLabel = answeredLabel ?? options?.find((option) => option.value === value)?.label ?? value;
|
|
28
|
+
return /* @__PURE__ */ jsxs("div", { ...rest, ref, role: "group", "aria-label": title, className: classes, children: [
|
|
29
|
+
/* @__PURE__ */ jsxs("div", { className: `${baseClass}__header`, children: [
|
|
30
|
+
icon && /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon`, "aria-hidden": "true", children: typeof icon === "string" ? /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", children: icon }) : icon }),
|
|
31
|
+
/* @__PURE__ */ jsxs("div", { className: `${baseClass}__heading`, children: [
|
|
32
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__title`, children: title }),
|
|
33
|
+
description && /* @__PURE__ */ jsx("p", { className: `${baseClass}__description`, children: description })
|
|
34
|
+
] }),
|
|
35
|
+
!answered && /* @__PURE__ */ jsx("span", { className: `${baseClass}__sr-only`, role: "status", children: "Waiting for your decision" })
|
|
36
|
+
] }),
|
|
37
|
+
children && /* @__PURE__ */ jsx("div", { className: `${baseClass}__detail`, children }),
|
|
38
|
+
answered ? /* @__PURE__ */ jsxs("div", { className: `${baseClass}__answer`, children: [
|
|
39
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__answer-icon material-symbols-rounded`, "aria-hidden": "true", children: "check" }),
|
|
40
|
+
echoedLabel
|
|
41
|
+
] }) : options && options.length > 0 && /* @__PURE__ */ jsx("div", { className: `${baseClass}__options`, children: options.map((option) => /* @__PURE__ */ jsx(
|
|
42
|
+
Button,
|
|
43
|
+
{
|
|
44
|
+
variant: OPTION_VARIANTS[option.variant ?? "secondary"],
|
|
45
|
+
size: "compact",
|
|
46
|
+
label: option.label,
|
|
47
|
+
onClick: () => onValueChange?.(option.value)
|
|
48
|
+
},
|
|
49
|
+
option.value
|
|
50
|
+
)) })
|
|
51
|
+
] });
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
InterruptCard.displayName = "InterruptCard";
|
|
55
|
+
export {
|
|
56
|
+
InterruptCard
|
|
57
|
+
};
|