@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.
Files changed (70) hide show
  1. package/README.md +21 -10
  2. package/components/AgentStatus/AgentStatus.css +82 -22
  3. package/components/AgentStatus/AgentStatus.d.ts +6 -2
  4. package/components/AgentStatus/AgentStatus.js +38 -6
  5. package/components/AiButton/AiButton.css +163 -0
  6. package/components/AiButton/AiButton.d.ts +36 -0
  7. package/components/AiButton/AiButton.js +58 -0
  8. package/components/ChatHeader/ChatHeader.css +35 -0
  9. package/components/ChatHeader/ChatHeader.d.ts +30 -0
  10. package/components/ChatHeader/ChatHeader.js +17 -0
  11. package/components/ChatMarker/ChatMarker.css +52 -0
  12. package/components/ChatMarker/ChatMarker.d.ts +22 -0
  13. package/components/ChatMarker/ChatMarker.js +18 -0
  14. package/components/ChatMessage/ChatMessage.css +310 -0
  15. package/components/ChatMessage/ChatMessage.d.ts +74 -0
  16. package/components/ChatMessage/ChatMessage.js +71 -0
  17. package/components/ChatThread/ChatThread.css +171 -0
  18. package/components/ChatThread/ChatThread.d.ts +41 -0
  19. package/components/ChatThread/ChatThread.js +174 -0
  20. package/components/Chip/Chip.css +27 -0
  21. package/components/Chip/Chip.d.ts +2 -2
  22. package/components/CircularButton/CircularButton.d.ts +23 -8
  23. package/components/CircularButton/CircularButton.js +54 -52
  24. package/components/CodeBlock/CodeBlock.css +2 -2
  25. package/components/Composer/Composer.css +163 -0
  26. package/components/Composer/Composer.d.ts +67 -0
  27. package/components/Composer/Composer.js +120 -0
  28. package/components/DocumentChip/DocumentChip.css +181 -0
  29. package/components/DocumentChip/DocumentChip.d.ts +41 -0
  30. package/components/DocumentChip/DocumentChip.js +78 -0
  31. package/components/InterruptCard/InterruptCard.css +164 -0
  32. package/components/InterruptCard/InterruptCard.d.ts +55 -0
  33. package/components/InterruptCard/InterruptCard.js +57 -0
  34. package/components/MessageActions/MessageActions.css +76 -0
  35. package/components/MessageActions/MessageActions.d.ts +38 -0
  36. package/components/MessageActions/MessageActions.js +33 -0
  37. package/components/MessageCard/MessageCard.css +112 -0
  38. package/components/MessageCard/MessageCard.d.ts +35 -0
  39. package/components/MessageCard/MessageCard.js +28 -0
  40. package/components/NavList/NavList.css +155 -0
  41. package/components/NavList/NavList.d.ts +56 -0
  42. package/components/NavList/NavList.js +99 -0
  43. package/components/PromptSuggestions/PromptSuggestions.css +87 -0
  44. package/components/PromptSuggestions/PromptSuggestions.d.ts +51 -0
  45. package/components/PromptSuggestions/PromptSuggestions.js +34 -0
  46. package/components/Prose/Prose.css +252 -0
  47. package/components/Prose/Prose.d.ts +21 -0
  48. package/components/Prose/Prose.js +14 -0
  49. package/components/Reasoning/Reasoning.css +149 -25
  50. package/components/Reasoning/Reasoning.d.ts +19 -0
  51. package/components/Reasoning/Reasoning.js +32 -3
  52. package/components/SourceChip/SourceChip.css +102 -0
  53. package/components/SourceChip/SourceChip.d.ts +32 -0
  54. package/components/SourceChip/SourceChip.js +31 -0
  55. package/components/Stat/Stat.css +2 -2
  56. package/components/Timeline/Timeline.css +9 -1
  57. package/components/ToolCall/ToolCall.css +3 -3
  58. package/components/registry.json +113 -1
  59. package/components/registry.json.d.ts +113 -1
  60. package/components/registry.json.js +1 -1
  61. package/index.d.ts +14 -0
  62. package/index.js +28 -0
  63. package/package.json +1 -1
  64. package/tokens/registry.json +13 -0
  65. package/tokens/registry.json.d.ts +13 -0
  66. package/tokens/registry.json.js +1 -1
  67. package/tokens/tokens-dark.css +13 -0
  68. package/tokens/tokens-light.css +43 -0
  69. package/tokens/tokens-primitives.css +5 -0
  70. 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
+ };