@robr0/design-system 0.7.0 → 0.8.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.
@@ -13,10 +13,16 @@
13
13
  *
14
14
  * Coordinate space is chosen to match a CSS blob layer exactly, so a fallback
15
15
  * built from absolutely-positioned discs lines up with it: origin at the
16
- * container centre (`left`/`top: 50%`), one unit = container width (discs are
17
- * vw-sized, and CSS percentage margins — top included — resolve against the
18
- * containing block's width). Centres are therefore constants that never need
19
- * re-uploading on resize.
16
+ * container centre (`left`/`top: 50%`), one unit = the field's reference
17
+ * width (discs are vw-sized, and CSS percentage margins — top included —
18
+ * resolve against the containing block's width). Centres are therefore
19
+ * constants that never need re-uploading on resize.
20
+ *
21
+ * That reference width is the container's own width until `crop` holds it
22
+ * back on a narrow viewport, where the field keeps more of the scale it was
23
+ * composed at and the viewport crops into it instead. The hook resolves it to
24
+ * `u_unit`, in drawing-buffer pixels, so the shader itself never has to know
25
+ * which of the two it is looking at.
20
26
  *
21
27
  * Colours arrive already converted to linear RGB (the hook does the sRGB
22
28
  * decode once per theme change); the shader only encodes back at the end.
@@ -30,4 +36,4 @@
30
36
  */
31
37
  export declare const BLOB_COUNT = 8;
32
38
  export declare const vertexSource = "#version 300 es\n// Fullscreen triangle \u2014 no buffers, three vertices from gl_VertexID.\nvoid main() {\n vec2 p = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2);\n gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);\n}\n";
33
- export declare const fragmentSource = "#version 300 es\nprecision highp float;\n\nuniform vec2 u_resolution; // drawing-buffer size in pixels\nuniform float u_time; // field time, seconds (speed applied JS-side)\nuniform vec3 u_color[8]; // linear RGB per blob\nuniform vec4 u_blob[8]; // xy = centre, z = sigma, w = emission weight\nuniform vec4 u_motion[8]; // xy = angular speeds, zw = phases\nuniform float u_intensity; // peak-alpha ceiling\nuniform float u_warp; // domain-warp strength\nuniform float u_scale; // noise frequency\nuniform float u_grain; // film-grain strength\nuniform float u_streak; // anisotropic stretch: 0 = blobs, 1 = light streams\nuniform vec2 u_mouse; // pointer in field coordinates (smoothed JS-side)\nuniform float u_mvel; // smoothed pointer speed, width units/s\nuniform float u_react; // cursor-reactivity strength\n\nout vec4 outColor;\n\n// Integer hash \u2014 WebGL2 has real uint ops, so no fract(sin()) precision\n// lottery on mobile GPUs.\nuint ihash(uvec2 p) {\n p = p * uvec2(73333u, 7777u) ^ (p.yx >> 3u);\n uint h = p.x ^ (p.y * 2654435761u);\n h ^= h >> 15u;\n h *= 2246822519u;\n h ^= h >> 13u;\n return h;\n}\n\nfloat rnd(ivec2 p) {\n return float(ihash(uvec2(p)) >> 8) * (1.0 / 16777216.0);\n}\n\nfloat vnoise(vec2 p) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n vec2 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);\n ivec2 c = ivec2(i);\n return mix(\n mix(rnd(c), rnd(c + ivec2(1, 0)), u.x),\n mix(rnd(c + ivec2(0, 1)), rnd(c + ivec2(1, 1)), u.x),\n u.y\n );\n}\n\n// 3 octaves; a 4th is invisible at this softness.\nfloat fbm(vec2 p) {\n float s = 0.0;\n float a = 0.5;\n for (int i = 0; i < 3; i++) {\n s += a * vnoise(p);\n p = p * 2.02 + vec2(17.0, 9.0);\n a *= 0.5;\n }\n return s;\n}\n\nvoid main() {\n // CSS coordinate space: origin at container centre, one unit = width,\n // y running downward.\n vec2 p = (gl_FragCoord.xy - 0.5 * u_resolution) / u_resolution.x;\n p.y = -p.y;\n\n // Light-stream axis: a fixed diagonal. Distances across the axis are\n // magnified before the Gaussian, so every source stretches along it and\n // the field reads as rays instead of discs.\n float ca = cos(0.5);\n float sa = sin(0.5);\n mat2 toStreak = mat2(ca, -sa, sa, ca);\n float squeeze = 1.0 + u_streak * 4.0;\n\n // Domain warp: the field flows rather than drifts. The noise is sampled\n // in the same anisotropic space, so the flow lines follow the streams.\n float t = u_time;\n vec2 ps = toStreak * p;\n ps.y *= squeeze;\n vec2 q = vec2(\n fbm(ps * u_scale + vec2(0.0, t * 0.030)),\n fbm(ps * u_scale + vec2(5.2, 1.3) - t * 0.024)\n );\n vec2 w = p + u_warp * (q - 0.5);\n\n // Cursor wake: a smooth vortex around the pointer, amplified by speed.\n // The displacement is built from the *unnormalised* offset so it falls to\n // zero at the cursor itself. Normalising here (md/mr) made the direction\n // spin through 360 degrees at full strength across the centre pixel, which\n // rendered as a pinch \u2014 a cone converging to a point.\n vec2 md = p - u_mouse;\n float mr2 = dot(md, md);\n float minf = exp(-mr2 / (2.0 * 0.22 * 0.22));\n vec2 swirl = vec2(-md.y, md.x);\n // smoothstep, not min(): a hard clamp stepped visibly as speed crossed it.\n float stir = 0.25 + 1.3 * smoothstep(0.0, 0.8, u_mvel);\n w += u_react * minf * stir * (swirl * 0.9 + md * 0.35);\n\n // Accumulate true Gaussians in linear space. Positive weights emit;\n // negative weights absorb (shadows \u2014 the eclipse disc).\n vec3 acc = vec3(0.0);\n float cov = 0.0;\n float shade = 0.0;\n for (int i = 0; i < 8; i++) {\n vec2 c = u_blob[i].xy + 0.045 * vec2(\n cos(u_motion[i].x * t + u_motion[i].z),\n sin(u_motion[i].y * t + u_motion[i].w)\n );\n vec2 d = toStreak * (w - c);\n d.y *= squeeze;\n float s = u_blob[i].z;\n float g = exp(-dot(d, d) / (2.0 * s * s));\n float wgt = u_blob[i].w;\n if (wgt >= 0.0) {\n acc += u_color[i] * (g * wgt);\n cov += g * wgt;\n } else {\n shade += g * -wgt;\n }\n }\n\n // Glow trail: wider and softer than the swirl, and mostly velocity-driven,\n // so a resting cursor does not park a bright dot on the field.\n //\n // The wake brightens whatever colour it is passing over rather than adding\n // one of its own. Injecting u_color[0] meant the trail always wore the\n // first blob's token \u2014 gold in the site palette, violet in beam \u2014 which\n // read as a stray colour with no relationship to the field beneath it.\n // Weighting by the local hue keeps the wake colour-neutral: it reads as\n // light falling on the field, and it stays correct for any palette.\n vec3 local = acc / max(cov, 1e-4);\n float gfall = exp(-mr2 / (2.0 * 0.3 * 0.3));\n float glow = u_react * gfall * (0.12 + 0.5 * smoothstep(0.0, 0.8, u_mvel));\n acc += local * glow;\n cov += glow;\n\n vec3 lin = acc / max(cov, 1e-4);\n float lit = max(cov - shade, 0.0);\n float alpha = u_intensity * (1.0 - exp(-lit * 0.9));\n\n vec3 srgb = pow(max(lin, vec3(0.0)), vec3(1.0 / 2.2));\n\n // Film grain: animated hash noise. At u_grain 0 this degenerates to the\n // \u00B11/255 dither that kills banding, so the two share one lookup.\n uvec2 gp = uvec2(gl_FragCoord.xy) + uvec2(\n uint(fract(t * 7.31) * 1024.0) * 7919u,\n uint(fract(t * 3.17) * 1024.0) * 104729u\n );\n float gr = float(ihash(gp) & 255u) / 255.0 - 0.5;\n srgb += gr * (1.0 / 255.0 + u_grain * 0.35);\n // A small grain-driven alpha floor lets the sparkle read even where the\n // field itself is empty (the reference's grainy dark scene).\n float grainFloor = u_grain * 0.1 * (gr + 0.5);\n alpha = clamp(alpha * (1.0 + gr * u_grain * 0.5) + grainFloor, 0.0, 1.0);\n srgb = clamp(srgb, 0.0, 1.0);\n\n // Premultiplied output (context is created with premultipliedAlpha: true).\n outColor = vec4(srgb * alpha, alpha);\n}\n";
39
+ export declare const fragmentSource = "#version 300 es\nprecision highp float;\n\nuniform vec2 u_resolution; // drawing-buffer size in pixels\nuniform float u_unit; // pixels per field unit (see coordinate space)\nuniform float u_time; // field time, seconds (speed applied JS-side)\nuniform vec3 u_color[8]; // linear RGB per blob\nuniform vec4 u_blob[8]; // xy = centre, z = sigma, w = emission weight\nuniform vec4 u_motion[8]; // xy = angular speeds, zw = phases\nuniform float u_intensity; // peak-alpha ceiling\nuniform float u_warp; // domain-warp strength\nuniform float u_scale; // noise frequency\nuniform float u_grain; // film-grain strength\nuniform float u_streak; // anisotropic stretch: 0 = blobs, 1 = light streams\nuniform vec2 u_mouse; // pointer in field coordinates (smoothed JS-side)\nuniform float u_mvel; // smoothed pointer speed, width units/s\nuniform float u_react; // cursor-reactivity strength\n\nout vec4 outColor;\n\n// Integer hash \u2014 WebGL2 has real uint ops, so no fract(sin()) precision\n// lottery on mobile GPUs.\nuint ihash(uvec2 p) {\n p = p * uvec2(73333u, 7777u) ^ (p.yx >> 3u);\n uint h = p.x ^ (p.y * 2654435761u);\n h ^= h >> 15u;\n h *= 2246822519u;\n h ^= h >> 13u;\n return h;\n}\n\nfloat rnd(ivec2 p) {\n return float(ihash(uvec2(p)) >> 8) * (1.0 / 16777216.0);\n}\n\nfloat vnoise(vec2 p) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n vec2 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);\n ivec2 c = ivec2(i);\n return mix(\n mix(rnd(c), rnd(c + ivec2(1, 0)), u.x),\n mix(rnd(c + ivec2(0, 1)), rnd(c + ivec2(1, 1)), u.x),\n u.y\n );\n}\n\n// 3 octaves; a 4th is invisible at this softness.\nfloat fbm(vec2 p) {\n float s = 0.0;\n float a = 0.5;\n for (int i = 0; i < 3; i++) {\n s += a * vnoise(p);\n p = p * 2.02 + vec2(17.0, 9.0);\n a *= 0.5;\n }\n return s;\n}\n\nvoid main() {\n // CSS coordinate space: origin at container centre, one unit = the\n // reference width the hook resolved, y running downward.\n vec2 p = (gl_FragCoord.xy - 0.5 * u_resolution) / u_unit;\n p.y = -p.y;\n\n // Light-stream axis: a fixed diagonal. Distances across the axis are\n // magnified before the Gaussian, so every source stretches along it and\n // the field reads as rays instead of discs.\n float ca = cos(0.5);\n float sa = sin(0.5);\n mat2 toStreak = mat2(ca, -sa, sa, ca);\n float squeeze = 1.0 + u_streak * 4.0;\n\n // Domain warp: the field flows rather than drifts. The noise is sampled\n // in the same anisotropic space, so the flow lines follow the streams.\n float t = u_time;\n vec2 ps = toStreak * p;\n ps.y *= squeeze;\n vec2 q = vec2(\n fbm(ps * u_scale + vec2(0.0, t * 0.030)),\n fbm(ps * u_scale + vec2(5.2, 1.3) - t * 0.024)\n );\n vec2 w = p + u_warp * (q - 0.5);\n\n // Cursor wake: a smooth vortex around the pointer, amplified by speed.\n // The displacement is built from the *unnormalised* offset so it falls to\n // zero at the cursor itself. Normalising here (md/mr) made the direction\n // spin through 360 degrees at full strength across the centre pixel, which\n // rendered as a pinch \u2014 a cone converging to a point.\n vec2 md = p - u_mouse;\n float mr2 = dot(md, md);\n float minf = exp(-mr2 / (2.0 * 0.22 * 0.22));\n vec2 swirl = vec2(-md.y, md.x);\n // smoothstep, not min(): a hard clamp stepped visibly as speed crossed it.\n float stir = 0.25 + 1.3 * smoothstep(0.0, 0.8, u_mvel);\n w += u_react * minf * stir * (swirl * 0.9 + md * 0.35);\n\n // Accumulate true Gaussians in linear space. Positive weights emit;\n // negative weights absorb (shadows \u2014 the eclipse disc).\n vec3 acc = vec3(0.0);\n float cov = 0.0;\n float shade = 0.0;\n for (int i = 0; i < 8; i++) {\n vec2 c = u_blob[i].xy + 0.045 * vec2(\n cos(u_motion[i].x * t + u_motion[i].z),\n sin(u_motion[i].y * t + u_motion[i].w)\n );\n vec2 d = toStreak * (w - c);\n d.y *= squeeze;\n float s = u_blob[i].z;\n float g = exp(-dot(d, d) / (2.0 * s * s));\n float wgt = u_blob[i].w;\n if (wgt >= 0.0) {\n acc += u_color[i] * (g * wgt);\n cov += g * wgt;\n } else {\n shade += g * -wgt;\n }\n }\n\n // Glow trail: wider and softer than the swirl, and mostly velocity-driven,\n // so a resting cursor does not park a bright dot on the field.\n //\n // The wake brightens whatever colour it is passing over rather than adding\n // one of its own. Injecting u_color[0] meant the trail always wore the\n // first blob's token \u2014 gold in the site palette, violet in beam \u2014 which\n // read as a stray colour with no relationship to the field beneath it.\n // Weighting by the local hue keeps the wake colour-neutral: it reads as\n // light falling on the field, and it stays correct for any palette.\n vec3 local = acc / max(cov, 1e-4);\n float gfall = exp(-mr2 / (2.0 * 0.3 * 0.3));\n float glow = u_react * gfall * (0.12 + 0.5 * smoothstep(0.0, 0.8, u_mvel));\n acc += local * glow;\n cov += glow;\n\n vec3 lin = acc / max(cov, 1e-4);\n float lit = max(cov - shade, 0.0);\n float alpha = u_intensity * (1.0 - exp(-lit * 0.9));\n\n vec3 srgb = pow(max(lin, vec3(0.0)), vec3(1.0 / 2.2));\n\n // Film grain: animated hash noise. At u_grain 0 this degenerates to the\n // \u00B11/255 dither that kills banding, so the two share one lookup.\n uvec2 gp = uvec2(gl_FragCoord.xy) + uvec2(\n uint(fract(t * 7.31) * 1024.0) * 7919u,\n uint(fract(t * 3.17) * 1024.0) * 104729u\n );\n float gr = float(ihash(gp) & 255u) / 255.0 - 0.5;\n srgb += gr * (1.0 / 255.0 + u_grain * 0.35);\n // A small grain-driven alpha floor lets the sparkle read even where the\n // field itself is empty (the reference's grainy dark scene).\n float grainFloor = u_grain * 0.1 * (gr + 0.5);\n alpha = clamp(alpha * (1.0 + gr * u_grain * 0.5) + grainFloor, 0.0, 1.0);\n srgb = clamp(srgb, 0.0, 1.0);\n\n // Premultiplied output (context is created with premultipliedAlpha: true).\n outColor = vec4(srgb * alpha, alpha);\n}\n";
@@ -15,6 +15,7 @@ const fragmentSource = (
15
15
  precision highp float;
16
16
 
17
17
  uniform vec2 u_resolution; // drawing-buffer size in pixels
18
+ uniform float u_unit; // pixels per field unit (see coordinate space)
18
19
  uniform float u_time; // field time, seconds (speed applied JS-side)
19
20
  uniform vec3 u_color[${BLOB_COUNT}]; // linear RGB per blob
20
21
  uniform vec4 u_blob[${BLOB_COUNT}]; // xy = centre, z = sigma, w = emission weight
@@ -70,9 +71,9 @@ float fbm(vec2 p) {
70
71
  }
71
72
 
72
73
  void main() {
73
- // CSS coordinate space: origin at container centre, one unit = width,
74
- // y running downward.
75
- vec2 p = (gl_FragCoord.xy - 0.5 * u_resolution) / u_resolution.x;
74
+ // CSS coordinate space: origin at container centre, one unit = the
75
+ // reference width the hook resolved, y running downward.
76
+ vec2 p = (gl_FragCoord.xy - 0.5 * u_resolution) / u_unit;
76
77
  p.y = -p.y;
77
78
 
78
79
  // Light-stream axis: a fixed diagonal. Distances across the axis are
@@ -1,5 +1,5 @@
1
1
  import { RefObject } from 'react';
2
- /** The seven tuneable properties of the field. */
2
+ /** The eight tuneable properties of the field. */
3
3
  export interface ShaderParams {
4
4
  /** Overall opacity of the field, 0.1–1. */
5
5
  intensity: number;
@@ -19,6 +19,23 @@ export interface ShaderParams {
19
19
  * with them the loop's step up from 30fps to 60fps while a wake is alive.
20
20
  */
21
21
  react: number;
22
+ /**
23
+ * How much the composition resists shrinking with the container, 0–1.
24
+ *
25
+ * At 0 — the default, and the behaviour a CSS blob layer has — the whole
26
+ * field is fitted to whatever width it is given, so a phone shows the
27
+ * entire composition at phone scale: every source small, and more of them
28
+ * crowded into view than the look was built for. At 1 the field holds the
29
+ * scale it was composed at (`REFERENCE_WIDTH`) and a narrower container
30
+ * crops into it instead. Values between blend the two geometrically,
31
+ * keeping some of the shrink so colours still bleed together on a small
32
+ * screen. Above the reference width it does nothing.
33
+ *
34
+ * Raise it for a full-viewport background, which is read on a phone as a
35
+ * scene rather than a diagram; leave it at 0 wherever the point is to see
36
+ * the whole composition, such as a small demo tile.
37
+ */
38
+ crop: number;
22
39
  }
23
40
  /**
24
41
  * One light source in the field.
@@ -52,7 +69,7 @@ export interface ShaderBlob {
52
69
  weight: number;
53
70
  }
54
71
  /**
55
- * The shipped look: seven parameters tuned by eye, cursor wake off. Spread it
72
+ * The shipped look: eight parameters tuned by eye, cursor wake off. Spread it
56
73
  * and override the one or two you want rather than restating the set.
57
74
  */
58
75
  export declare const DEFAULT_SHADER_PARAMS: ShaderParams;
@@ -7,7 +7,8 @@ const DEFAULT_SHADER_PARAMS = {
7
7
  speed: 2,
8
8
  grain: 0.1,
9
9
  streak: 0.4,
10
- react: 0
10
+ react: 0,
11
+ crop: 0.5
11
12
  };
12
13
  const DEFAULT_SHADER_BLOBS = [
13
14
  { token: "--color-core-accent-gold", size: 0.45, cx: 0.175, cy: 0.125, period: 18, phase: 0, weight: 1 },
@@ -20,6 +21,11 @@ const DEFAULT_SHADER_BLOBS = [
20
21
  { token: "--color-core-ui-secondary", size: 0.75, cx: -0.125, cy: -0.025, period: 20, phase: 0.7, weight: 1 }
21
22
  ];
22
23
  const RENDER_SCALE = 0.5;
24
+ const REFERENCE_WIDTH = 1440;
25
+ function fieldUnit(cssWidth, crop) {
26
+ if (cssWidth <= 0) return 1;
27
+ return cssWidth * Math.pow(Math.max(REFERENCE_WIDTH / cssWidth, 1), crop);
28
+ }
23
29
  const FRAME_MS = 1e3 / 30;
24
30
  const FRAME_MS_ACTIVE = 1e3 / 60;
25
31
  const WAKE_IDLE = 0.015;
@@ -113,6 +119,7 @@ function useShaderField(canvasRef, params, enabled = true) {
113
119
  loc = {};
114
120
  for (const name of [
115
121
  "u_resolution",
122
+ "u_unit",
116
123
  "u_time",
117
124
  "u_color[0]",
118
125
  "u_blob[0]",
@@ -176,10 +183,12 @@ function useShaderField(canvasRef, params, enabled = true) {
176
183
  colorTarget.set(colorCurrent);
177
184
  let bufWidth = 0;
178
185
  let bufHeight = 0;
186
+ let cssWidth = 0;
179
187
  function resize() {
180
188
  if (!gl) return;
181
189
  const c = canvas;
182
190
  const dpr = window.devicePixelRatio || 1;
191
+ cssWidth = c.clientWidth;
183
192
  const w = Math.max(1, Math.round(c.clientWidth * dpr * RENDER_SCALE));
184
193
  const h = Math.max(1, Math.round(c.clientHeight * dpr * RENDER_SCALE));
185
194
  if (w === bufWidth && h === bufHeight) return;
@@ -200,8 +209,9 @@ function useShaderField(canvasRef, params, enabled = true) {
200
209
  const c = canvas;
201
210
  const rect = c.getBoundingClientRect();
202
211
  if (rect.width === 0) return;
203
- const x = (e.clientX - rect.left - rect.width / 2) / rect.width;
204
- const y = (e.clientY - rect.top - rect.height / 2) / rect.width;
212
+ const unit = fieldUnit(rect.width, paramsRef.current.crop);
213
+ const x = (e.clientX - rect.left - rect.width / 2) / unit;
214
+ const y = (e.clientY - rect.top - rect.height / 2) / unit;
205
215
  if (mouseTarget.x < 5) {
206
216
  moveAccum += Math.hypot(x - mouseTarget.x, y - mouseTarget.y);
207
217
  }
@@ -234,6 +244,10 @@ function useShaderField(canvasRef, params, enabled = true) {
234
244
  }
235
245
  const p = paramsRef.current;
236
246
  gl.uniform3fv(loc["u_color[0]"], colorCurrent);
247
+ gl.uniform1f(
248
+ loc["u_unit"],
249
+ bufWidth * (fieldUnit(cssWidth, p.crop) / Math.max(cssWidth, 1))
250
+ );
237
251
  gl.uniform1f(loc["u_time"], fieldTime);
238
252
  gl.uniform1f(loc["u_intensity"], p.intensity);
239
253
  gl.uniform1f(loc["u_warp"], p.warp);
@@ -286,6 +286,23 @@
286
286
  color: var(--color-status-positive-text);
287
287
  }
288
288
 
289
+ .ds-timeline__role-subtitle {
290
+ margin: 0;
291
+ font-family: var(--font-paragraph-family);
292
+ font-size: var(--font-paragraph-size);
293
+ font-weight: var(--font-paragraph-weight);
294
+ line-height: var(--font-paragraph-line-height);
295
+ letter-spacing: var(--font-paragraph-letter-spacing);
296
+ color: var(--color-text-secondary);
297
+ }
298
+
299
+ /* The title and its subtitle read as one header block, so whatever follows
300
+ needs a wider break than the role's own 8px rhythm to separate from it. */
301
+ .ds-timeline__role-subtitle + .ds-timeline__role-description,
302
+ .ds-timeline__role-subtitle + .ds-timeline__role-bullets {
303
+ margin-top: var(--gap-sm);
304
+ }
305
+
289
306
  .ds-timeline__role-description {
290
307
  margin: 0;
291
308
  font-family: var(--font-paragraph-family);
@@ -13,6 +13,8 @@ export interface TimelineItem {
13
13
  export interface TimelineRole {
14
14
  /** Role or position title */
15
15
  title: string;
16
+ /** Secondary line under the title, e.g. the team, org, or product the role sat in */
17
+ subtitle?: React.ReactNode;
16
18
  /** Start of the date range, e.g. "May 2024" (omit for entries with no dates) */
17
19
  start?: string;
18
20
  /** End of the date range, e.g. "Jan 2026" (ignored when `present` is set) */
@@ -23,6 +23,7 @@ const CompanyTimeline = ({ items, className = "" }) => {
23
23
  /* @__PURE__ */ jsx("h3", { className: `${BASE_CLASS}__role-title`, children: role.title }),
24
24
  renderRoleDates(role)
25
25
  ] }),
26
+ role.subtitle && /* @__PURE__ */ jsx("p", { className: `${BASE_CLASS}__role-subtitle`, children: role.subtitle }),
26
27
  role.description && /* @__PURE__ */ jsx("p", { className: `${BASE_CLASS}__role-description`, children: role.description }),
27
28
  role.bullets && role.bullets.length > 0 && /* @__PURE__ */ jsx("ul", { className: `${BASE_CLASS}__role-bullets`, children: role.bullets.map((bullet, bulletIndex) => /* @__PURE__ */ jsx("li", { children: bullet }, bulletIndex)) })
28
29
  ] }, `${role.title}-${roleIndex}`)) })
@@ -20,6 +20,14 @@
20
20
  "category": "data-display",
21
21
  "client": true
22
22
  },
23
+ {
24
+ "name": "AgentPlan",
25
+ "label": "Agent plan",
26
+ "slug": "agent-plan",
27
+ "description": "A collapsible checklist of an agent's task, with live step states and a progress readout.",
28
+ "category": "ai",
29
+ "client": true
30
+ },
23
31
  {
24
32
  "name": "AgentStatus",
25
33
  "label": "Agent status",
@@ -252,6 +260,14 @@
252
260
  "category": "charts",
253
261
  "client": false
254
262
  },
263
+ {
264
+ "name": "DataTable",
265
+ "label": "Data table",
266
+ "slug": "data-table",
267
+ "description": "The wired table: sorting, search, row selection, and pagination assembled around Table.",
268
+ "category": "data-display",
269
+ "client": true
270
+ },
255
271
  {
256
272
  "name": "DateInput",
257
273
  "label": "Date input",
@@ -332,6 +348,14 @@
332
348
  "category": "data-display",
333
349
  "client": false
334
350
  },
351
+ {
352
+ "name": "EventCalendar",
353
+ "label": "Event calendar",
354
+ "slug": "event-calendar",
355
+ "description": "A month grid with event pills, overflow counts, and month navigation.",
356
+ "category": "data-display",
357
+ "client": true
358
+ },
335
359
  {
336
360
  "name": "Field",
337
361
  "label": "Field",
@@ -412,6 +436,14 @@
412
436
  "category": "ai",
413
437
  "client": false
414
438
  },
439
+ {
440
+ "name": "ModelPicker",
441
+ "label": "Model picker",
442
+ "slug": "model-picker",
443
+ "description": "A model selector for chat surfaces, with per-model descriptions and an optional effort row.",
444
+ "category": "ai",
445
+ "client": true
446
+ },
415
447
  {
416
448
  "name": "Nav",
417
449
  "label": "Navigation",
@@ -428,6 +460,14 @@
428
460
  "category": "navigation",
429
461
  "client": true
430
462
  },
463
+ {
464
+ "name": "NotificationCenter",
465
+ "label": "Notification centre",
466
+ "slug": "notification-center",
467
+ "description": "A persistent notification inbox with unread count, filter tabs, and per-item actions.",
468
+ "category": "feedback",
469
+ "client": true
470
+ },
431
471
  {
432
472
  "name": "Pagination",
433
473
  "label": "Pagination",
@@ -20,6 +20,14 @@ declare const _default: {
20
20
  "category": "data-display",
21
21
  "client": true
22
22
  },
23
+ {
24
+ "name": "AgentPlan",
25
+ "label": "Agent plan",
26
+ "slug": "agent-plan",
27
+ "description": "A collapsible checklist of an agent's task, with live step states and a progress readout.",
28
+ "category": "ai",
29
+ "client": true
30
+ },
23
31
  {
24
32
  "name": "AgentStatus",
25
33
  "label": "Agent status",
@@ -252,6 +260,14 @@ declare const _default: {
252
260
  "category": "charts",
253
261
  "client": false
254
262
  },
263
+ {
264
+ "name": "DataTable",
265
+ "label": "Data table",
266
+ "slug": "data-table",
267
+ "description": "The wired table: sorting, search, row selection, and pagination assembled around Table.",
268
+ "category": "data-display",
269
+ "client": true
270
+ },
255
271
  {
256
272
  "name": "DateInput",
257
273
  "label": "Date input",
@@ -332,6 +348,14 @@ declare const _default: {
332
348
  "category": "data-display",
333
349
  "client": false
334
350
  },
351
+ {
352
+ "name": "EventCalendar",
353
+ "label": "Event calendar",
354
+ "slug": "event-calendar",
355
+ "description": "A month grid with event pills, overflow counts, and month navigation.",
356
+ "category": "data-display",
357
+ "client": true
358
+ },
335
359
  {
336
360
  "name": "Field",
337
361
  "label": "Field",
@@ -412,6 +436,14 @@ declare const _default: {
412
436
  "category": "ai",
413
437
  "client": false
414
438
  },
439
+ {
440
+ "name": "ModelPicker",
441
+ "label": "Model picker",
442
+ "slug": "model-picker",
443
+ "description": "A model selector for chat surfaces, with per-model descriptions and an optional effort row.",
444
+ "category": "ai",
445
+ "client": true
446
+ },
415
447
  {
416
448
  "name": "Nav",
417
449
  "label": "Navigation",
@@ -428,6 +460,14 @@ declare const _default: {
428
460
  "category": "navigation",
429
461
  "client": true
430
462
  },
463
+ {
464
+ "name": "NotificationCenter",
465
+ "label": "Notification centre",
466
+ "slug": "notification-center",
467
+ "description": "A persistent notification inbox with unread count, filter tabs, and per-item actions.",
468
+ "category": "feedback",
469
+ "client": true
470
+ },
431
471
  {
432
472
  "name": "Pagination",
433
473
  "label": "Pagination",
@@ -1,5 +1,5 @@
1
1
  const categories = ["actions", "ai", "charts", "data-display", "feedback", "forms", "layout", "navigation", "overlays"];
2
- const components = /* @__PURE__ */ JSON.parse(`[{"name":"Accordion","label":"Accordion","slug":"accordion","description":"Collapsible content sections for organising related information.","category":"data-display","client":true},{"name":"AgentStatus","label":"Agent status","slug":"agent-status","description":"A dot-matrix indicator and status line reporting what an agent is doing right now.","category":"ai","client":true},{"name":"AiButton","label":"AI button","slug":"ai-button","description":"The AI entry point: icon and label on a transparent field, ringed by a slowly turning gradient and a soft glow.","category":"ai","client":true},{"name":"Alert","label":"Alert","slug":"alert","description":"Contextual feedback with status variants, optional dismiss, and compact sizing.","category":"feedback","client":false},{"name":"AlertDialog","label":"Alert dialog","slug":"alert-dialog","description":"Modal confirmation overlay with title, description, and confirm / cancel actions.","category":"overlays","client":true},{"name":"AppLayout","label":"App layout","slug":"app-layout","description":"Full-page template pairing the collapsible App sidebar with a centred content area.","category":"layout","client":true},{"name":"AppSidebar","label":"App sidebar","slug":"app-sidebar","description":"Collapsible navigation rail with accordion sub-items, category headings, and profile section.","category":"layout","client":true},{"name":"Avatar","label":"Avatar","slug":"avatar","description":"User profile image with initials and icon fallback, status indicator, and multiple sizes.","category":"data-display","client":true},{"name":"Badge","label":"Badge","slug":"badge","description":"Small inline status labels with info, positive, warning, error, and neutral variants.","category":"data-display","client":false},{"name":"Breadcrumb","label":"Breadcrumb","slug":"breadcrumb","description":"Hierarchical navigation trail showing the user's location within the site.","category":"navigation","client":false},{"name":"Button","label":"Button","slug":"button","description":"Primary and secondary button variants in default and compact sizes, with icon support and multiple states.","category":"actions","client":true},{"name":"ButtonGroup","label":"Button group","slug":"button-group","description":"Horizontal and vertical button group layouts for related actions and navigation patterns.","category":"actions","client":false},{"name":"Card","label":"Card","slug":"card","description":"Card components for previews, navigation, and token documentation, from content cards to colour swatches and typography specimens.","category":"data-display","client":true},{"name":"Carousel","label":"Carousel","slug":"carousel","description":"Sliding content viewer with navigation arrows, dot indicators, auto-play, and keyboard support.","category":"data-display","client":true},{"name":"Chart","label":"Chart","slug":"chart","description":"Data visualisation components built on Recharts. Includes bar, line, pie, and radial chart types with design-system token integration.","category":"charts","client":false},{"name":"ChatHeader","label":"Chat header","slug":"chat-header","description":"The top row of a chat surface, with the conversation title and its controls.","category":"ai","client":false},{"name":"ChatMarker","label":"Chat marker","slug":"chat-marker","description":"An inline conversation separator for date breaks and system notes.","category":"ai","client":false},{"name":"ChatMessage","label":"Chat message","slug":"chat-message","description":"A single chat turn with avatar, author, timestamp, and bubble or plain content aligned by role.","category":"ai","client":false},{"name":"ChatThread","label":"Chat thread","slug":"chat-thread","description":"A scrollable conversation column with edge fades, send anchoring, and a subtle scrollbar.","category":"ai","client":true},{"name":"Checkbox","label":"Checkbox","slug":"checkbox","description":"Custom checkbox with check and indeterminate states, keyboard accessible with animated transitions.","category":"forms","client":true},{"name":"Chip","label":"Chip","slug":"chip","description":"Compact pills for attributes, filters, and inline metadata.","category":"data-display","client":false},{"name":"CircularButton","label":"Circular button","slug":"circular-button","description":"Round icon button with primary and secondary variants, default and compact sizes.","category":"actions","client":false},{"name":"CodeBlock","label":"Code block","slug":"code-block","description":"Monospace code with a header and one-click copy.","category":"data-display","client":true},{"name":"ColorPicker","label":"Colour picker","slug":"color-picker","description":"Swatch trigger opening a saturation area, hue and alpha sliders, and a hex field; controlled or uncontrolled.","category":"forms","client":true},{"name":"Combobox","label":"Combobox","slug":"combobox","description":"A filterable select that narrows options as the user types, with multi-select chips, grouping, and async loading.","category":"forms","client":true},{"name":"CommandPalette","label":"Command palette","slug":"command-palette","description":"A modal Cmd+K launcher that searches a grouped command list, with keyboard navigation and shortcut hints.","category":"overlays","client":true},{"name":"Composer","label":"Composer","slug":"composer","description":"An auto-growing message input with send and stop states, an attachment slot, and Enter-to-send.","category":"ai","client":true},{"name":"ContactCard","label":"Contact card","slug":"contact-card","description":"Linked contact method with icon, label, and value.","category":"data-display","client":true},{"name":"ContextMenu","label":"Context menu","slug":"context-menu","description":"Right-click menu at the pointer with groups, sub-menus, and shortcut hints.","category":"overlays","client":true},{"name":"ContributionGraph","label":"Contribution graph","slug":"contribution-graph","description":"A year of activity, one cell per day.","category":"charts","client":false},{"name":"DateInput","label":"Date input","slug":"date-input","description":"Date input with native picker, calendar icon, label, and validation states.","category":"forms","client":true},{"name":"DatePicker","label":"Date picker","slug":"date-picker","description":"Inline calendar with month navigation, day selection, and today indicator.","category":"forms","client":true},{"name":"Dialog","label":"Dialog","slug":"dialog","description":"A general-purpose modal for focused tasks, with sizes, an optional footer, and full focus management.","category":"overlays","client":true},{"name":"Divider","label":"Divider","slug":"divider","description":"A thin rule separating stacked content, with optional inline label and vertical orientation.","category":"layout","client":false},{"name":"DocumentChip","label":"Document chip","slug":"document-chip","description":"A compact file reference with a type icon, name, metadata, and optional remove.","category":"ai","client":false},{"name":"Drawer","label":"Drawer","slug":"drawer","description":"An edge-anchored modal panel that slides in from any side, for filter panels, detail views, and mobile navigation.","category":"overlays","client":true},{"name":"Dropdown","label":"Dropdown","slug":"dropdown","description":"Custom select dropdown with keyboard navigation, disabled options, and error states.","category":"forms","client":true},{"name":"DropdownMenu","label":"Dropdown menu","slug":"dropdown-menu","description":"Contextual menu with sections, sub-menus, keyboard shortcuts, and inset-gap hover styling.","category":"overlays","client":true},{"name":"EmptyState","label":"Empty state","slug":"empty-state","description":"The placeholder for a list, table, or search with nothing to show: icon, headline, guidance, and a next action.","category":"feedback","client":false},{"name":"EntityCard","label":"Entity card","slug":"entity-card","description":"Compact display-only card with a centred icon or image and a label, used in the Icons and Logos galleries.","category":"data-display","client":false},{"name":"Field","label":"Field","slug":"field","description":"The shared scaffolding for labelled form controls: label, required marker, helper and error text, and the ARIA wiring that ties them together.","category":"forms","client":true},{"name":"Figure","label":"Figure","slug":"figure","description":"Images with captions, in the case-study frame.","category":"data-display","client":false},{"name":"FileInput","label":"File input","slug":"file-input","description":"A click-or-drop upload zone paired with a controlled file list showing size, progress, and per-file errors.","category":"forms","client":true},{"name":"Input","label":"Input","slug":"input","description":"Text input with label, placeholder, left and right icons, helper text, and error states.","category":"forms","client":true},{"name":"Instructions","label":"Instructions","slug":"instructions","description":"Step-by-step guidance with numbered badges, connecting lines, and horizontal layout.","category":"data-display","client":false},{"name":"InterruptCard","label":"Interrupt card","slug":"interrupt-card","description":"A human-in-the-loop checkpoint with a question from the agent and option buttons to decide.","category":"ai","client":false},{"name":"Kbd","label":"Kbd","slug":"kbd","description":"A keyboard key rendered as a keycap, for shortcut hints in menus and prose.","category":"data-display","client":false},{"name":"LinkList","label":"Link list","slug":"link-list","description":"Linked items with logo, label, and subtitle.","category":"data-display","client":false},{"name":"MessageActions","label":"Message actions","slug":"message-actions","description":"An icon-button row for message-level actions like copy, retry, and feedback.","category":"ai","client":false},{"name":"MessageCard","label":"Message card","slug":"message-card","description":"A structured rich-content card embedded in a chat message, with media, title, body, and actions.","category":"ai","client":false},{"name":"Nav","label":"Navigation","slug":"navigation","description":"Desktop top navigation bar with a brand slot, horizontal button group, and optional trailing content.","category":"navigation","client":false},{"name":"NavList","label":"Nav list","slug":"nav-list","description":"Vertical list of navigation links for drawers and menus, with three indent levels and per-row expand toggles.","category":"navigation","client":true},{"name":"Pagination","label":"Pagination","slug":"pagination","description":"Numbered page navigation for long datasets, with ellipses, disabled end arrows, and a compact readout mode.","category":"navigation","client":true},{"name":"Popover","label":"Popover","slug":"popover","description":"Contextual overlay panel with click and hover triggers, positioned relative to its anchor.","category":"overlays","client":true},{"name":"ProgressBar","label":"Progress bar","slug":"progress-bar","description":"Horizontal bar indicating completion progress, with an optional percentage label.","category":"feedback","client":false},{"name":"PromptSuggestions","label":"Prompt suggestions","slug":"prompt-suggestions","description":"A horizontal row of tappable prompt suggestions to start or steer a conversation.","category":"ai","client":false},{"name":"Prose","label":"Prose","slug":"prose","description":"Token-styled typography for rendered markdown and rich agent output.","category":"ai","client":false},{"name":"Quote","label":"Quote","slug":"quote","description":"Blockquotes and pull-quotes with attribution.","category":"data-display","client":false},{"name":"RadioButton","label":"Radio button","slug":"radio-button","description":"Radio button and radio group with vertical and horizontal layouts, animated dot indicator.","category":"forms","client":true},{"name":"Reasoning","label":"Reasoning","slug":"reasoning","description":"A model's thinking, disclosed behind a one-line summary and collapsed once it finishes.","category":"ai","client":true},{"name":"SectionTitle","label":"Section title","slug":"section-title","description":"Heading with a divider line and optional trailing content for organising page sections.","category":"layout","client":false},{"name":"SegmentedControl","label":"Segmented control","slug":"segmented-control","description":"Pill-style toggle between related views with keyboard navigation and icon support.","category":"actions","client":true},{"name":"SelectionCard","label":"Selection card","slug":"selection-card","description":"Large selectable option cards with radio or checkbox indicators for high-visibility choices like settings and onboarding.","category":"data-display","client":true},{"name":"ShaderField","label":"Shader field","slug":"shader-field","description":"An ambient WebGL2 field of soft light sources that sample colour tokens, with a reported fallback status.","category":"layout","client":true},{"name":"Skeleton","label":"Skeleton","slug":"skeleton","description":"Placeholder loading indicators with text, circular, and rectangular variants.","category":"feedback","client":false},{"name":"Slider","label":"Slider","slug":"slider","description":"Range input for selecting a value between a minimum and maximum, in default and compact sizes.","category":"forms","client":true},{"name":"SourceChip","label":"Source chip","slug":"source-chip","description":"A numbered citation pill linking a claim to its source.","category":"ai","client":false},{"name":"Spinner","label":"Spinner","slug":"spinner","description":"Animated circular loading indicator in three sizes and primary, neutral, or inherit variants.","category":"feedback","client":false},{"name":"Stat","label":"Stat","slug":"stat","description":"Headline metrics with labels and trend deltas.","category":"data-display","client":false},{"name":"Swatch","label":"Swatch","slug":"swatch","description":"Clickable colour tile for preset palettes and picker triggers, with a theme-aware selection ring.","category":"forms","client":false},{"name":"Table","label":"Table","slug":"table","description":"Data table with flexible cell content, striped rows, compact sizing, and support for icons, inputs, buttons, and interactive controls.","category":"data-display","client":false},{"name":"Tabs","label":"Tabs","slug":"tabs","description":"Tab navigation with underline indicator, icon support, compact size, and full-width mode.","category":"navigation","client":true},{"name":"Textarea","label":"Textarea","slug":"textarea","description":"Multi-line text input with character counter, resize control, helper text, and error states.","category":"forms","client":true},{"name":"Timeline","label":"Timeline","slug":"timeline","description":"Ordered sequences: histories and steppers.","category":"data-display","client":false},{"name":"Toast","label":"Toast","slug":"toast","description":"Temporary notification with status variants, auto-dismiss, and stacking via ToastProvider.","category":"feedback","client":true},{"name":"ToggleGroup","label":"Toggle group","slug":"toggle-group","description":"A set of two-state buttons that can be toggled on or off, supporting text and icon items.","category":"actions","client":true},{"name":"ToggleSwitch","label":"Toggle switch","slug":"toggle-switch","description":"Binary on/off toggle control with sliding thumb and check indicator, used for settings like theme switching.","category":"forms","client":true},{"name":"ToolCall","label":"Tool call","slug":"tool-call","description":"The record of one tool invocation, with its arguments and result behind a disclosure.","category":"ai","client":true},{"name":"Tooltip","label":"Tooltip","slug":"tooltip","description":"Contextual text label that appears on hover or focus with position and delay options.","category":"overlays","client":true}]`);
2
+ const components = /* @__PURE__ */ JSON.parse(`[{"name":"Accordion","label":"Accordion","slug":"accordion","description":"Collapsible content sections for organising related information.","category":"data-display","client":true},{"name":"AgentPlan","label":"Agent plan","slug":"agent-plan","description":"A collapsible checklist of an agent's task, with live step states and a progress readout.","category":"ai","client":true},{"name":"AgentStatus","label":"Agent status","slug":"agent-status","description":"A dot-matrix indicator and status line reporting what an agent is doing right now.","category":"ai","client":true},{"name":"AiButton","label":"AI button","slug":"ai-button","description":"The AI entry point: icon and label on a transparent field, ringed by a slowly turning gradient and a soft glow.","category":"ai","client":true},{"name":"Alert","label":"Alert","slug":"alert","description":"Contextual feedback with status variants, optional dismiss, and compact sizing.","category":"feedback","client":false},{"name":"AlertDialog","label":"Alert dialog","slug":"alert-dialog","description":"Modal confirmation overlay with title, description, and confirm / cancel actions.","category":"overlays","client":true},{"name":"AppLayout","label":"App layout","slug":"app-layout","description":"Full-page template pairing the collapsible App sidebar with a centred content area.","category":"layout","client":true},{"name":"AppSidebar","label":"App sidebar","slug":"app-sidebar","description":"Collapsible navigation rail with accordion sub-items, category headings, and profile section.","category":"layout","client":true},{"name":"Avatar","label":"Avatar","slug":"avatar","description":"User profile image with initials and icon fallback, status indicator, and multiple sizes.","category":"data-display","client":true},{"name":"Badge","label":"Badge","slug":"badge","description":"Small inline status labels with info, positive, warning, error, and neutral variants.","category":"data-display","client":false},{"name":"Breadcrumb","label":"Breadcrumb","slug":"breadcrumb","description":"Hierarchical navigation trail showing the user's location within the site.","category":"navigation","client":false},{"name":"Button","label":"Button","slug":"button","description":"Primary and secondary button variants in default and compact sizes, with icon support and multiple states.","category":"actions","client":true},{"name":"ButtonGroup","label":"Button group","slug":"button-group","description":"Horizontal and vertical button group layouts for related actions and navigation patterns.","category":"actions","client":false},{"name":"Card","label":"Card","slug":"card","description":"Card components for previews, navigation, and token documentation, from content cards to colour swatches and typography specimens.","category":"data-display","client":true},{"name":"Carousel","label":"Carousel","slug":"carousel","description":"Sliding content viewer with navigation arrows, dot indicators, auto-play, and keyboard support.","category":"data-display","client":true},{"name":"Chart","label":"Chart","slug":"chart","description":"Data visualisation components built on Recharts. Includes bar, line, pie, and radial chart types with design-system token integration.","category":"charts","client":false},{"name":"ChatHeader","label":"Chat header","slug":"chat-header","description":"The top row of a chat surface, with the conversation title and its controls.","category":"ai","client":false},{"name":"ChatMarker","label":"Chat marker","slug":"chat-marker","description":"An inline conversation separator for date breaks and system notes.","category":"ai","client":false},{"name":"ChatMessage","label":"Chat message","slug":"chat-message","description":"A single chat turn with avatar, author, timestamp, and bubble or plain content aligned by role.","category":"ai","client":false},{"name":"ChatThread","label":"Chat thread","slug":"chat-thread","description":"A scrollable conversation column with edge fades, send anchoring, and a subtle scrollbar.","category":"ai","client":true},{"name":"Checkbox","label":"Checkbox","slug":"checkbox","description":"Custom checkbox with check and indeterminate states, keyboard accessible with animated transitions.","category":"forms","client":true},{"name":"Chip","label":"Chip","slug":"chip","description":"Compact pills for attributes, filters, and inline metadata.","category":"data-display","client":false},{"name":"CircularButton","label":"Circular button","slug":"circular-button","description":"Round icon button with primary and secondary variants, default and compact sizes.","category":"actions","client":false},{"name":"CodeBlock","label":"Code block","slug":"code-block","description":"Monospace code with a header and one-click copy.","category":"data-display","client":true},{"name":"ColorPicker","label":"Colour picker","slug":"color-picker","description":"Swatch trigger opening a saturation area, hue and alpha sliders, and a hex field; controlled or uncontrolled.","category":"forms","client":true},{"name":"Combobox","label":"Combobox","slug":"combobox","description":"A filterable select that narrows options as the user types, with multi-select chips, grouping, and async loading.","category":"forms","client":true},{"name":"CommandPalette","label":"Command palette","slug":"command-palette","description":"A modal Cmd+K launcher that searches a grouped command list, with keyboard navigation and shortcut hints.","category":"overlays","client":true},{"name":"Composer","label":"Composer","slug":"composer","description":"An auto-growing message input with send and stop states, an attachment slot, and Enter-to-send.","category":"ai","client":true},{"name":"ContactCard","label":"Contact card","slug":"contact-card","description":"Linked contact method with icon, label, and value.","category":"data-display","client":true},{"name":"ContextMenu","label":"Context menu","slug":"context-menu","description":"Right-click menu at the pointer with groups, sub-menus, and shortcut hints.","category":"overlays","client":true},{"name":"ContributionGraph","label":"Contribution graph","slug":"contribution-graph","description":"A year of activity, one cell per day.","category":"charts","client":false},{"name":"DataTable","label":"Data table","slug":"data-table","description":"The wired table: sorting, search, row selection, and pagination assembled around Table.","category":"data-display","client":true},{"name":"DateInput","label":"Date input","slug":"date-input","description":"Date input with native picker, calendar icon, label, and validation states.","category":"forms","client":true},{"name":"DatePicker","label":"Date picker","slug":"date-picker","description":"Inline calendar with month navigation, day selection, and today indicator.","category":"forms","client":true},{"name":"Dialog","label":"Dialog","slug":"dialog","description":"A general-purpose modal for focused tasks, with sizes, an optional footer, and full focus management.","category":"overlays","client":true},{"name":"Divider","label":"Divider","slug":"divider","description":"A thin rule separating stacked content, with optional inline label and vertical orientation.","category":"layout","client":false},{"name":"DocumentChip","label":"Document chip","slug":"document-chip","description":"A compact file reference with a type icon, name, metadata, and optional remove.","category":"ai","client":false},{"name":"Drawer","label":"Drawer","slug":"drawer","description":"An edge-anchored modal panel that slides in from any side, for filter panels, detail views, and mobile navigation.","category":"overlays","client":true},{"name":"Dropdown","label":"Dropdown","slug":"dropdown","description":"Custom select dropdown with keyboard navigation, disabled options, and error states.","category":"forms","client":true},{"name":"DropdownMenu","label":"Dropdown menu","slug":"dropdown-menu","description":"Contextual menu with sections, sub-menus, keyboard shortcuts, and inset-gap hover styling.","category":"overlays","client":true},{"name":"EmptyState","label":"Empty state","slug":"empty-state","description":"The placeholder for a list, table, or search with nothing to show: icon, headline, guidance, and a next action.","category":"feedback","client":false},{"name":"EntityCard","label":"Entity card","slug":"entity-card","description":"Compact display-only card with a centred icon or image and a label, used in the Icons and Logos galleries.","category":"data-display","client":false},{"name":"EventCalendar","label":"Event calendar","slug":"event-calendar","description":"A month grid with event pills, overflow counts, and month navigation.","category":"data-display","client":true},{"name":"Field","label":"Field","slug":"field","description":"The shared scaffolding for labelled form controls: label, required marker, helper and error text, and the ARIA wiring that ties them together.","category":"forms","client":true},{"name":"Figure","label":"Figure","slug":"figure","description":"Images with captions, in the case-study frame.","category":"data-display","client":false},{"name":"FileInput","label":"File input","slug":"file-input","description":"A click-or-drop upload zone paired with a controlled file list showing size, progress, and per-file errors.","category":"forms","client":true},{"name":"Input","label":"Input","slug":"input","description":"Text input with label, placeholder, left and right icons, helper text, and error states.","category":"forms","client":true},{"name":"Instructions","label":"Instructions","slug":"instructions","description":"Step-by-step guidance with numbered badges, connecting lines, and horizontal layout.","category":"data-display","client":false},{"name":"InterruptCard","label":"Interrupt card","slug":"interrupt-card","description":"A human-in-the-loop checkpoint with a question from the agent and option buttons to decide.","category":"ai","client":false},{"name":"Kbd","label":"Kbd","slug":"kbd","description":"A keyboard key rendered as a keycap, for shortcut hints in menus and prose.","category":"data-display","client":false},{"name":"LinkList","label":"Link list","slug":"link-list","description":"Linked items with logo, label, and subtitle.","category":"data-display","client":false},{"name":"MessageActions","label":"Message actions","slug":"message-actions","description":"An icon-button row for message-level actions like copy, retry, and feedback.","category":"ai","client":false},{"name":"MessageCard","label":"Message card","slug":"message-card","description":"A structured rich-content card embedded in a chat message, with media, title, body, and actions.","category":"ai","client":false},{"name":"ModelPicker","label":"Model picker","slug":"model-picker","description":"A model selector for chat surfaces, with per-model descriptions and an optional effort row.","category":"ai","client":true},{"name":"Nav","label":"Navigation","slug":"navigation","description":"Desktop top navigation bar with a brand slot, horizontal button group, and optional trailing content.","category":"navigation","client":false},{"name":"NavList","label":"Nav list","slug":"nav-list","description":"Vertical list of navigation links for drawers and menus, with three indent levels and per-row expand toggles.","category":"navigation","client":true},{"name":"NotificationCenter","label":"Notification centre","slug":"notification-center","description":"A persistent notification inbox with unread count, filter tabs, and per-item actions.","category":"feedback","client":true},{"name":"Pagination","label":"Pagination","slug":"pagination","description":"Numbered page navigation for long datasets, with ellipses, disabled end arrows, and a compact readout mode.","category":"navigation","client":true},{"name":"Popover","label":"Popover","slug":"popover","description":"Contextual overlay panel with click and hover triggers, positioned relative to its anchor.","category":"overlays","client":true},{"name":"ProgressBar","label":"Progress bar","slug":"progress-bar","description":"Horizontal bar indicating completion progress, with an optional percentage label.","category":"feedback","client":false},{"name":"PromptSuggestions","label":"Prompt suggestions","slug":"prompt-suggestions","description":"A horizontal row of tappable prompt suggestions to start or steer a conversation.","category":"ai","client":false},{"name":"Prose","label":"Prose","slug":"prose","description":"Token-styled typography for rendered markdown and rich agent output.","category":"ai","client":false},{"name":"Quote","label":"Quote","slug":"quote","description":"Blockquotes and pull-quotes with attribution.","category":"data-display","client":false},{"name":"RadioButton","label":"Radio button","slug":"radio-button","description":"Radio button and radio group with vertical and horizontal layouts, animated dot indicator.","category":"forms","client":true},{"name":"Reasoning","label":"Reasoning","slug":"reasoning","description":"A model's thinking, disclosed behind a one-line summary and collapsed once it finishes.","category":"ai","client":true},{"name":"SectionTitle","label":"Section title","slug":"section-title","description":"Heading with a divider line and optional trailing content for organising page sections.","category":"layout","client":false},{"name":"SegmentedControl","label":"Segmented control","slug":"segmented-control","description":"Pill-style toggle between related views with keyboard navigation and icon support.","category":"actions","client":true},{"name":"SelectionCard","label":"Selection card","slug":"selection-card","description":"Large selectable option cards with radio or checkbox indicators for high-visibility choices like settings and onboarding.","category":"data-display","client":true},{"name":"ShaderField","label":"Shader field","slug":"shader-field","description":"An ambient WebGL2 field of soft light sources that sample colour tokens, with a reported fallback status.","category":"layout","client":true},{"name":"Skeleton","label":"Skeleton","slug":"skeleton","description":"Placeholder loading indicators with text, circular, and rectangular variants.","category":"feedback","client":false},{"name":"Slider","label":"Slider","slug":"slider","description":"Range input for selecting a value between a minimum and maximum, in default and compact sizes.","category":"forms","client":true},{"name":"SourceChip","label":"Source chip","slug":"source-chip","description":"A numbered citation pill linking a claim to its source.","category":"ai","client":false},{"name":"Spinner","label":"Spinner","slug":"spinner","description":"Animated circular loading indicator in three sizes and primary, neutral, or inherit variants.","category":"feedback","client":false},{"name":"Stat","label":"Stat","slug":"stat","description":"Headline metrics with labels and trend deltas.","category":"data-display","client":false},{"name":"Swatch","label":"Swatch","slug":"swatch","description":"Clickable colour tile for preset palettes and picker triggers, with a theme-aware selection ring.","category":"forms","client":false},{"name":"Table","label":"Table","slug":"table","description":"Data table with flexible cell content, striped rows, compact sizing, and support for icons, inputs, buttons, and interactive controls.","category":"data-display","client":false},{"name":"Tabs","label":"Tabs","slug":"tabs","description":"Tab navigation with underline indicator, icon support, compact size, and full-width mode.","category":"navigation","client":true},{"name":"Textarea","label":"Textarea","slug":"textarea","description":"Multi-line text input with character counter, resize control, helper text, and error states.","category":"forms","client":true},{"name":"Timeline","label":"Timeline","slug":"timeline","description":"Ordered sequences: histories and steppers.","category":"data-display","client":false},{"name":"Toast","label":"Toast","slug":"toast","description":"Temporary notification with status variants, auto-dismiss, and stacking via ToastProvider.","category":"feedback","client":true},{"name":"ToggleGroup","label":"Toggle group","slug":"toggle-group","description":"A set of two-state buttons that can be toggled on or off, supporting text and icon items.","category":"actions","client":true},{"name":"ToggleSwitch","label":"Toggle switch","slug":"toggle-switch","description":"Binary on/off toggle control with sliding thumb and check indicator, used for settings like theme switching.","category":"forms","client":true},{"name":"ToolCall","label":"Tool call","slug":"tool-call","description":"The record of one tool invocation, with its arguments and result behind a disclosure.","category":"ai","client":true},{"name":"Tooltip","label":"Tooltip","slug":"tooltip","description":"Contextual text label that appears on hover or focus with position and delay options.","category":"overlays","client":true}]`);
3
3
  const registry = {
4
4
  categories,
5
5
  components
package/index.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * (validate-registry chain). recharts-backed modules live in charts.ts, not here — recharts is an optional peer.
5
5
  */
6
6
  export * from './components/Accordion/Accordion';
7
+ export * from './components/AgentPlan/AgentPlan';
7
8
  export * from './components/AgentStatus/AgentStatus';
8
9
  export * from './components/AgentStatus/AgentStatusPatterns';
9
10
  export * from './components/AiButton/AiButton';
@@ -33,6 +34,7 @@ export * from './components/Composer/Composer';
33
34
  export * from './components/ContactCard/ContactCard';
34
35
  export * from './components/ContextMenu/ContextMenu';
35
36
  export * from './components/ContributionGraph/ContributionGraph';
37
+ export * from './components/DataTable/DataTable';
36
38
  export * from './components/DateInput/DateInput';
37
39
  export * from './components/DatePicker/DatePicker';
38
40
  export * from './components/Dialog/Dialog';
@@ -43,6 +45,7 @@ export * from './components/Dropdown/Dropdown';
43
45
  export * from './components/DropdownMenu/DropdownMenu';
44
46
  export * from './components/EmptyState/EmptyState';
45
47
  export * from './components/EntityCard/EntityCard';
48
+ export * from './components/EventCalendar/EventCalendar';
46
49
  export * from './components/Field/Field';
47
50
  export * from './components/Field/FieldContext';
48
51
  export * from './components/Figure/Figure';
@@ -54,8 +57,10 @@ export * from './components/Kbd/Kbd';
54
57
  export * from './components/LinkList/LinkList';
55
58
  export * from './components/MessageActions/MessageActions';
56
59
  export * from './components/MessageCard/MessageCard';
60
+ export * from './components/ModelPicker/ModelPicker';
57
61
  export * from './components/Nav/Nav';
58
62
  export * from './components/NavList/NavList';
63
+ export * from './components/NotificationCenter/NotificationCenter';
59
64
  export * from './components/Pagination/Pagination';
60
65
  export * from './components/Popover/Popover';
61
66
  export * from './components/ProgressBar/ProgressBar';
package/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Accordion } from "./components/Accordion/Accordion.js";
2
+ import { AgentPlan } from "./components/AgentPlan/AgentPlan.js";
2
3
  import { AgentStatus } from "./components/AgentStatus/AgentStatus.js";
3
4
  import { agentStatusPatterns } from "./components/AgentStatus/AgentStatusPatterns.js";
4
5
  import { AiButton } from "./components/AiButton/AiButton.js";
@@ -28,6 +29,7 @@ import { Composer } from "./components/Composer/Composer.js";
28
29
  import { ContactCard } from "./components/ContactCard/ContactCard.js";
29
30
  import { ContextMenu } from "./components/ContextMenu/ContextMenu.js";
30
31
  import { ContributionGraph } from "./components/ContributionGraph/ContributionGraph.js";
32
+ import { DataTable } from "./components/DataTable/DataTable.js";
31
33
  import { DateInput } from "./components/DateInput/DateInput.js";
32
34
  import { DatePicker } from "./components/DatePicker/DatePicker.js";
33
35
  import { Dialog } from "./components/Dialog/Dialog.js";
@@ -38,6 +40,7 @@ import { Dropdown } from "./components/Dropdown/Dropdown.js";
38
40
  import { DropdownMenu } from "./components/DropdownMenu/DropdownMenu.js";
39
41
  import { EmptyState } from "./components/EmptyState/EmptyState.js";
40
42
  import { EntityCard } from "./components/EntityCard/EntityCard.js";
43
+ import { EventCalendar } from "./components/EventCalendar/EventCalendar.js";
41
44
  import { Field } from "./components/Field/Field.js";
42
45
  import { FieldContext, useField } from "./components/Field/FieldContext.js";
43
46
  import { Figure } from "./components/Figure/Figure.js";
@@ -49,8 +52,10 @@ import { Kbd } from "./components/Kbd/Kbd.js";
49
52
  import { LinkList } from "./components/LinkList/LinkList.js";
50
53
  import { MessageActions } from "./components/MessageActions/MessageActions.js";
51
54
  import { MessageCard } from "./components/MessageCard/MessageCard.js";
55
+ import { ModelPicker } from "./components/ModelPicker/ModelPicker.js";
52
56
  import { Nav } from "./components/Nav/Nav.js";
53
57
  import { NavList } from "./components/NavList/NavList.js";
58
+ import { NotificationCenter, NotificationItem } from "./components/NotificationCenter/NotificationCenter.js";
54
59
  import { Pagination } from "./components/Pagination/Pagination.js";
55
60
  import { Popover } from "./components/Popover/Popover.js";
56
61
  import { ProgressBar } from "./components/ProgressBar/ProgressBar.js";
@@ -84,6 +89,7 @@ import { BLOB_COUNT } from "./components/ShaderField/field.glsl.js";
84
89
  import { DEFAULT_SHADER_BLOBS, DEFAULT_SHADER_PARAMS, useShaderField } from "./components/ShaderField/useShaderField.js";
85
90
  export {
86
91
  Accordion,
92
+ AgentPlan,
87
93
  AgentStatus,
88
94
  AiButton,
89
95
  Alert,
@@ -117,6 +123,7 @@ export {
117
123
  ContributionGraph,
118
124
  DEFAULT_SHADER_BLOBS,
119
125
  DEFAULT_SHADER_PARAMS,
126
+ DataTable,
120
127
  DateInput,
121
128
  DatePicker,
122
129
  Dialog,
@@ -127,6 +134,7 @@ export {
127
134
  DropdownMenu,
128
135
  EmptyState,
129
136
  EntityCard,
137
+ EventCalendar,
130
138
  Field,
131
139
  FieldContext,
132
140
  Figure,
@@ -138,8 +146,11 @@ export {
138
146
  LinkList,
139
147
  MessageActions,
140
148
  MessageCard,
149
+ ModelPicker,
141
150
  Nav,
142
151
  NavList,
152
+ NotificationCenter,
153
+ NotificationItem,
143
154
  Pagination,
144
155
  Popover,
145
156
  ProgressBar,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robr0/design-system",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "An AI-ready React design system: accessible components on composable tokens, light/dark theming, and CSS-variable overrides.",
5
5
  "license": "MIT",
6
6
  "repository": {