@robr0/design-system 0.3.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 +30 -27
- package/components/AgentStatus/AgentStatus.css +496 -0
- package/components/AgentStatus/AgentStatus.d.ts +41 -0
- package/components/AgentStatus/AgentStatus.js +85 -0
- package/components/AgentStatus/AgentStatusPatterns.d.ts +17 -0
- package/components/AgentStatus/AgentStatusPatterns.js +20 -0
- package/components/AiButton/AiButton.css +163 -0
- package/components/AiButton/AiButton.d.ts +36 -0
- package/components/AiButton/AiButton.js +58 -0
- package/components/AlertDialog/AlertDialog.css +5 -5
- package/components/Button/Button.d.ts +14 -2
- package/components/ButtonGroup/ButtonGroup.js +2 -9
- 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/Checkbox/Checkbox.d.ts +17 -3
- package/components/Chip/Chip.css +27 -0
- package/components/Chip/Chip.d.ts +2 -2
- package/components/CircularButton/CircularButton.d.ts +31 -10
- package/components/CircularButton/CircularButton.js +54 -50
- package/components/CodeBlock/CodeBlock.css +2 -2
- package/components/Combobox/Combobox.d.ts +10 -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/ContextMenu/ContextMenu.css +0 -4
- package/components/DateInput/DateInput.d.ts +5 -1
- package/components/Dialog/Dialog.css +5 -5
- package/components/DocumentChip/DocumentChip.css +181 -0
- package/components/DocumentChip/DocumentChip.d.ts +41 -0
- package/components/DocumentChip/DocumentChip.js +78 -0
- package/components/Dropdown/Dropdown.css +2 -4
- package/components/Dropdown/Dropdown.d.ts +10 -2
- package/components/DropdownMenu/DropdownMenu.css +2 -4
- package/components/FileInput/FileInput.d.ts +5 -1
- package/components/Input/Input.d.ts +5 -1
- package/components/InterruptCard/InterruptCard.css +164 -0
- package/components/InterruptCard/InterruptCard.d.ts +55 -0
- package/components/InterruptCard/InterruptCard.js +57 -0
- package/components/LinkList/LinkList.d.ts +1 -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/RadioButton/RadioButton.d.ts +17 -3
- package/components/Reasoning/Reasoning.css +276 -0
- package/components/Reasoning/Reasoning.d.ts +55 -0
- package/components/Reasoning/Reasoning.js +98 -0
- package/components/SelectionCard/SelectionCard.d.ts +5 -1
- package/components/Slider/Slider.d.ts +5 -1
- 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/Textarea/Textarea.d.ts +5 -1
- package/components/Timeline/Timeline.d.ts +2 -0
- package/components/ToggleGroup/ToggleGroup.d.ts +10 -2
- package/components/ToggleSwitch/ToggleSwitch.d.ts +10 -2
- package/components/ToolCall/ToolCall.css +213 -0
- package/components/ToolCall/ToolCall.d.ts +43 -0
- package/components/ToolCall/ToolCall.js +89 -0
- package/components/registry.json +145 -8
- package/components/registry.json.d.ts +145 -8
- package/components/registry.json.js +2 -2
- package/index.d.ts +18 -0
- package/index.js +36 -0
- package/package.json +8 -4
- 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 +42 -0
- package/tokens/tokens-motion.css +3 -1
- package/tokens/tokens-typography.css +24 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import React, { useRef, useEffect, useState, useLayoutEffect } from "react";
|
|
3
|
+
import "./ChatThread.css";
|
|
4
|
+
import "../../fonts/material-symbols.css";
|
|
5
|
+
const SCROLLBAR_SETTLE_MS = 600;
|
|
6
|
+
const JUMP_THRESHOLD_PX = 24;
|
|
7
|
+
const ChatThread = React.forwardRef(
|
|
8
|
+
({
|
|
9
|
+
anchor = true,
|
|
10
|
+
ariaLabel = "Conversation",
|
|
11
|
+
jumpLabel = "Scroll to the latest message",
|
|
12
|
+
className = "",
|
|
13
|
+
children,
|
|
14
|
+
onScroll,
|
|
15
|
+
...rest
|
|
16
|
+
}, ref) => {
|
|
17
|
+
const baseClass = "ds-chat-thread";
|
|
18
|
+
const scrollRef = useRef(null);
|
|
19
|
+
const contentRef = useRef(null);
|
|
20
|
+
const spacerRef = useRef(null);
|
|
21
|
+
const prevCount = useRef(0);
|
|
22
|
+
const mounted = useRef(false);
|
|
23
|
+
const anchorTop = useRef(null);
|
|
24
|
+
const sizeSpacer = () => {
|
|
25
|
+
const container = scrollRef.current;
|
|
26
|
+
const content = contentRef.current;
|
|
27
|
+
const spacer = spacerRef.current;
|
|
28
|
+
const top = anchorTop.current;
|
|
29
|
+
if (!container || !content || !spacer || top == null) return;
|
|
30
|
+
const needed = Math.max(0, top + container.clientHeight - content.offsetHeight);
|
|
31
|
+
if (Math.abs(needed - spacer.offsetHeight) > 1) spacer.style.height = `${needed}px`;
|
|
32
|
+
};
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const container = scrollRef.current;
|
|
35
|
+
const content = contentRef.current;
|
|
36
|
+
if (!container || !content) return;
|
|
37
|
+
const syncGutter = () => {
|
|
38
|
+
const gutter = container.offsetWidth - container.clientWidth;
|
|
39
|
+
container.style.setProperty("--ds-chat-thread-gutter", `${gutter}px`);
|
|
40
|
+
};
|
|
41
|
+
const observer = new ResizeObserver(() => {
|
|
42
|
+
syncGutter();
|
|
43
|
+
const top = anchorTop.current;
|
|
44
|
+
const wasClamped = top != null && container.scrollTop < top && container.scrollTop >= container.scrollHeight - container.clientHeight - 1;
|
|
45
|
+
sizeSpacer();
|
|
46
|
+
if (top != null && wasClamped) container.scrollTo({ top, behavior: "instant" });
|
|
47
|
+
updateJump();
|
|
48
|
+
});
|
|
49
|
+
observer.observe(container);
|
|
50
|
+
observer.observe(content);
|
|
51
|
+
return () => observer.disconnect();
|
|
52
|
+
}, []);
|
|
53
|
+
const [scrolling, setScrolling] = useState(false);
|
|
54
|
+
const settleTimer = useRef(void 0);
|
|
55
|
+
const [canScrollDown, setCanScrollDown] = useState(false);
|
|
56
|
+
const updateJump = () => {
|
|
57
|
+
const container = scrollRef.current;
|
|
58
|
+
const content = contentRef.current;
|
|
59
|
+
if (!container || !content) return;
|
|
60
|
+
const remaining = content.offsetHeight - container.scrollTop - container.clientHeight;
|
|
61
|
+
const next = remaining > JUMP_THRESHOLD_PX;
|
|
62
|
+
setCanScrollDown((prev) => prev === next ? prev : next);
|
|
63
|
+
};
|
|
64
|
+
const setRef = (node) => {
|
|
65
|
+
scrollRef.current = node;
|
|
66
|
+
if (typeof ref === "function") ref(node);
|
|
67
|
+
else if (ref) ref.current = node;
|
|
68
|
+
};
|
|
69
|
+
useLayoutEffect(() => {
|
|
70
|
+
const container = scrollRef.current;
|
|
71
|
+
const content = contentRef.current;
|
|
72
|
+
const spacer = spacerRef.current;
|
|
73
|
+
if (!container || !content || !spacer) return;
|
|
74
|
+
const childCount = content.children.length;
|
|
75
|
+
const previous = prevCount.current;
|
|
76
|
+
const isFirstRun = !mounted.current;
|
|
77
|
+
mounted.current = true;
|
|
78
|
+
if (childCount === previous) return;
|
|
79
|
+
prevCount.current = childCount;
|
|
80
|
+
if (childCount < previous) {
|
|
81
|
+
anchorTop.current = null;
|
|
82
|
+
spacer.style.height = "0px";
|
|
83
|
+
container.scrollTo({ top: 0 });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (isFirstRun) {
|
|
87
|
+
if (childCount > 0 && anchor) {
|
|
88
|
+
const target2 = Math.max(0, content.offsetHeight - container.clientHeight);
|
|
89
|
+
anchorTop.current = target2;
|
|
90
|
+
sizeSpacer();
|
|
91
|
+
container.scrollTo({ top: target2, behavior: "instant" });
|
|
92
|
+
updateJump();
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (!anchor) return;
|
|
97
|
+
const contentStyle = getComputedStyle(content);
|
|
98
|
+
const paddingTop = parseFloat(contentStyle.paddingTop) || 0;
|
|
99
|
+
if (previous === 0) {
|
|
100
|
+
const target2 = content.children[0];
|
|
101
|
+
if (!target2) return;
|
|
102
|
+
anchorTop.current = 0;
|
|
103
|
+
sizeSpacer();
|
|
104
|
+
const paddingBottom = parseFloat(contentStyle.paddingBottom) || 0;
|
|
105
|
+
const rise = Math.max(
|
|
106
|
+
0,
|
|
107
|
+
container.clientHeight - target2.offsetHeight - paddingTop - paddingBottom
|
|
108
|
+
);
|
|
109
|
+
content.style.transition = "none";
|
|
110
|
+
content.style.transform = `translateY(${rise}px)`;
|
|
111
|
+
void content.offsetHeight;
|
|
112
|
+
content.style.transition = "";
|
|
113
|
+
content.style.transform = "";
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const target = content.children[previous];
|
|
117
|
+
if (!target) return;
|
|
118
|
+
const targetTop = target.offsetTop - paddingTop;
|
|
119
|
+
anchorTop.current = targetTop;
|
|
120
|
+
sizeSpacer();
|
|
121
|
+
container.scrollTo({ top: targetTop });
|
|
122
|
+
});
|
|
123
|
+
useEffect(updateJump);
|
|
124
|
+
const handleScroll = (e) => {
|
|
125
|
+
onScroll?.(e);
|
|
126
|
+
updateJump();
|
|
127
|
+
setScrolling(true);
|
|
128
|
+
window.clearTimeout(settleTimer.current);
|
|
129
|
+
settleTimer.current = window.setTimeout(
|
|
130
|
+
() => setScrolling(false),
|
|
131
|
+
SCROLLBAR_SETTLE_MS
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
const jumpToBottom = () => {
|
|
135
|
+
const container = scrollRef.current;
|
|
136
|
+
const content = contentRef.current;
|
|
137
|
+
if (!container || !content) return;
|
|
138
|
+
container.scrollTo({ top: content.offsetHeight - container.clientHeight });
|
|
139
|
+
};
|
|
140
|
+
useEffect(() => () => window.clearTimeout(settleTimer.current), []);
|
|
141
|
+
const classes = [baseClass, scrolling ? `${baseClass}--scrolling` : "", className].filter(Boolean).join(" ");
|
|
142
|
+
return /* @__PURE__ */ jsxs(
|
|
143
|
+
"div",
|
|
144
|
+
{
|
|
145
|
+
...rest,
|
|
146
|
+
ref: setRef,
|
|
147
|
+
className: classes,
|
|
148
|
+
role: "region",
|
|
149
|
+
"aria-label": ariaLabel,
|
|
150
|
+
tabIndex: 0,
|
|
151
|
+
onScroll: handleScroll,
|
|
152
|
+
children: [
|
|
153
|
+
/* @__PURE__ */ jsx("div", { ref: contentRef, className: `${baseClass}__content`, children }),
|
|
154
|
+
/* @__PURE__ */ jsx("div", { ref: spacerRef, className: `${baseClass}__spacer`, "aria-hidden": "true" }),
|
|
155
|
+
/* @__PURE__ */ jsx("div", { className: `${baseClass}__jump-slot`, "aria-hidden": !canScrollDown, children: /* @__PURE__ */ jsx(
|
|
156
|
+
"button",
|
|
157
|
+
{
|
|
158
|
+
type: "button",
|
|
159
|
+
className: `${baseClass}__jump${canScrollDown ? ` ${baseClass}__jump--visible` : ""}`,
|
|
160
|
+
tabIndex: canScrollDown ? 0 : -1,
|
|
161
|
+
"aria-label": jumpLabel,
|
|
162
|
+
onClick: jumpToBottom,
|
|
163
|
+
children: /* @__PURE__ */ jsx("span", { className: `${baseClass}__jump-icon material-symbols-rounded`, "aria-hidden": "true", children: "arrow_downward" })
|
|
164
|
+
}
|
|
165
|
+
) })
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
ChatThread.displayName = "ChatThread";
|
|
172
|
+
export {
|
|
173
|
+
ChatThread
|
|
174
|
+
};
|
|
@@ -15,11 +15,21 @@ type CheckboxOwnProps = {
|
|
|
15
15
|
onCheckedChange?: (checked: boolean) => void;
|
|
16
16
|
/** Additional CSS classes */
|
|
17
17
|
className?: string;
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Legacy change handler, kept for backwards compatibility.
|
|
20
|
+
*
|
|
21
|
+
* @deprecated Use `onCheckedChange` instead.
|
|
22
|
+
*/
|
|
19
23
|
onChange?: (checked: boolean) => void;
|
|
20
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Legacy accessible-name prop.
|
|
26
|
+
*
|
|
27
|
+
* @deprecated Pass the native `aria-label` attribute instead.
|
|
28
|
+
*/
|
|
21
29
|
ariaLabel?: string;
|
|
22
30
|
/**
|
|
31
|
+
* Legacy form-field name.
|
|
32
|
+
*
|
|
23
33
|
* @deprecated No-op. This component renders a `<div role="checkbox">`, not a
|
|
24
34
|
* native `<input>`, so it cannot participate in native form submission.
|
|
25
35
|
* Declared only so the attribute is not forwarded to an element that rejects it.
|
|
@@ -48,7 +58,11 @@ type CheckboxGroupOwnProps = {
|
|
|
48
58
|
onValuesChange?: (values: string[]) => void;
|
|
49
59
|
/** Additional CSS classes */
|
|
50
60
|
className?: string;
|
|
51
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* Legacy change handler, kept for backwards compatibility.
|
|
63
|
+
*
|
|
64
|
+
* @deprecated Use `onValuesChange` instead.
|
|
65
|
+
*/
|
|
52
66
|
onChange?: (values: string[]) => void;
|
|
53
67
|
};
|
|
54
68
|
export interface CheckboxGroupProps extends CheckboxGroupOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof CheckboxGroupOwnProps> {
|
package/components/Chip/Chip.css
CHANGED
|
@@ -32,6 +32,33 @@
|
|
|
32
32
|
color var(--motion-duration-base) var(--motion-ease-standard), opacity var(--motion-duration-base) var(--motion-ease-standard);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/* ============================================
|
|
36
|
+
SIZE — LARGE (40px tall)
|
|
37
|
+
The default Button's footprint and the body
|
|
38
|
+
paragraph scale, so a chip used as a primary
|
|
39
|
+
tap target reads at the same weight as the
|
|
40
|
+
text around it.
|
|
41
|
+
============================================ */
|
|
42
|
+
|
|
43
|
+
.ds-chip--large {
|
|
44
|
+
padding: var(--padding-sm) var(--padding-lg); /* 8px 20px */
|
|
45
|
+
font-family: var(--font-paragraph-em-family);
|
|
46
|
+
font-size: var(--font-paragraph-em-size);
|
|
47
|
+
font-weight: var(--font-paragraph-em-weight);
|
|
48
|
+
line-height: var(--font-paragraph-em-line-height);
|
|
49
|
+
letter-spacing: var(--font-paragraph-em-letter-spacing);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.ds-chip--large .ds-chip__icon,
|
|
53
|
+
.ds-chip--large .ds-chip__remove .material-symbols-rounded {
|
|
54
|
+
--icon-size: var(--icon-size-md);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.ds-chip--large .ds-chip__icon svg {
|
|
58
|
+
width: 24px;
|
|
59
|
+
height: 24px;
|
|
60
|
+
}
|
|
61
|
+
|
|
35
62
|
/* ============================================
|
|
36
63
|
SIZE — COMPACT (24px tall)
|
|
37
64
|
============================================ */
|
|
@@ -8,8 +8,8 @@ export interface ChipProps {
|
|
|
8
8
|
selected?: boolean;
|
|
9
9
|
/** Disabled state */
|
|
10
10
|
disabled?: boolean;
|
|
11
|
-
/** Chip size */
|
|
12
|
-
size?: 'default' | 'compact';
|
|
11
|
+
/** Chip size — `large` matches the default Button and paragraph body scale, for pills that are a primary tap target rather than metadata */
|
|
12
|
+
size?: 'large' | 'default' | 'compact';
|
|
13
13
|
/** Click handler — presence makes the chip an interactive <button> */
|
|
14
14
|
onClick?: () => void;
|
|
15
15
|
/** Remove handler — renders a trailing close button (input-style chips) */
|
|
@@ -1,9 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/** Props owned by CircularButton itself — everything else falls through to the DOM node. */
|
|
3
|
+
type CircularButtonOwnProps = {
|
|
2
4
|
/** Material Symbol icon name */
|
|
3
5
|
icon: string;
|
|
4
|
-
/**
|
|
6
|
+
/** Visual treatment */
|
|
7
|
+
variant?: 'primary' | 'secondary' | 'tertiary';
|
|
8
|
+
/**
|
|
9
|
+
* Legacy alias for `variant`.
|
|
10
|
+
*
|
|
11
|
+
* @deprecated Use `variant` instead.
|
|
12
|
+
*/
|
|
5
13
|
priority?: 'primary' | 'secondary' | 'tertiary';
|
|
6
|
-
/**
|
|
14
|
+
/** Whether the button is disabled */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Documentation-only interaction state.
|
|
18
|
+
*
|
|
19
|
+
* @deprecated Use `disabled` for the disabled state.
|
|
20
|
+
*
|
|
21
|
+
* Documentation-only affordance for rendering a *static* interaction state in
|
|
22
|
+
* Storybook and the showcase site. Real hover/active styling comes from CSS
|
|
23
|
+
* pseudo-classes and needs no prop — for docs, prefer
|
|
24
|
+
* `className="ds-circular-button--hover"`.
|
|
25
|
+
*/
|
|
7
26
|
state?: 'default' | 'hover' | 'active' | 'disabled';
|
|
8
27
|
/** Button size */
|
|
9
28
|
size?: 'default' | 'compact';
|
|
@@ -13,24 +32,26 @@ export interface CircularButtonProps {
|
|
|
13
32
|
* the disabled state) and sets `aria-busy` on the rendered element.
|
|
14
33
|
*/
|
|
15
34
|
loading?: boolean;
|
|
16
|
-
/** Accessible label */
|
|
35
|
+
/** Accessible label — required, because the button has no visible text */
|
|
17
36
|
ariaLabel: string;
|
|
18
|
-
/** Optional click handler */
|
|
19
|
-
onClick?: () => void;
|
|
20
37
|
/** Optional href — renders as <a> instead of <button> */
|
|
21
38
|
href?: string;
|
|
22
39
|
/** Optional target attribute for links */
|
|
23
40
|
target?: string;
|
|
24
41
|
/** Optional rel attribute for links */
|
|
25
42
|
rel?: string;
|
|
26
|
-
/** Id of an element describing this button — set automatically by Tooltip */
|
|
27
|
-
'aria-describedby'?: string;
|
|
28
43
|
/** Additional CSS classes */
|
|
29
44
|
className?: string;
|
|
45
|
+
};
|
|
46
|
+
export interface CircularButtonProps extends CircularButtonOwnProps, Omit<React.ComponentPropsWithoutRef<'button'>, keyof CircularButtonOwnProps | 'type'> {
|
|
30
47
|
}
|
|
31
48
|
/**
|
|
32
49
|
* Circular icon button component.
|
|
33
50
|
* A round button containing a single icon, available in
|
|
34
|
-
* primary and
|
|
51
|
+
* primary, secondary and tertiary variants with default and compact sizes.
|
|
52
|
+
*
|
|
53
|
+
* Renders a `<button>`, or an `<a>` when `href` is supplied. Forwards a ref to
|
|
54
|
+
* whichever element it renders, and spreads unrecognised props onto it.
|
|
35
55
|
*/
|
|
36
|
-
export declare const CircularButton:
|
|
56
|
+
export declare const CircularButton: React.ForwardRefExoticComponent<CircularButtonProps & React.RefAttributes<HTMLButtonElement | HTMLAnchorElement>>;
|
|
57
|
+
export {};
|
|
@@ -1,65 +1,69 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
2
3
|
import { Spinner } from "../Spinner/Spinner.js";
|
|
3
4
|
import "./CircularButton.css";
|
|
4
5
|
import "../../fonts/material-symbols.css";
|
|
5
|
-
const CircularButton = (
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
6
|
+
const CircularButton = React.forwardRef(
|
|
7
|
+
({
|
|
8
|
+
icon,
|
|
9
|
+
variant,
|
|
10
|
+
priority,
|
|
11
|
+
disabled,
|
|
12
|
+
state,
|
|
13
|
+
size = "default",
|
|
14
|
+
loading,
|
|
15
|
+
ariaLabel,
|
|
16
|
+
href,
|
|
17
|
+
target,
|
|
18
|
+
rel,
|
|
19
|
+
className = "",
|
|
20
|
+
...rest
|
|
21
|
+
}, ref) => {
|
|
22
|
+
const baseClass = "ds-circular-button";
|
|
23
|
+
const resolvedVariant = variant ?? priority ?? "primary";
|
|
24
|
+
const isDisabled = disabled ?? state === "disabled";
|
|
25
|
+
const resolvedState = state ?? (isDisabled ? "disabled" : "default");
|
|
26
|
+
const isLoading = Boolean(loading);
|
|
27
|
+
const classes = [
|
|
28
|
+
baseClass,
|
|
29
|
+
`${baseClass}--${resolvedVariant}`,
|
|
30
|
+
`${baseClass}--${resolvedState}`,
|
|
31
|
+
`${baseClass}--${size}`,
|
|
32
|
+
isLoading && `${baseClass}--loading`,
|
|
33
|
+
className
|
|
34
|
+
].filter(Boolean).join(" ");
|
|
35
|
+
const children = isLoading ? /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon`, "aria-hidden": "true", children: /* @__PURE__ */ jsx(Spinner, { size: size === "compact" ? "sm" : "md", variant: "inherit" }) }) : /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon material-symbols-rounded`, "aria-hidden": "true", children: icon });
|
|
36
|
+
if (href && !isDisabled && !isLoading) {
|
|
37
|
+
return /* @__PURE__ */ jsx(
|
|
38
|
+
"a",
|
|
39
|
+
{
|
|
40
|
+
...rest,
|
|
41
|
+
ref,
|
|
42
|
+
className: classes,
|
|
43
|
+
href,
|
|
44
|
+
target,
|
|
45
|
+
rel,
|
|
46
|
+
"aria-label": ariaLabel,
|
|
47
|
+
children
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
}
|
|
35
51
|
return /* @__PURE__ */ jsx(
|
|
36
|
-
"
|
|
52
|
+
"button",
|
|
37
53
|
{
|
|
54
|
+
...rest,
|
|
55
|
+
ref,
|
|
56
|
+
type: "button",
|
|
38
57
|
className: classes,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
rel,
|
|
42
|
-
onClick,
|
|
58
|
+
disabled: isDisabled || isLoading,
|
|
59
|
+
"aria-busy": isLoading || void 0,
|
|
43
60
|
"aria-label": ariaLabel,
|
|
44
|
-
"aria-describedby": ariaDescribedby,
|
|
45
61
|
children
|
|
46
62
|
}
|
|
47
63
|
);
|
|
48
64
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
{
|
|
52
|
-
type: "button",
|
|
53
|
-
className: classes,
|
|
54
|
-
onClick,
|
|
55
|
-
disabled: isDisabled || isLoading,
|
|
56
|
-
"aria-busy": isLoading || void 0,
|
|
57
|
-
"aria-label": ariaLabel,
|
|
58
|
-
"aria-describedby": ariaDescribedby,
|
|
59
|
-
children
|
|
60
|
-
}
|
|
61
|
-
);
|
|
62
|
-
};
|
|
65
|
+
);
|
|
66
|
+
CircularButton.displayName = "CircularButton";
|
|
63
67
|
export {
|
|
64
68
|
CircularButton
|
|
65
69
|
};
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
.ds-code-block__filename {
|
|
83
|
-
font-family:
|
|
83
|
+
font-family: var(--font-family-code);
|
|
84
84
|
font-size: var(--font-paragraph-sm-size);
|
|
85
85
|
line-height: var(--font-paragraph-sm-line-height);
|
|
86
86
|
color: var(--color-text-secondary);
|
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
.ds-code-block__code {
|
|
175
|
-
font-family:
|
|
175
|
+
font-family: var(--font-family-code);
|
|
176
176
|
font-size: var(--font-paragraph-sm-size);
|
|
177
177
|
line-height: var(--font-paragraph-sm-line-height);
|
|
178
178
|
color: var(--color-text-primary);
|
|
@@ -59,9 +59,17 @@ type ComboboxOwnProps = {
|
|
|
59
59
|
manualFiltering?: boolean;
|
|
60
60
|
/** Additional CSS classes */
|
|
61
61
|
className?: string;
|
|
62
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* Legacy change handler, kept for backwards compatibility.
|
|
64
|
+
*
|
|
65
|
+
* @deprecated Use `onValueChange` instead.
|
|
66
|
+
*/
|
|
63
67
|
onChange?: (value: string | string[]) => void;
|
|
64
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* Legacy accessible-name prop.
|
|
70
|
+
*
|
|
71
|
+
* @deprecated Pass the native `aria-label` attribute instead.
|
|
72
|
+
*/
|
|
65
73
|
ariaLabel?: string;
|
|
66
74
|
/**
|
|
67
75
|
* Used only as a fallback for deriving the element id (`id || name || generated`).
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
COMPOSER COMPONENT
|
|
3
|
+
The chat input shell: attachments, auto-growing textarea, send/stop
|
|
4
|
+
============================================ */
|
|
5
|
+
|
|
6
|
+
.ds-composer {
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
width: 100%;
|
|
10
|
+
/* The send button's diameter — CircularButton's default size, which is
|
|
11
|
+
a raw value in CircularButton.css too (icon-button sizes are not on
|
|
12
|
+
a token scale). Everything concentric derives from it. */
|
|
13
|
+
--ds-composer-send-size: 40px;
|
|
14
|
+
background-color: var(--color-input-bg-primary);
|
|
15
|
+
border: var(--border-xs) solid var(--color-input-border-primary);
|
|
16
|
+
/* Corner concentric with the send button it wraps: half the button,
|
|
17
|
+
plus the action bar's padding-sm ring, plus the border width
|
|
18
|
+
(border-radius applies to the border's outer edge). */
|
|
19
|
+
border-radius: calc(
|
|
20
|
+
var(--ds-composer-send-size) / 2 + var(--padding-sm) + var(--border-xs)
|
|
21
|
+
);
|
|
22
|
+
box-shadow: var(--shadow-floating);
|
|
23
|
+
transition: border-color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
24
|
+
/* The whole shell is the input affordance — clicking anywhere that is
|
|
25
|
+
not a control focuses the textarea, and the cursor says so. The
|
|
26
|
+
action bar below opts back out. */
|
|
27
|
+
cursor: text;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* The shell carries the input focus/hover treatment — the textarea inside
|
|
31
|
+
is borderless, so the whole surface reads as one control */
|
|
32
|
+
.ds-composer:hover:not(:focus-within) {
|
|
33
|
+
border-color: var(--color-input-border-hover);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.ds-composer:focus-within {
|
|
37
|
+
border-color: var(--color-input-border-selected);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* ============================================
|
|
41
|
+
ATTACHMENTS ROW
|
|
42
|
+
============================================ */
|
|
43
|
+
|
|
44
|
+
.ds-composer__attachments {
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-wrap: wrap;
|
|
47
|
+
align-items: flex-end;
|
|
48
|
+
gap: var(--gap-xs);
|
|
49
|
+
/* Top ring only — the text zone below keeps its own full padding-md,
|
|
50
|
+
so chips sit a clear padding-md above the first text line */
|
|
51
|
+
padding: var(--padding-md) var(--padding-lg) 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* ============================================
|
|
55
|
+
CONTENT ZONE
|
|
56
|
+
The text zone breathes more than the action bar: generous top and
|
|
57
|
+
inline padding, tighter toward the bar below it.
|
|
58
|
+
============================================ */
|
|
59
|
+
|
|
60
|
+
.ds-composer__content {
|
|
61
|
+
display: flex;
|
|
62
|
+
width: 100%;
|
|
63
|
+
padding: var(--padding-md) var(--padding-lg) var(--padding-sm);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.ds-composer__textarea {
|
|
67
|
+
width: 100%;
|
|
68
|
+
padding: 0;
|
|
69
|
+
border: none;
|
|
70
|
+
outline: none;
|
|
71
|
+
background-color: transparent;
|
|
72
|
+
resize: none;
|
|
73
|
+
font-family: var(--font-paragraph-family);
|
|
74
|
+
font-size: var(--font-paragraph-size);
|
|
75
|
+
font-weight: var(--font-paragraph-weight);
|
|
76
|
+
line-height: var(--font-paragraph-line-height);
|
|
77
|
+
letter-spacing: var(--font-paragraph-letter-spacing);
|
|
78
|
+
color: var(--color-input-text-primary);
|
|
79
|
+
/* Growth cap: the shell owns the block padding, so the cap is rows × line-height.
|
|
80
|
+
--ds-composer-max-rows is set inline from the maxRows prop. */
|
|
81
|
+
max-height: calc(var(--ds-composer-max-rows, 8) * var(--font-paragraph-line-height));
|
|
82
|
+
overflow-y: auto;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.ds-composer__textarea::placeholder {
|
|
86
|
+
color: var(--color-input-text-placeholder);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Overflow scrollbar — thin, trackless, and quiet instead of the bulky
|
|
90
|
+
platform default. scrollbar-* covers Firefox and modern Chromium;
|
|
91
|
+
the -webkit rules cover Safari. */
|
|
92
|
+
.ds-composer__textarea {
|
|
93
|
+
scrollbar-width: thin;
|
|
94
|
+
scrollbar-color: var(--color-bg-container-secondary) transparent;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.ds-composer__textarea::-webkit-scrollbar {
|
|
98
|
+
width: 6px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.ds-composer__textarea::-webkit-scrollbar-track {
|
|
102
|
+
background: transparent;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.ds-composer__textarea::-webkit-scrollbar-thumb {
|
|
106
|
+
background-color: var(--color-bg-container-secondary);
|
|
107
|
+
border-radius: var(--radius-full);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Native auto-grow where supported; the JS measurement path stands down
|
|
111
|
+
(it checks the same capability) */
|
|
112
|
+
@supports (field-sizing: content) {
|
|
113
|
+
.ds-composer__textarea {
|
|
114
|
+
field-sizing: content;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* ============================================
|
|
119
|
+
FOOTER ROW
|
|
120
|
+
============================================ */
|
|
121
|
+
|
|
122
|
+
.ds-composer__footer {
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
gap: var(--gap-xs);
|
|
126
|
+
/* The tight uniform ring the concentric corner is computed from */
|
|
127
|
+
padding: var(--padding-sm);
|
|
128
|
+
/* Empty bar space still focuses the textarea on click, but the bar is
|
|
129
|
+
control territory, so it keeps the default cursor. */
|
|
130
|
+
cursor: default;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.ds-composer__actions {
|
|
134
|
+
display: flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
gap: var(--gap-xs);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.ds-composer__trailing {
|
|
140
|
+
display: flex;
|
|
141
|
+
align-items: center;
|
|
142
|
+
gap: var(--gap-xs);
|
|
143
|
+
margin-left: auto;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* ============================================
|
|
147
|
+
DISABLED
|
|
148
|
+
============================================ */
|
|
149
|
+
|
|
150
|
+
.ds-composer--disabled {
|
|
151
|
+
opacity: 0.4;
|
|
152
|
+
cursor: not-allowed;
|
|
153
|
+
background-color: var(--color-input-bg-disabled);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.ds-composer--disabled:hover:not(:focus-within),
|
|
157
|
+
.ds-composer--disabled:focus-within {
|
|
158
|
+
border-color: var(--color-input-border-disabled);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.ds-composer--disabled .ds-composer__textarea {
|
|
162
|
+
cursor: not-allowed;
|
|
163
|
+
color: var(--color-input-text-disabled);
|
|
164
|
+
}
|