@robr0/design-system 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +2 -2
  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 +304 -0
  15. package/components/ChatMessage/ChatMessage.d.ts +68 -0
  16. package/components/ChatMessage/ChatMessage.js +69 -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 +164 -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/ToolCall/ToolCall.css +3 -3
  57. package/components/registry.json +113 -1
  58. package/components/registry.json.d.ts +113 -1
  59. package/components/registry.json.js +1 -1
  60. package/index.d.ts +14 -0
  61. package/index.js +28 -0
  62. package/package.json +1 -1
  63. package/tokens/registry.json +12 -0
  64. package/tokens/registry.json.d.ts +12 -0
  65. package/tokens/registry.json.js +1 -1
  66. package/tokens/tokens-dark.css +13 -0
  67. package/tokens/tokens-light.css +42 -0
  68. package/tokens/tokens-typography.css +24 -0
package/README.md CHANGED
@@ -18,7 +18,7 @@ The **live site** is the portfolio built on the design system. **Storybook** is
18
18
  | Part | Description |
19
19
  |---|---|
20
20
  | **Portfolio website** (`/website`) | Next.js app with case studies, work history, writing, and about pages, all built exclusively with the design system components below. **[Live site →](https://robertritacca.com/)** |
21
- | **Design system** (`/src`) | <!-- component-count -->64<!-- /component-count --> React components, a three-tier token architecture, dark mode, and a full documentation site. Built to production standards. **[Storybook →](https://design-system-iota-one.vercel.app/?path=/docs/robr0-ds--docs)** |
21
+ | **Design system** (`/src`) | <!-- component-count -->78<!-- /component-count --> React components, a three-tier token architecture, dark mode, and a full documentation site. Built to production standards. **[Storybook →](https://design-system-iota-one.vercel.app/?path=/docs/robr0-ds--docs)** |
22
22
 
23
23
  ---
24
24
 
@@ -27,7 +27,7 @@ The **live site** is the portfolio built on the design system. **Storybook** is
27
27
  ### Components
28
28
 
29
29
  <!-- component-list:start -->
30
- Accordion · Agent status · Alert · Alert dialog · App layout · App sidebar · Avatar · Badge · Breadcrumb · Button · Button group · Card · Carousel · Chart · Checkbox · Chip · Circular button · Code block · Colour picker · Combobox · Command palette · Contact card · Context menu · Contribution graph · Date input · Date picker · Dialog · Divider · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Field · Figure · File input · Input · Instructions · Kbd · Link list · Navigation · Pagination · Popover · Progress bar · Quote · Radio button · Reasoning · Section title · Segmented control · Selection card · Skeleton · Slider · Spinner · Stat · Swatch · Table · Tabs · Textarea · Timeline · Toast · Toggle group · Toggle switch · Tool call · Tooltip
30
+ Accordion · Agent status · AI button · Alert · Alert dialog · App layout · App sidebar · Avatar · Badge · Breadcrumb · Button · Button group · Card · Carousel · Chart · Chat header · Chat marker · Chat message · Chat thread · Checkbox · Chip · Circular button · Code block · Colour picker · Combobox · Command palette · Composer · Contact card · Context menu · Contribution graph · Date input · Date picker · Dialog · Divider · Document chip · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Field · Figure · File input · Input · Instructions · Interrupt card · Kbd · Link list · Message actions · Message card · Navigation · Nav list · Pagination · Popover · Progress bar · Prompt suggestions · Prose · Quote · Radio button · Reasoning · Section title · Segmented control · Selection card · Skeleton · Slider · Source chip · Spinner · Stat · Swatch · Table · Tabs · Textarea · Timeline · Toast · Toggle group · Toggle switch · Tool call · Tooltip
31
31
  <!-- component-list:end -->
32
32
 
33
33
  ### Using the package
@@ -22,20 +22,22 @@
22
22
  .ds-agent-status {
23
23
  --ds-agent-color: var(--color-text-secondary);
24
24
  --ds-agent-label-color: var(--color-text-secondary);
25
+ --ds-agent-shimmer-base: var(--color-text-primary);
26
+ --ds-agent-shimmer-sweep: var(--color-text-tertiary);
25
27
  --ds-agent-dot-dim: 0.2;
26
28
  --ds-agent-dot-rest: 0.82;
27
29
  --ds-agent-cycle: var(--motion-duration-loop-matrix);
28
30
  --ds-agent-slot: calc(var(--ds-agent-cycle) / 12);
29
- --ds-agent-matrix-size: var(--icon-size-sm);
31
+ --ds-agent-matrix-size: var(--icon-size-md);
30
32
 
31
33
  display: inline-flex;
32
34
  align-items: center;
33
- gap: var(--gap-sm);
34
- font-family: var(--font-paragraph-sm-family);
35
- font-size: var(--font-paragraph-sm-size);
36
- font-weight: var(--font-paragraph-sm-weight);
37
- line-height: var(--font-paragraph-sm-line-height);
38
- letter-spacing: var(--font-paragraph-sm-letter-spacing);
35
+ gap: var(--gap-sm-md);
36
+ font-family: var(--font-paragraph-family);
37
+ font-size: var(--font-paragraph-size);
38
+ font-weight: var(--font-paragraph-weight);
39
+ line-height: var(--font-paragraph-line-height);
40
+ letter-spacing: var(--font-paragraph-letter-spacing);
39
41
  }
40
42
 
41
43
  /* ============================================
@@ -63,20 +65,60 @@
63
65
  ============================================ */
64
66
 
65
67
  .ds-agent-status__label {
68
+ position: relative;
66
69
  color: var(--ds-agent-label-color);
67
70
  min-width: 0;
68
71
  }
69
72
 
70
- /* A gradient between the secondary text colour the label already sits at and
71
- the primary text colour, sweeping left to right: with an oversized
72
- background image, background-position 100% shows its right edge, 0% its
73
- left, so counting down walks the bright band across the text. */
73
+ /* Label crossfade: when the label string changes, the outgoing copy sits
74
+ absolutely over the incoming one and fades up and out while the new one
75
+ fades up and in one continuous upward motion. */
76
+ .ds-agent-status__label-text {
77
+ display: inline-block;
78
+ }
79
+
80
+ /* The animations live only on the modifier classes, and the modifiers come
81
+ off once the swap settles: a filled animation can pin the span to its
82
+ own compositor layer, which would escape the shimmer's text clip and
83
+ leave the label invisible for good. */
84
+ .ds-agent-status__label-text--in {
85
+ animation: ds-agent-label-in var(--motion-duration-slow) var(--motion-ease-entrance) both;
86
+ animation-delay: calc(var(--motion-duration-slow) / 2);
87
+ }
88
+
89
+ .ds-agent-status__label-text--out {
90
+ position: absolute;
91
+ left: 0;
92
+ top: 0;
93
+ white-space: nowrap;
94
+ animation: ds-agent-label-out var(--motion-duration-slow) var(--motion-ease-standard) both;
95
+ animation-delay: 0s;
96
+ }
97
+
98
+ @keyframes ds-agent-label-in {
99
+ from {
100
+ opacity: 0;
101
+ transform: translateY(8px);
102
+ }
103
+ }
104
+
105
+ @keyframes ds-agent-label-out {
106
+ to {
107
+ opacity: 0;
108
+ transform: translateY(-8px);
109
+ }
110
+ }
111
+
112
+ /* A gradient between the shimmer base and sweep colours, moving left to
113
+ right: with an oversized background image, background-position 100% shows
114
+ its right edge, 0% its left, so counting down walks the sweep band across
115
+ the text. */
74
116
  .ds-agent-status__label--shimmer {
75
117
  background-image: linear-gradient(
76
118
  100deg,
77
- var(--ds-agent-label-color) 35%,
78
- var(--color-text-primary) 50%,
79
- var(--ds-agent-label-color) 65%
119
+ var(--ds-agent-shimmer-base) 35%,
120
+ var(--ds-agent-shimmer-sweep) 50%,
121
+ var(--ds-agent-shimmer-base) 65%
80
122
  );
81
123
  background-size: 250% 100%;
82
124
  background-clip: text;
@@ -86,6 +128,16 @@
86
128
  var(--motion-ease-linear) infinite;
87
129
  }
88
130
 
131
+ /* While a label crossfade runs, the shimmer's text clip stands down: an
132
+ animated descendant composites on its own layer, which escapes
133
+ background-clip: text and renders the text invisible. The swap motion
134
+ covers the pause; the sweep resumes on the settled text. */
135
+ .ds-agent-status__label--shimmer.ds-agent-status__label--swapping {
136
+ background-image: none;
137
+ animation: none;
138
+ color: var(--ds-agent-label-color);
139
+ }
140
+
89
141
  /* ============================================
90
142
  STATES
91
143
  The three working states are deliberately
@@ -102,6 +154,11 @@
102
154
  .ds-agent-status--waiting {
103
155
  --ds-agent-color: var(--color-status-warning-text);
104
156
  --ds-agent-label-color: var(--color-status-warning-text);
157
+
158
+ /* Waiting keeps its warning colour through the shimmer — colour is the
159
+ signal, so the sweep only brightens it rather than going monochrome. */
160
+ --ds-agent-shimmer-base: var(--color-status-warning-text);
161
+ --ds-agent-shimmer-sweep: var(--color-text-primary);
105
162
  }
106
163
 
107
164
  .ds-agent-status--done {
@@ -123,17 +180,20 @@
123
180
 
124
181
  /* ============================================
125
182
  SIZES
183
+ The scale pairs with ChatMessage: default
184
+ matches default message text, compact
185
+ matches compact message text.
126
186
  ============================================ */
127
187
 
128
- .ds-agent-status--md {
129
- --ds-agent-matrix-size: var(--icon-size-md);
188
+ .ds-agent-status--compact {
189
+ --ds-agent-matrix-size: var(--icon-size-sm);
130
190
 
131
- gap: var(--gap-sm-md);
132
- font-family: var(--font-paragraph-family);
133
- font-size: var(--font-paragraph-size);
134
- font-weight: var(--font-paragraph-weight);
135
- line-height: var(--font-paragraph-line-height);
136
- letter-spacing: var(--font-paragraph-letter-spacing);
191
+ gap: var(--gap-sm);
192
+ font-family: var(--font-paragraph-sm-family);
193
+ font-size: var(--font-paragraph-sm-size);
194
+ font-weight: var(--font-paragraph-sm-weight);
195
+ line-height: var(--font-paragraph-sm-line-height);
196
+ letter-spacing: var(--font-paragraph-sm-letter-spacing);
137
197
  }
138
198
 
139
199
  /* ============================================
@@ -13,8 +13,12 @@ type AgentStatusOwnProps = {
13
13
  * on for `thinking`, `working` and `waiting`, off for the resting states.
14
14
  */
15
15
  shimmer?: boolean;
16
- /** Indicator and text scale. */
17
- size?: 'sm' | 'md';
16
+ /**
17
+ * Indicator and text scale, paired with ChatMessage's sizes: `default`
18
+ * matches default message text, `compact` matches compact message text.
19
+ * `sm` and `md` are legacy aliases for `compact` and `default`.
20
+ */
21
+ size?: 'default' | 'compact' | 'sm' | 'md';
18
22
  /** `inline` sits in a line of content; `bar` is a full-width row for the top of a panel. */
19
23
  variant?: 'inline' | 'bar';
20
24
  /** Additional CSS classes */
@@ -1,5 +1,5 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
2
- import React from "react";
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
+ import React, { useState, useRef, useEffect } from "react";
3
3
  import "./AgentStatus.css";
4
4
  const DOT_COUNT = 12;
5
5
  const DEFAULT_LABELS = {
@@ -17,7 +17,7 @@ const AgentStatus = React.forwardRef(
17
17
  label,
18
18
  pattern = "orbit",
19
19
  shimmer,
20
- size = "sm",
20
+ size = "default",
21
21
  variant = "inline",
22
22
  className = "",
23
23
  children,
@@ -26,11 +26,23 @@ const AgentStatus = React.forwardRef(
26
26
  const baseClass = "ds-agent-status";
27
27
  const isActive = ACTIVE_STATES.includes(state);
28
28
  const isShimmering = shimmer ?? isActive;
29
+ const resolvedSize = size === "sm" ? "compact" : size === "md" ? "default" : size;
30
+ const resolvedLabel = label ?? DEFAULT_LABELS[state];
31
+ const [outgoing, setOutgoing] = useState(null);
32
+ const lastLabel = useRef(resolvedLabel);
33
+ useEffect(() => {
34
+ if (children != null) return;
35
+ if (lastLabel.current !== resolvedLabel) {
36
+ setOutgoing(lastLabel.current);
37
+ lastLabel.current = resolvedLabel;
38
+ }
39
+ }, [resolvedLabel, children]);
40
+ const isSwapping = outgoing !== null;
29
41
  const classes = [
30
42
  baseClass,
31
43
  `${baseClass}--${state}`,
32
44
  `${baseClass}--${pattern}`,
33
- `${baseClass}--${size}`,
45
+ `${baseClass}--${resolvedSize}`,
34
46
  `${baseClass}--${variant}`,
35
47
  isActive ? `${baseClass}--animated` : "",
36
48
  className
@@ -40,8 +52,28 @@ const AgentStatus = React.forwardRef(
40
52
  /* @__PURE__ */ jsx(
41
53
  "span",
42
54
  {
43
- className: `${baseClass}__label${isShimmering ? ` ${baseClass}__label--shimmer` : ""}`,
44
- children: children ?? label ?? DEFAULT_LABELS[state]
55
+ className: `${baseClass}__label${isShimmering ? ` ${baseClass}__label--shimmer` : ""}${isSwapping ? ` ${baseClass}__label--swapping` : ""}`,
56
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
57
+ isSwapping && /* @__PURE__ */ jsx(
58
+ "span",
59
+ {
60
+ className: `${baseClass}__label-text ${baseClass}__label-text--out`,
61
+ "aria-hidden": "true",
62
+ children: outgoing
63
+ }
64
+ ),
65
+ /* @__PURE__ */ jsx(
66
+ "span",
67
+ {
68
+ className: `${baseClass}__label-text${isSwapping ? ` ${baseClass}__label-text--in` : ""}`,
69
+ onAnimationEnd: (e) => {
70
+ if (e.target === e.currentTarget) setOutgoing(null);
71
+ },
72
+ children: resolvedLabel
73
+ },
74
+ resolvedLabel
75
+ )
76
+ ] })
45
77
  }
46
78
  )
47
79
  ] });
@@ -0,0 +1,163 @@
1
+ /* ============================================
2
+ AI BUTTON
3
+ The AI entry point: transparent field, slowly
4
+ rotating gradient ring, soft glow of the same
5
+ gradient. The ring is the system's "a model
6
+ answers here" signal — ordinary actions keep
7
+ the flat action teal.
8
+ ============================================ */
9
+
10
+ /* The rotation animates a registered custom property: a conic gradient's
11
+ angle cannot tween otherwise. Browsers without @property render the ring
12
+ at its initial angle, static — a correct, quieter fallback. */
13
+ @property --ds-ai-button-angle {
14
+ syntax: '<angle>';
15
+ initial-value: 0deg;
16
+ inherits: false;
17
+ }
18
+
19
+ /* Base */
20
+ .ds-ai-button {
21
+ /* Layout */
22
+ position: relative;
23
+ display: inline-flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ gap: var(--gap-sm);
27
+
28
+ /* Spacing — matches Button so the two sit on one control scale */
29
+ padding: var(--padding-sm) var(--padding-lg);
30
+
31
+ /* Buttons are always pill-shaped (design.md invariant) */
32
+ border-radius: var(--radius-full);
33
+
34
+ /* Typography — the clickable-control face, same as Button */
35
+ font-family: var(--font-paragraph-em-family);
36
+ font-weight: var(--font-paragraph-em-weight);
37
+ font-size: var(--font-paragraph-em-size);
38
+ line-height: var(--font-paragraph-em-line-height);
39
+ letter-spacing: var(--font-paragraph-em-letter-spacing);
40
+ text-align: center;
41
+
42
+ /* The field is transparent by design: the ring and glow are the button */
43
+ background: transparent;
44
+ color: var(--color-text-primary);
45
+
46
+ transition: opacity var(--motion-duration-base) var(--motion-ease-standard);
47
+
48
+ /* Reset */
49
+ cursor: pointer;
50
+ border: none;
51
+ text-decoration: none;
52
+ }
53
+
54
+ /* The gradient ring: a conic gradient painted across the whole button, then
55
+ masked to the border band (two fills, exclude-composited, inset by the
56
+ border width). The gradient repeats back to its start colour so the seam
57
+ at 360° is invisible while it turns. */
58
+ .ds-ai-button::before {
59
+ content: '';
60
+ position: absolute;
61
+ inset: 0;
62
+ border-radius: inherit;
63
+ padding: var(--border-xs);
64
+ background: conic-gradient(
65
+ from var(--ds-ai-button-angle),
66
+ var(--color-ai-gradient-start),
67
+ var(--color-ai-gradient-mid),
68
+ var(--color-ai-gradient-end),
69
+ var(--color-ai-gradient-mid),
70
+ var(--color-ai-gradient-start)
71
+ );
72
+ /* Standard properties only — the build pipeline prefixes per its browser
73
+ targets, and hand-written -webkit- duplicates confuse its merging. */
74
+ mask:
75
+ linear-gradient(#fff 0 0) content-box,
76
+ linear-gradient(#fff 0 0);
77
+ mask-composite: exclude;
78
+ pointer-events: none;
79
+ /* ds-allow(motion): ambient signature rotation, tuned by eye — deliberately
80
+ far slower than the interaction-scale motion tokens */
81
+ animation: ds-ai-button-turn 7s linear infinite;
82
+ }
83
+
84
+ /* The glow: the same gradient, blurred behind the button at low opacity.
85
+ A colour effect, not an elevation shadow — depth stays token-owned. */
86
+ .ds-ai-button::after {
87
+ content: '';
88
+ position: absolute;
89
+ inset: 0;
90
+ border-radius: inherit;
91
+ background: conic-gradient(
92
+ from var(--ds-ai-button-angle),
93
+ var(--color-ai-gradient-start),
94
+ var(--color-ai-gradient-mid),
95
+ var(--color-ai-gradient-end),
96
+ var(--color-ai-gradient-mid),
97
+ var(--color-ai-gradient-start)
98
+ );
99
+ /* The glow's spread; a length with no token family, chosen by eye. */
100
+ --ds-ai-button-glow: 14px;
101
+ filter: blur(var(--ds-ai-button-glow));
102
+ opacity: 0.3;
103
+ z-index: -1;
104
+ pointer-events: none;
105
+ transition: opacity var(--motion-duration-base) var(--motion-ease-standard);
106
+ /* ds-allow(motion): same ambient rotation as the ring, kept in phase */
107
+ animation: ds-ai-button-turn 7s linear infinite;
108
+ }
109
+
110
+ @keyframes ds-ai-button-turn {
111
+ to {
112
+ --ds-ai-button-angle: 360deg;
113
+ }
114
+ }
115
+
116
+ /* Icon rides the icon-size scale */
117
+ .ds-ai-button__icon {
118
+ --icon-size: var(--icon-size-md);
119
+ line-height: 1;
120
+ }
121
+
122
+ /* States */
123
+ .ds-ai-button:hover::after {
124
+ opacity: 0.55;
125
+ }
126
+
127
+ .ds-ai-button:focus-visible {
128
+ outline: var(--border-md) solid var(--color-action-primary-bg);
129
+ outline-offset: 2px;
130
+ }
131
+
132
+ .ds-ai-button--disabled,
133
+ .ds-ai-button:disabled {
134
+ opacity: 0.4;
135
+ cursor: not-allowed;
136
+ }
137
+
138
+ .ds-ai-button--disabled::before,
139
+ .ds-ai-button--disabled::after,
140
+ .ds-ai-button:disabled::before,
141
+ .ds-ai-button:disabled::after {
142
+ animation-play-state: paused;
143
+ }
144
+
145
+ /* Sizes */
146
+ .ds-ai-button--compact {
147
+ padding: var(--padding-xs) var(--padding-sm-md);
148
+ font-size: var(--font-paragraph-sm-em-size);
149
+ line-height: var(--font-paragraph-sm-em-line-height);
150
+ letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
151
+ }
152
+
153
+ .ds-ai-button--compact .ds-ai-button__icon {
154
+ --icon-size: var(--icon-size-sm);
155
+ }
156
+
157
+ /* Reduced motion: the ring and glow stay, the turning stops. */
158
+ @media (prefers-reduced-motion: reduce) {
159
+ .ds-ai-button::before,
160
+ .ds-ai-button::after {
161
+ animation: none;
162
+ }
163
+ }
@@ -0,0 +1,36 @@
1
+ import { default as React } from 'react';
2
+ /** Props owned by AiButton itself — everything else falls through to the DOM node. */
3
+ type AiButtonOwnProps = {
4
+ /** Button text content */
5
+ label?: string;
6
+ /** Leading icon — Material Symbol name (string) or custom element (ReactNode) */
7
+ icon?: string | React.ReactNode;
8
+ /** Button size */
9
+ size?: 'default' | 'compact';
10
+ /** Whether the button is disabled */
11
+ disabled?: boolean;
12
+ /** Optional href — renders as <a> instead of <button> */
13
+ href?: string;
14
+ /** Optional target attribute for links */
15
+ target?: string;
16
+ /** Optional rel attribute for links */
17
+ rel?: string;
18
+ /** Additional CSS classes */
19
+ className?: string;
20
+ };
21
+ export interface AiButtonProps extends AiButtonOwnProps, Omit<React.ComponentPropsWithoutRef<'button'>, keyof AiButtonOwnProps | 'type'> {
22
+ }
23
+ /**
24
+ * The AI entry point. Icon and label on a transparent field, ringed by the
25
+ * AI gradient (`--color-ai-gradient-*`) rotating slowly, with a soft glow of
26
+ * the same gradient behind it. The treatment is the design system's signal
27
+ * for "a model answers here": ordinary actions keep the flat action teal,
28
+ * and this ring is reserved for AI surfaces so neither affordance dilutes
29
+ * the other.
30
+ *
31
+ * Renders a `<button>`, or an `<a>` when `href` is supplied. Forwards a ref
32
+ * to whichever element it renders, and spreads unrecognised props onto it.
33
+ * The rotation pauses under `prefers-reduced-motion`; the ring and glow stay.
34
+ */
35
+ export declare const AiButton: React.ForwardRefExoticComponent<AiButtonProps & React.RefAttributes<HTMLButtonElement | HTMLAnchorElement>>;
36
+ export {};
@@ -0,0 +1,58 @@
1
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import "./AiButton.css";
4
+ import "../../fonts/material-symbols.css";
5
+ const AiButton = React.forwardRef(
6
+ ({
7
+ label = "Ask AI",
8
+ icon = "auto_awesome",
9
+ size = "default",
10
+ disabled,
11
+ href,
12
+ target,
13
+ rel,
14
+ className = "",
15
+ ...rest
16
+ }, ref) => {
17
+ const baseClass = "ds-ai-button";
18
+ const classes = [
19
+ baseClass,
20
+ `${baseClass}--${size}`,
21
+ disabled && `${baseClass}--disabled`,
22
+ className
23
+ ].filter(Boolean).join(" ");
24
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
25
+ icon && (typeof icon === "string" ? /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon material-symbols-rounded`, "aria-hidden": "true", children: icon }) : /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon`, "aria-hidden": "true", children: icon })),
26
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__text`, children: label })
27
+ ] });
28
+ if (href && !disabled) {
29
+ return /* @__PURE__ */ jsx(
30
+ "a",
31
+ {
32
+ ...rest,
33
+ ref,
34
+ className: classes,
35
+ href,
36
+ target,
37
+ rel,
38
+ children: content
39
+ }
40
+ );
41
+ }
42
+ return /* @__PURE__ */ jsx(
43
+ "button",
44
+ {
45
+ ...rest,
46
+ ref,
47
+ type: "button",
48
+ className: classes,
49
+ disabled,
50
+ children: content
51
+ }
52
+ );
53
+ }
54
+ );
55
+ AiButton.displayName = "AiButton";
56
+ export {
57
+ AiButton
58
+ };
@@ -0,0 +1,35 @@
1
+ /* ============================================
2
+ CHAT HEADER COMPONENT
3
+ Top row of a chat surface: title on the
4
+ left, controls on the right. No divider —
5
+ it floats free of the transcript.
6
+ ============================================ */
7
+
8
+ /* Base */
9
+ .ds-chat-header {
10
+ display: flex;
11
+ align-items: center;
12
+ justify-content: space-between;
13
+ gap: var(--gap-md);
14
+ padding: var(--padding-xs) var(--padding-md);
15
+ }
16
+
17
+ /* Title */
18
+ .ds-chat-header__title {
19
+ font-family: var(--font-title-body-family);
20
+ font-size: var(--font-title-body-size);
21
+ font-weight: var(--font-title-body-weight);
22
+ line-height: var(--font-title-body-line-height);
23
+ color: var(--color-text-primary);
24
+ overflow: hidden;
25
+ text-overflow: ellipsis;
26
+ white-space: nowrap;
27
+ }
28
+
29
+ /* Actions */
30
+ .ds-chat-header__actions {
31
+ display: flex;
32
+ align-items: center;
33
+ gap: var(--gap-xs);
34
+ flex-shrink: 0;
35
+ }
@@ -0,0 +1,30 @@
1
+ import { default as React } from 'react';
2
+ /** Props owned by ChatHeader itself — everything else falls through to the root element. */
3
+ type ChatHeaderOwnProps = {
4
+ /**
5
+ * The chat or assistant name. Deliberately shadows the native `title`
6
+ * tooltip attribute — a header's title is content, not a tooltip. Pass a
7
+ * string for the default treatment, or your own heading element.
8
+ */
9
+ title?: React.ReactNode;
10
+ /**
11
+ * Trailing controls: a new-chat CircularButton, a view switcher, a close
12
+ * button. The slot only lays them out — each control owns its behaviour.
13
+ */
14
+ actions?: React.ReactNode;
15
+ /** Additional CSS classes */
16
+ className?: string;
17
+ };
18
+ export interface ChatHeaderProps extends ChatHeaderOwnProps, Omit<React.ComponentPropsWithoutRef<'header'>, keyof ChatHeaderOwnProps> {
19
+ }
20
+ /**
21
+ * ChatHeader is the top row of a chat surface: the conversation's name on the
22
+ * left, its controls on the right. It draws no bottom border — like the
23
+ * composer below, it floats free of the transcript, and the thread's own
24
+ * padding provides the separation.
25
+ *
26
+ * Purely presentational, so it renders from a Server Component; the controls
27
+ * passed into `actions` bring their own interactivity.
28
+ */
29
+ export declare const ChatHeader: React.ForwardRefExoticComponent<ChatHeaderProps & React.RefAttributes<HTMLElement>>;
30
+ export {};
@@ -0,0 +1,17 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import "./ChatHeader.css";
4
+ const ChatHeader = React.forwardRef(
5
+ ({ title, actions, className = "", ...rest }, ref) => {
6
+ const baseClass = "ds-chat-header";
7
+ const classes = [baseClass, className].filter(Boolean).join(" ");
8
+ return /* @__PURE__ */ jsxs("header", { ...rest, ref, className: classes, children: [
9
+ typeof title === "string" ? /* @__PURE__ */ jsx("span", { className: `${baseClass}__title`, children: title }) : title,
10
+ actions && /* @__PURE__ */ jsx("div", { className: `${baseClass}__actions`, children: actions })
11
+ ] });
12
+ }
13
+ );
14
+ ChatHeader.displayName = "ChatHeader";
15
+ export {
16
+ ChatHeader
17
+ };
@@ -0,0 +1,52 @@
1
+ /* ============================================
2
+ CHAT MARKER COMPONENT
3
+ Inline conversation separator for dates and system notes
4
+ ============================================ */
5
+
6
+ .ds-chat-marker {
7
+ display: flex;
8
+ align-items: center;
9
+ gap: var(--gap-sm-md);
10
+ width: 100%;
11
+ }
12
+
13
+ /* Flanking lines — kept as flex spacers when bare, so the label
14
+ stays centred either way */
15
+ .ds-chat-marker::before,
16
+ .ds-chat-marker::after {
17
+ content: '';
18
+ flex: 1;
19
+ height: var(--border-xs);
20
+ background-color: var(--color-divider);
21
+ }
22
+
23
+ .ds-chat-marker--bare::before,
24
+ .ds-chat-marker--bare::after {
25
+ background-color: transparent;
26
+ }
27
+
28
+ /* ============================================
29
+ LABEL
30
+ ============================================ */
31
+
32
+ .ds-chat-marker__label {
33
+ flex-shrink: 0;
34
+ display: inline-flex;
35
+ align-items: center;
36
+ gap: var(--gap-xs);
37
+ font-family: var(--font-paragraph-sm-family);
38
+ font-size: var(--font-paragraph-sm-size);
39
+ font-weight: var(--font-paragraph-sm-weight);
40
+ line-height: var(--font-paragraph-sm-line-height);
41
+ letter-spacing: var(--font-paragraph-sm-letter-spacing);
42
+ color: var(--color-text-tertiary);
43
+ }
44
+
45
+ .ds-chat-marker__icon {
46
+ display: inline-flex;
47
+ align-items: center;
48
+ }
49
+
50
+ .ds-chat-marker__icon .material-symbols-rounded {
51
+ --icon-size: var(--icon-size-sm);
52
+ }