@robr0/design-system 0.4.0 → 0.6.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 +21 -10
- 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 +310 -0
- package/components/ChatMessage/ChatMessage.d.ts +74 -0
- package/components/ChatMessage/ChatMessage.js +71 -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 +163 -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/Timeline/Timeline.css +9 -1
- 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 +13 -0
- package/tokens/registry.json.d.ts +13 -0
- package/tokens/registry.json.js +1 -1
- package/tokens/tokens-dark.css +13 -0
- package/tokens/tokens-light.css +43 -0
- package/tokens/tokens-primitives.css +5 -0
- package/tokens/tokens-typography.css +24 -0
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
MESSAGE ACTIONS COMPONENT
|
|
3
|
+
The icon-button row under a chat message:
|
|
4
|
+
copy, retry, feedback. Ghost buttons — no
|
|
5
|
+
border, no resting background — so the row
|
|
6
|
+
stays quiet until pointed at.
|
|
7
|
+
|
|
8
|
+
Stateless beyond hover: copy feedback is the
|
|
9
|
+
consumer swapping an item's icon, never a
|
|
10
|
+
timer inside the row.
|
|
11
|
+
============================================ */
|
|
12
|
+
|
|
13
|
+
.ds-message-actions {
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
gap: var(--gap-xxs);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* ============================================
|
|
20
|
+
BUTTON
|
|
21
|
+
Ghost: transparent at rest, the passive
|
|
22
|
+
action ramp on hover and for the active item.
|
|
23
|
+
============================================ */
|
|
24
|
+
|
|
25
|
+
.ds-message-actions__button {
|
|
26
|
+
display: inline-flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
justify-content: center;
|
|
29
|
+
padding: var(--padding-xxs);
|
|
30
|
+
border: none;
|
|
31
|
+
border-radius: var(--radius-sm);
|
|
32
|
+
background-color: transparent;
|
|
33
|
+
color: var(--color-icon-primary);
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
transition:
|
|
36
|
+
background-color var(--motion-duration-fast) var(--motion-ease-standard),
|
|
37
|
+
color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.ds-message-actions__button .material-symbols-rounded {
|
|
41
|
+
--icon-size: var(--icon-size-sm);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.ds-message-actions__button:hover:not(:disabled) {
|
|
45
|
+
background-color: var(--color-action-passive-bg-hover);
|
|
46
|
+
color: var(--color-text-primary);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* ============================================
|
|
50
|
+
ACTIVE
|
|
51
|
+
The selected item, e.g. the chosen feedback
|
|
52
|
+
thumb — held on the pressed step of the ramp.
|
|
53
|
+
============================================ */
|
|
54
|
+
|
|
55
|
+
.ds-message-actions__button--active {
|
|
56
|
+
background-color: var(--color-action-passive-bg-active);
|
|
57
|
+
color: var(--color-text-primary);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* ============================================
|
|
61
|
+
FOCUS
|
|
62
|
+
============================================ */
|
|
63
|
+
|
|
64
|
+
.ds-message-actions__button:focus-visible {
|
|
65
|
+
outline: 2px solid var(--color-action-primary-bg);
|
|
66
|
+
outline-offset: -2px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* ============================================
|
|
70
|
+
DISABLED
|
|
71
|
+
============================================ */
|
|
72
|
+
|
|
73
|
+
.ds-message-actions__button:disabled {
|
|
74
|
+
opacity: 0.4;
|
|
75
|
+
cursor: not-allowed;
|
|
76
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/** One action in the row: a stable id, a glyph, and an accessible name. */
|
|
3
|
+
export interface MessageAction {
|
|
4
|
+
/** Stable identifier passed to `onActionClick`. */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Material Symbol name for the button glyph. */
|
|
7
|
+
icon: string;
|
|
8
|
+
/** Accessible name, also shown as the tooltip. */
|
|
9
|
+
label: string;
|
|
10
|
+
/** Marks the action as selected, e.g. the chosen feedback thumb. */
|
|
11
|
+
active?: boolean;
|
|
12
|
+
/** Disables just this action. */
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** Props owned by MessageActions itself — everything else falls through to the root element. */
|
|
16
|
+
type MessageActionsOwnProps = {
|
|
17
|
+
/** The actions, rendered left to right. */
|
|
18
|
+
items: MessageAction[];
|
|
19
|
+
/** Fires with the pressed action's id. */
|
|
20
|
+
onActionClick?: (id: string) => void;
|
|
21
|
+
/** Wrap each button in a Tooltip showing its label. The label stays as `aria-label` either way. */
|
|
22
|
+
showTooltips?: boolean;
|
|
23
|
+
/** Additional CSS classes */
|
|
24
|
+
className?: string;
|
|
25
|
+
};
|
|
26
|
+
export interface MessageActionsProps extends MessageActionsOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof MessageActionsOwnProps> {
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* MessageActions is the icon-button row for message-level actions: copy,
|
|
30
|
+
* retry, feedback. It slots into ChatMessage's `actions` prop, which reveals
|
|
31
|
+
* it on hover and keyboard focus.
|
|
32
|
+
*
|
|
33
|
+
* The row is stateless beyond hover — copy feedback is the consumer swapping
|
|
34
|
+
* an item's `icon` (content_copy → check) and flipping it back, so the row
|
|
35
|
+
* never owns a timer or a clipboard call.
|
|
36
|
+
*/
|
|
37
|
+
export declare const MessageActions: React.ForwardRefExoticComponent<MessageActionsProps & React.RefAttributes<HTMLDivElement>>;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Tooltip } from "../Tooltip/Tooltip.js";
|
|
4
|
+
import "./MessageActions.css";
|
|
5
|
+
import "../../fonts/material-symbols.css";
|
|
6
|
+
const MessageActions = React.forwardRef(
|
|
7
|
+
({ items, onActionClick, showTooltips = true, className = "", ...rest }, ref) => {
|
|
8
|
+
const baseClass = "ds-message-actions";
|
|
9
|
+
const classes = [baseClass, className].filter(Boolean).join(" ");
|
|
10
|
+
return /* @__PURE__ */ jsx("div", { ...rest, ref, className: classes, children: items.map((item) => {
|
|
11
|
+
const button = /* @__PURE__ */ jsx(
|
|
12
|
+
"button",
|
|
13
|
+
{
|
|
14
|
+
type: "button",
|
|
15
|
+
className: [
|
|
16
|
+
`${baseClass}__button`,
|
|
17
|
+
item.active ? `${baseClass}__button--active` : ""
|
|
18
|
+
].filter(Boolean).join(" "),
|
|
19
|
+
"aria-label": item.label,
|
|
20
|
+
"aria-pressed": item.active !== void 0 ? item.active : void 0,
|
|
21
|
+
disabled: item.disabled,
|
|
22
|
+
onClick: () => onActionClick?.(item.id),
|
|
23
|
+
children: /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", "aria-hidden": "true", children: item.icon })
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
return showTooltips ? /* @__PURE__ */ jsx(Tooltip, { content: item.label, children: button }, item.id) : /* @__PURE__ */ jsx(React.Fragment, { children: button }, item.id);
|
|
27
|
+
}) });
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
MessageActions.displayName = "MessageActions";
|
|
31
|
+
export {
|
|
32
|
+
MessageActions
|
|
33
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
MESSAGE CARD COMPONENT
|
|
3
|
+
Structured rich content inside a chat turn:
|
|
4
|
+
media on top, a title/meta/description body,
|
|
5
|
+
and an actions footer.
|
|
6
|
+
|
|
7
|
+
Not the navigation Card — this is content
|
|
8
|
+
furniture inside a conversation, --radius-md
|
|
9
|
+
like the other chat primitives, sized by the
|
|
10
|
+
bubble or turn that holds it.
|
|
11
|
+
============================================ */
|
|
12
|
+
|
|
13
|
+
.ds-message-card {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
width: 100%;
|
|
17
|
+
max-width: var(--ds-message-card-max-width, 100%);
|
|
18
|
+
background-color: var(--color-bg-container-primary);
|
|
19
|
+
border: var(--border-xs) solid var(--color-bg-container-border);
|
|
20
|
+
border-radius: var(--radius-md);
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* ============================================
|
|
25
|
+
MEDIA
|
|
26
|
+
Full bleed — the card's overflow clip rounds
|
|
27
|
+
the corners, so the slot carries no padding.
|
|
28
|
+
============================================ */
|
|
29
|
+
|
|
30
|
+
.ds-message-card__media {
|
|
31
|
+
display: block;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.ds-message-card__media img {
|
|
35
|
+
display: block;
|
|
36
|
+
width: 100%;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* ============================================
|
|
40
|
+
BODY
|
|
41
|
+
============================================ */
|
|
42
|
+
|
|
43
|
+
.ds-message-card__body {
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
gap: var(--gap-xs);
|
|
47
|
+
padding: var(--padding-sm-md) var(--padding-md);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.ds-message-card__title-row {
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
gap: var(--gap-xs);
|
|
54
|
+
min-width: 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.ds-message-card__icon {
|
|
58
|
+
display: inline-flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
flex: none;
|
|
61
|
+
color: var(--color-icon-primary);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.ds-message-card__icon .material-symbols-rounded {
|
|
65
|
+
--icon-size: var(--icon-size-sm);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.ds-message-card__title {
|
|
69
|
+
min-width: 0;
|
|
70
|
+
font-family: var(--font-title-body-family);
|
|
71
|
+
font-size: var(--font-title-body-size);
|
|
72
|
+
font-weight: var(--font-title-body-weight);
|
|
73
|
+
line-height: var(--font-title-body-line-height);
|
|
74
|
+
letter-spacing: var(--font-title-body-letter-spacing);
|
|
75
|
+
color: var(--color-text-primary);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.ds-message-card__meta {
|
|
79
|
+
font-family: var(--font-paragraph-sm-family);
|
|
80
|
+
font-size: var(--font-paragraph-sm-size);
|
|
81
|
+
font-weight: var(--font-paragraph-sm-weight);
|
|
82
|
+
line-height: var(--font-paragraph-sm-line-height);
|
|
83
|
+
letter-spacing: var(--font-paragraph-sm-letter-spacing);
|
|
84
|
+
color: var(--color-text-tertiary);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.ds-message-card__description {
|
|
88
|
+
margin: 0;
|
|
89
|
+
font-family: var(--font-paragraph-sm-family);
|
|
90
|
+
font-size: var(--font-paragraph-sm-size);
|
|
91
|
+
font-weight: var(--font-paragraph-sm-weight);
|
|
92
|
+
line-height: var(--font-paragraph-sm-line-height);
|
|
93
|
+
letter-spacing: var(--font-paragraph-sm-letter-spacing);
|
|
94
|
+
color: var(--color-text-secondary);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* ============================================
|
|
98
|
+
ACTIONS
|
|
99
|
+
Mirrors ToolCall's actions footer exactly, so
|
|
100
|
+
approvals on a tool call and link actions on
|
|
101
|
+
a card read as the same row.
|
|
102
|
+
============================================ */
|
|
103
|
+
|
|
104
|
+
.ds-message-card__actions {
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
justify-content: flex-end;
|
|
108
|
+
gap: var(--gap-sm);
|
|
109
|
+
padding: var(--padding-sm) var(--padding-sm-md);
|
|
110
|
+
border-top: var(--border-xs) solid var(--color-divider);
|
|
111
|
+
background-color: var(--color-bg-container-secondary);
|
|
112
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/** Props owned by MessageCard itself — everything else falls through to the root element. */
|
|
3
|
+
type MessageCardOwnProps = {
|
|
4
|
+
/** Card heading. */
|
|
5
|
+
title?: string;
|
|
6
|
+
/** One or two lines under the title. */
|
|
7
|
+
description?: string;
|
|
8
|
+
/** Top media slot — an image or chart, rendered flush to the card edges (the card clips it). */
|
|
9
|
+
media?: React.ReactNode;
|
|
10
|
+
/** Small leading icon beside the title — a Material Symbol name, or any custom element. */
|
|
11
|
+
icon?: string | React.ReactNode;
|
|
12
|
+
/** Free-text metadata line, e.g. a domain or date, so callers keep their own formatting. */
|
|
13
|
+
meta?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Action row rendered in a footer outside the body — small secondary or
|
|
16
|
+
* tertiary Buttons, mirroring ToolCall's actions footer.
|
|
17
|
+
*/
|
|
18
|
+
actions?: React.ReactNode;
|
|
19
|
+
/** Additional CSS classes */
|
|
20
|
+
className?: string;
|
|
21
|
+
/** Body content between the description and the footer — rich content, a Prose block, a DocumentChip row. */
|
|
22
|
+
children?: React.ReactNode;
|
|
23
|
+
};
|
|
24
|
+
export interface MessageCardProps extends MessageCardOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof MessageCardOwnProps> {
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* MessageCard is a structured rich-content card embedded in a chat message:
|
|
28
|
+
* media, title, body, and an actions row, for link previews, search results,
|
|
29
|
+
* and booking-style rich responses inside an assistant turn or bubble.
|
|
30
|
+
*
|
|
31
|
+
* It is not the navigation Card — this is content furniture inside a
|
|
32
|
+
* conversation, sized by the bubble or turn that holds it.
|
|
33
|
+
*/
|
|
34
|
+
export declare const MessageCard: React.ForwardRefExoticComponent<MessageCardProps & React.RefAttributes<HTMLDivElement>>;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import "./MessageCard.css";
|
|
4
|
+
import "../../fonts/material-symbols.css";
|
|
5
|
+
const MessageCard = React.forwardRef(
|
|
6
|
+
({ title, description, media, icon, meta, actions, className = "", children, ...rest }, ref) => {
|
|
7
|
+
const baseClass = "ds-message-card";
|
|
8
|
+
const classes = [baseClass, className].filter(Boolean).join(" ");
|
|
9
|
+
const hasBody = Boolean(title || icon || meta || description || children);
|
|
10
|
+
return /* @__PURE__ */ jsxs("div", { ...rest, ref, className: classes, children: [
|
|
11
|
+
media && /* @__PURE__ */ jsx("div", { className: `${baseClass}__media`, children: media }),
|
|
12
|
+
hasBody && /* @__PURE__ */ jsxs("div", { className: `${baseClass}__body`, children: [
|
|
13
|
+
(title || icon) && /* @__PURE__ */ jsxs("div", { className: `${baseClass}__title-row`, children: [
|
|
14
|
+
icon && /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon`, "aria-hidden": "true", children: typeof icon === "string" ? /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", children: icon }) : icon }),
|
|
15
|
+
title && /* @__PURE__ */ jsx("span", { className: `${baseClass}__title`, children: title })
|
|
16
|
+
] }),
|
|
17
|
+
meta && /* @__PURE__ */ jsx("span", { className: `${baseClass}__meta`, children: meta }),
|
|
18
|
+
description && /* @__PURE__ */ jsx("p", { className: `${baseClass}__description`, children: description }),
|
|
19
|
+
children
|
|
20
|
+
] }),
|
|
21
|
+
actions && /* @__PURE__ */ jsx("div", { className: `${baseClass}__actions`, children: actions })
|
|
22
|
+
] });
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
MessageCard.displayName = "MessageCard";
|
|
26
|
+
export {
|
|
27
|
+
MessageCard
|
|
28
|
+
};
|