@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.
Files changed (94) hide show
  1. package/README.md +30 -27
  2. package/components/AgentStatus/AgentStatus.css +496 -0
  3. package/components/AgentStatus/AgentStatus.d.ts +41 -0
  4. package/components/AgentStatus/AgentStatus.js +85 -0
  5. package/components/AgentStatus/AgentStatusPatterns.d.ts +17 -0
  6. package/components/AgentStatus/AgentStatusPatterns.js +20 -0
  7. package/components/AiButton/AiButton.css +163 -0
  8. package/components/AiButton/AiButton.d.ts +36 -0
  9. package/components/AiButton/AiButton.js +58 -0
  10. package/components/AlertDialog/AlertDialog.css +5 -5
  11. package/components/Button/Button.d.ts +14 -2
  12. package/components/ButtonGroup/ButtonGroup.js +2 -9
  13. package/components/ChatHeader/ChatHeader.css +35 -0
  14. package/components/ChatHeader/ChatHeader.d.ts +30 -0
  15. package/components/ChatHeader/ChatHeader.js +17 -0
  16. package/components/ChatMarker/ChatMarker.css +52 -0
  17. package/components/ChatMarker/ChatMarker.d.ts +22 -0
  18. package/components/ChatMarker/ChatMarker.js +18 -0
  19. package/components/ChatMessage/ChatMessage.css +304 -0
  20. package/components/ChatMessage/ChatMessage.d.ts +68 -0
  21. package/components/ChatMessage/ChatMessage.js +69 -0
  22. package/components/ChatThread/ChatThread.css +171 -0
  23. package/components/ChatThread/ChatThread.d.ts +41 -0
  24. package/components/ChatThread/ChatThread.js +174 -0
  25. package/components/Checkbox/Checkbox.d.ts +17 -3
  26. package/components/Chip/Chip.css +27 -0
  27. package/components/Chip/Chip.d.ts +2 -2
  28. package/components/CircularButton/CircularButton.d.ts +31 -10
  29. package/components/CircularButton/CircularButton.js +54 -50
  30. package/components/CodeBlock/CodeBlock.css +2 -2
  31. package/components/Combobox/Combobox.d.ts +10 -2
  32. package/components/Composer/Composer.css +164 -0
  33. package/components/Composer/Composer.d.ts +67 -0
  34. package/components/Composer/Composer.js +120 -0
  35. package/components/ContextMenu/ContextMenu.css +0 -4
  36. package/components/DateInput/DateInput.d.ts +5 -1
  37. package/components/Dialog/Dialog.css +5 -5
  38. package/components/DocumentChip/DocumentChip.css +181 -0
  39. package/components/DocumentChip/DocumentChip.d.ts +41 -0
  40. package/components/DocumentChip/DocumentChip.js +78 -0
  41. package/components/Dropdown/Dropdown.css +2 -4
  42. package/components/Dropdown/Dropdown.d.ts +10 -2
  43. package/components/DropdownMenu/DropdownMenu.css +2 -4
  44. package/components/FileInput/FileInput.d.ts +5 -1
  45. package/components/Input/Input.d.ts +5 -1
  46. package/components/InterruptCard/InterruptCard.css +164 -0
  47. package/components/InterruptCard/InterruptCard.d.ts +55 -0
  48. package/components/InterruptCard/InterruptCard.js +57 -0
  49. package/components/LinkList/LinkList.d.ts +1 -0
  50. package/components/MessageActions/MessageActions.css +76 -0
  51. package/components/MessageActions/MessageActions.d.ts +38 -0
  52. package/components/MessageActions/MessageActions.js +33 -0
  53. package/components/MessageCard/MessageCard.css +112 -0
  54. package/components/MessageCard/MessageCard.d.ts +35 -0
  55. package/components/MessageCard/MessageCard.js +28 -0
  56. package/components/NavList/NavList.css +155 -0
  57. package/components/NavList/NavList.d.ts +56 -0
  58. package/components/NavList/NavList.js +99 -0
  59. package/components/PromptSuggestions/PromptSuggestions.css +87 -0
  60. package/components/PromptSuggestions/PromptSuggestions.d.ts +51 -0
  61. package/components/PromptSuggestions/PromptSuggestions.js +34 -0
  62. package/components/Prose/Prose.css +252 -0
  63. package/components/Prose/Prose.d.ts +21 -0
  64. package/components/Prose/Prose.js +14 -0
  65. package/components/RadioButton/RadioButton.d.ts +17 -3
  66. package/components/Reasoning/Reasoning.css +276 -0
  67. package/components/Reasoning/Reasoning.d.ts +55 -0
  68. package/components/Reasoning/Reasoning.js +98 -0
  69. package/components/SelectionCard/SelectionCard.d.ts +5 -1
  70. package/components/Slider/Slider.d.ts +5 -1
  71. package/components/SourceChip/SourceChip.css +102 -0
  72. package/components/SourceChip/SourceChip.d.ts +32 -0
  73. package/components/SourceChip/SourceChip.js +31 -0
  74. package/components/Stat/Stat.css +2 -2
  75. package/components/Textarea/Textarea.d.ts +5 -1
  76. package/components/Timeline/Timeline.d.ts +2 -0
  77. package/components/ToggleGroup/ToggleGroup.d.ts +10 -2
  78. package/components/ToggleSwitch/ToggleSwitch.d.ts +10 -2
  79. package/components/ToolCall/ToolCall.css +213 -0
  80. package/components/ToolCall/ToolCall.d.ts +43 -0
  81. package/components/ToolCall/ToolCall.js +89 -0
  82. package/components/registry.json +145 -8
  83. package/components/registry.json.d.ts +145 -8
  84. package/components/registry.json.js +2 -2
  85. package/index.d.ts +18 -0
  86. package/index.js +36 -0
  87. package/package.json +8 -4
  88. package/tokens/registry.json +13 -0
  89. package/tokens/registry.json.d.ts +13 -0
  90. package/tokens/registry.json.js +1 -1
  91. package/tokens/tokens-dark.css +13 -0
  92. package/tokens/tokens-light.css +42 -0
  93. package/tokens/tokens-motion.css +3 -1
  94. 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
- /** @deprecated Use `onCheckedChange` instead. */
18
+ /**
19
+ * Legacy change handler, kept for backwards compatibility.
20
+ *
21
+ * @deprecated Use `onCheckedChange` instead.
22
+ */
19
23
  onChange?: (checked: boolean) => void;
20
- /** @deprecated Pass the native `aria-label` attribute instead. */
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
- /** @deprecated Use `onValuesChange` instead. */
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> {
@@ -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
- export interface CircularButtonProps {
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
- /** Button priority/variant */
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
- /** Button state */
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 secondary variants with default and compact sizes.
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: ({ icon, priority, state, size, loading, ariaLabel, onClick, href, target, rel, "aria-describedby": ariaDescribedby, className, }: CircularButtonProps) => import("react").JSX.Element;
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
- icon,
7
- priority = "primary",
8
- state = "default",
9
- size = "default",
10
- loading,
11
- ariaLabel,
12
- onClick,
13
- href,
14
- target,
15
- rel,
16
- "aria-describedby": ariaDescribedby,
17
- className = ""
18
- }) => {
19
- const baseClass = "ds-circular-button";
20
- const variantClass = `${baseClass}--${priority}`;
21
- const stateClass = `${baseClass}--${state}`;
22
- const sizeClass = `${baseClass}--${size}`;
23
- const isLoading = Boolean(loading);
24
- const classes = [
25
- baseClass,
26
- variantClass,
27
- stateClass,
28
- sizeClass,
29
- isLoading && `${baseClass}--loading`,
30
- className
31
- ].filter(Boolean).join(" ");
32
- const isDisabled = state === "disabled";
33
- 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 });
34
- if (href && !isDisabled && !isLoading) {
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
- "a",
52
+ "button",
37
53
  {
54
+ ...rest,
55
+ ref,
56
+ type: "button",
38
57
  className: classes,
39
- href,
40
- target,
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
- return /* @__PURE__ */ jsx(
50
- "button",
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: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
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: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
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
- /** @deprecated Use `onValueChange` instead. */
62
+ /**
63
+ * Legacy change handler, kept for backwards compatibility.
64
+ *
65
+ * @deprecated Use `onValueChange` instead.
66
+ */
63
67
  onChange?: (value: string | string[]) => void;
64
- /** @deprecated Pass the native `aria-label` attribute instead. */
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
+ }