@robr0/design-system 0.7.0 → 0.9.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 (109) hide show
  1. package/README.md +4 -4
  2. package/components/Accordion/Accordion.js +1 -0
  3. package/components/AgentPlan/AgentPlan.css +231 -0
  4. package/components/AgentPlan/AgentPlan.d.ts +41 -0
  5. package/components/AgentPlan/AgentPlan.js +100 -0
  6. package/components/AgentStatus/AgentStatus.js +1 -0
  7. package/components/AiButton/AiButton.js +1 -0
  8. package/components/AlertDialog/AlertDialog.js +1 -0
  9. package/components/AnchorNav/AnchorNav.css +63 -0
  10. package/components/AnchorNav/AnchorNav.d.ts +33 -0
  11. package/components/AnchorNav/AnchorNav.js +82 -0
  12. package/components/AppLayout/AppLayout.js +1 -0
  13. package/components/AppSidebar/AppSidebar.js +1 -0
  14. package/components/Avatar/Avatar.js +1 -0
  15. package/components/Button/Button.js +1 -0
  16. package/components/Card/Card.js +1 -0
  17. package/components/Carousel/Carousel.js +1 -0
  18. package/components/ChatThread/ChatThread.js +1 -0
  19. package/components/Checkbox/Checkbox.js +1 -0
  20. package/components/CodeBlock/CodeBlock.js +1 -0
  21. package/components/CodeDiff/CodeDiff.css +175 -0
  22. package/components/CodeDiff/CodeDiff.d.ts +58 -0
  23. package/components/CodeDiff/CodeDiff.js +114 -0
  24. package/components/ColorPicker/ColorPicker.js +1 -0
  25. package/components/Combobox/Combobox.js +1 -0
  26. package/components/CommandPalette/CommandPalette.js +1 -0
  27. package/components/Composer/Composer.js +1 -0
  28. package/components/ContactCard/ContactCard.js +1 -0
  29. package/components/ContextMenu/ContextMenu.js +1 -0
  30. package/components/DataTable/DataTable.css +105 -0
  31. package/components/DataTable/DataTable.d.ts +81 -0
  32. package/components/DataTable/DataTable.js +236 -0
  33. package/components/DateInput/DateInput.js +1 -0
  34. package/components/DatePicker/DatePicker.js +1 -0
  35. package/components/Dialog/Dialog.js +1 -0
  36. package/components/Drawer/Drawer.js +1 -0
  37. package/components/Dropdown/Dropdown.js +1 -0
  38. package/components/DropdownMenu/DropdownMenu.css +1 -1
  39. package/components/DropdownMenu/DropdownMenu.js +1 -0
  40. package/components/EventCalendar/EventCalendar.css +328 -0
  41. package/components/EventCalendar/EventCalendar.d.ts +51 -0
  42. package/components/EventCalendar/EventCalendar.js +214 -0
  43. package/components/Field/Field.js +1 -0
  44. package/components/Field/FieldContext.js +1 -0
  45. package/components/FileInput/FileInput.js +1 -0
  46. package/components/Input/Input.js +1 -0
  47. package/components/InterruptCard/InterruptCard.css +11 -21
  48. package/components/InterruptCard/InterruptCard.d.ts +5 -6
  49. package/components/MessageCard/MessageCard.css +1 -2
  50. package/components/ModelPicker/ModelPicker.css +240 -0
  51. package/components/ModelPicker/ModelPicker.d.ts +60 -0
  52. package/components/ModelPicker/ModelPicker.js +218 -0
  53. package/components/NavList/NavList.js +1 -0
  54. package/components/NotificationCenter/NotificationCenter.css +262 -0
  55. package/components/NotificationCenter/NotificationCenter.d.ts +72 -0
  56. package/components/NotificationCenter/NotificationCenter.js +129 -0
  57. package/components/NumberInput/NumberInput.css +154 -0
  58. package/components/NumberInput/NumberInput.d.ts +44 -0
  59. package/components/NumberInput/NumberInput.js +147 -0
  60. package/components/Pagination/Pagination.js +1 -0
  61. package/components/PinInput/PinInput.css +92 -0
  62. package/components/PinInput/PinInput.d.ts +46 -0
  63. package/components/PinInput/PinInput.js +161 -0
  64. package/components/Popover/Popover.css +5 -6
  65. package/components/Popover/Popover.js +1 -0
  66. package/components/RadioButton/RadioButton.js +1 -0
  67. package/components/Reasoning/Reasoning.js +1 -0
  68. package/components/SegmentedControl/SegmentedControl.js +1 -0
  69. package/components/SelectionCard/SelectionCard.js +1 -0
  70. package/components/ShaderField/ShaderField.js +1 -0
  71. package/components/ShaderField/field.glsl.d.ts +11 -5
  72. package/components/ShaderField/field.glsl.js +4 -3
  73. package/components/ShaderField/useShaderField.d.ts +19 -2
  74. package/components/ShaderField/useShaderField.js +18 -3
  75. package/components/Slider/Slider.js +1 -0
  76. package/components/Sparkline/Sparkline.css +52 -0
  77. package/components/Sparkline/Sparkline.d.ts +49 -0
  78. package/components/Sparkline/Sparkline.js +86 -0
  79. package/components/Stepper/Stepper.css +207 -0
  80. package/components/Stepper/Stepper.d.ts +41 -0
  81. package/components/Stepper/Stepper.js +61 -0
  82. package/components/Tabs/Tabs.js +1 -0
  83. package/components/TagInput/TagInput.css +160 -0
  84. package/components/TagInput/TagInput.d.ts +43 -0
  85. package/components/TagInput/TagInput.js +137 -0
  86. package/components/Textarea/Textarea.js +1 -0
  87. package/components/TimePicker/TimePicker.css +230 -0
  88. package/components/TimePicker/TimePicker.d.ts +46 -0
  89. package/components/TimePicker/TimePicker.js +246 -0
  90. package/components/Timeline/Timeline.css +17 -0
  91. package/components/Timeline/Timeline.d.ts +2 -0
  92. package/components/Timeline/Timeline.js +1 -0
  93. package/components/Toast/Toast.js +1 -0
  94. package/components/ToggleGroup/ToggleGroup.js +1 -0
  95. package/components/ToggleSwitch/ToggleSwitch.js +1 -0
  96. package/components/ToolCall/ToolCall.css +7 -11
  97. package/components/ToolCall/ToolCall.js +1 -0
  98. package/components/Tooltip/Tooltip.js +1 -0
  99. package/components/TreeView/TreeView.css +119 -0
  100. package/components/TreeView/TreeView.d.ts +45 -0
  101. package/components/TreeView/TreeView.js +232 -0
  102. package/components/registry.d.ts +22 -6
  103. package/components/registry.js +3 -1
  104. package/components/registry.json +246 -20
  105. package/components/registry.json.d.ts +246 -20
  106. package/components/registry.json.js +2 -2
  107. package/index.d.ts +16 -2
  108. package/index.js +32 -1
  109. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx, jsxs } from "react/jsx-runtime";
2
3
  import { useRef } from "react";
3
4
  import "./SegmentedControl.css";
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx, jsxs } from "react/jsx-runtime";
2
3
  import React from "react";
3
4
  import "./SelectionCard.css";
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx } from "react/jsx-runtime";
2
3
  import React, { useRef, useMemo, useState, useEffect } from "react";
3
4
  import { DEFAULT_SHADER_BLOBS, DEFAULT_SHADER_PARAMS, useShaderField } from "./useShaderField.js";
@@ -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;
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { useState, useRef, useEffect } from "react";
2
3
  import { BLOB_COUNT, vertexSource, fragmentSource } from "./field.glsl.js";
3
4
  const DEFAULT_SHADER_PARAMS = {
@@ -7,7 +8,8 @@ const DEFAULT_SHADER_PARAMS = {
7
8
  speed: 2,
8
9
  grain: 0.1,
9
10
  streak: 0.4,
10
- react: 0
11
+ react: 0,
12
+ crop: 0.5
11
13
  };
12
14
  const DEFAULT_SHADER_BLOBS = [
13
15
  { token: "--color-core-accent-gold", size: 0.45, cx: 0.175, cy: 0.125, period: 18, phase: 0, weight: 1 },
@@ -20,6 +22,11 @@ const DEFAULT_SHADER_BLOBS = [
20
22
  { token: "--color-core-ui-secondary", size: 0.75, cx: -0.125, cy: -0.025, period: 20, phase: 0.7, weight: 1 }
21
23
  ];
22
24
  const RENDER_SCALE = 0.5;
25
+ const REFERENCE_WIDTH = 1440;
26
+ function fieldUnit(cssWidth, crop) {
27
+ if (cssWidth <= 0) return 1;
28
+ return cssWidth * Math.pow(Math.max(REFERENCE_WIDTH / cssWidth, 1), crop);
29
+ }
23
30
  const FRAME_MS = 1e3 / 30;
24
31
  const FRAME_MS_ACTIVE = 1e3 / 60;
25
32
  const WAKE_IDLE = 0.015;
@@ -113,6 +120,7 @@ function useShaderField(canvasRef, params, enabled = true) {
113
120
  loc = {};
114
121
  for (const name of [
115
122
  "u_resolution",
123
+ "u_unit",
116
124
  "u_time",
117
125
  "u_color[0]",
118
126
  "u_blob[0]",
@@ -176,10 +184,12 @@ function useShaderField(canvasRef, params, enabled = true) {
176
184
  colorTarget.set(colorCurrent);
177
185
  let bufWidth = 0;
178
186
  let bufHeight = 0;
187
+ let cssWidth = 0;
179
188
  function resize() {
180
189
  if (!gl) return;
181
190
  const c = canvas;
182
191
  const dpr = window.devicePixelRatio || 1;
192
+ cssWidth = c.clientWidth;
183
193
  const w = Math.max(1, Math.round(c.clientWidth * dpr * RENDER_SCALE));
184
194
  const h = Math.max(1, Math.round(c.clientHeight * dpr * RENDER_SCALE));
185
195
  if (w === bufWidth && h === bufHeight) return;
@@ -200,8 +210,9 @@ function useShaderField(canvasRef, params, enabled = true) {
200
210
  const c = canvas;
201
211
  const rect = c.getBoundingClientRect();
202
212
  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;
213
+ const unit = fieldUnit(rect.width, paramsRef.current.crop);
214
+ const x = (e.clientX - rect.left - rect.width / 2) / unit;
215
+ const y = (e.clientY - rect.top - rect.height / 2) / unit;
205
216
  if (mouseTarget.x < 5) {
206
217
  moveAccum += Math.hypot(x - mouseTarget.x, y - mouseTarget.y);
207
218
  }
@@ -234,6 +245,10 @@ function useShaderField(canvasRef, params, enabled = true) {
234
245
  }
235
246
  const p = paramsRef.current;
236
247
  gl.uniform3fv(loc["u_color[0]"], colorCurrent);
248
+ gl.uniform1f(
249
+ loc["u_unit"],
250
+ bufWidth * (fieldUnit(cssWidth, p.crop) / Math.max(cssWidth, 1))
251
+ );
237
252
  gl.uniform1f(loc["u_time"], fieldTime);
238
253
  gl.uniform1f(loc["u_intensity"], p.intensity);
239
254
  gl.uniform1f(loc["u_warp"], p.warp);
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx } from "react/jsx-runtime";
2
3
  import React from "react";
3
4
  import "./Slider.css";
@@ -0,0 +1,52 @@
1
+ /* ============================================
2
+ SPARKLINE COMPONENT
3
+ Inline trend line for stats and table cells.
4
+ Colour flows through `color` — the line, fill
5
+ and dot all draw with currentColor, so a tone
6
+ is one custom property swap.
7
+ ============================================ */
8
+
9
+ /* Base */
10
+
11
+ .ds-sparkline {
12
+ display: inline-block;
13
+ vertical-align: middle;
14
+ flex-shrink: 0;
15
+ color: var(--color-action-primary-bg);
16
+ }
17
+
18
+ /* Parts */
19
+
20
+ .ds-sparkline__line {
21
+ fill: none;
22
+ stroke: currentColor;
23
+ stroke-linecap: round;
24
+ stroke-linejoin: round;
25
+ }
26
+
27
+ .ds-sparkline__fill {
28
+ fill: currentColor;
29
+ opacity: 0.15;
30
+ }
31
+
32
+ .ds-sparkline__dot {
33
+ fill: currentColor;
34
+ }
35
+
36
+ /* Tones */
37
+
38
+ .ds-sparkline--accent {
39
+ color: var(--color-action-primary-bg);
40
+ }
41
+
42
+ .ds-sparkline--positive {
43
+ color: var(--color-status-positive-text);
44
+ }
45
+
46
+ .ds-sparkline--negative {
47
+ color: var(--color-status-error-text);
48
+ }
49
+
50
+ .ds-sparkline--neutral {
51
+ color: var(--color-text-secondary);
52
+ }
@@ -0,0 +1,49 @@
1
+ import { default as React } from 'react';
2
+ /** Props owned by Sparkline itself — everything else falls through to the SVG node. */
3
+ type SparklineOwnProps = {
4
+ /** The series to plot, in order. Values are normalised into the viewBox. */
5
+ data: number[];
6
+ /** Rendering treatment — a bare line, or a line with a soft fill underneath. */
7
+ variant?: 'line' | 'area';
8
+ /**
9
+ * Colour role for the line and end dot. `accent` follows the chart palette's
10
+ * lead colour; `positive`/`negative` map to the status text tokens; `neutral`
11
+ * recedes to secondary text.
12
+ */
13
+ tone?: 'accent' | 'positive' | 'negative' | 'neutral';
14
+ /** Marks the final point with a small dot. */
15
+ showDot?: boolean;
16
+ /** Line thickness in viewBox units — an SVG geometry attribute, not a token. */
17
+ strokeWidth?: number;
18
+ /**
19
+ * ViewBox width. Also the default rendered width; the SVG scales to its
20
+ * container when sized externally via CSS. Intentionally shadows the native
21
+ * SVG `width` attribute, which it sets.
22
+ */
23
+ width?: number;
24
+ /**
25
+ * ViewBox height. Also the default rendered height; the SVG scales to its
26
+ * container when sized externally via CSS. Intentionally shadows the native
27
+ * SVG `height` attribute, which it sets.
28
+ */
29
+ height?: number;
30
+ /**
31
+ * Accessible description of the trend, e.g. "Revenue, trending up". When
32
+ * omitted the SVG is decorative and hidden from assistive technology.
33
+ */
34
+ label?: string;
35
+ /** Additional CSS classes */
36
+ className?: string;
37
+ };
38
+ export interface SparklineProps extends SparklineOwnProps, Omit<React.ComponentPropsWithoutRef<'svg'>, keyof SparklineOwnProps | 'children'> {
39
+ }
40
+ /**
41
+ * Sparkline — an inline trend line for stats and table cells, drawn without
42
+ * axes or chrome. Pure SVG computed from props: no charting library, no hooks,
43
+ * no dependencies, so it renders from a Server Component and costs nothing in
44
+ * a dense table. Degenerate data never breaks the path: an all-equal series
45
+ * renders a horizontal midline, and fewer than two points render just the
46
+ * end dot, or nothing.
47
+ */
48
+ export declare const Sparkline: React.ForwardRefExoticComponent<SparklineProps & React.RefAttributes<SVGSVGElement>>;
49
+ export {};
@@ -0,0 +1,86 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import "./Sparkline.css";
4
+ const round = (n) => Math.round(n * 100) / 100;
5
+ const Sparkline = React.forwardRef(
6
+ ({
7
+ data,
8
+ variant = "line",
9
+ tone = "accent",
10
+ showDot = true,
11
+ strokeWidth = 2,
12
+ width = 120,
13
+ height = 32,
14
+ label,
15
+ className = "",
16
+ ...rest
17
+ }, ref) => {
18
+ const baseClass = "ds-sparkline";
19
+ const classes = [
20
+ baseClass,
21
+ `${baseClass}--${variant}`,
22
+ `${baseClass}--${tone}`,
23
+ className
24
+ ].filter(Boolean).join(" ");
25
+ const dotRadius = strokeWidth * 1.5;
26
+ const pad = dotRadius + strokeWidth / 2;
27
+ const innerWidth = Math.max(width - pad * 2, 0);
28
+ const innerHeight = Math.max(height - pad * 2, 0);
29
+ const min = Math.min(...data);
30
+ const max = Math.max(...data);
31
+ const range = max - min;
32
+ const points = data.map((value, i) => ({
33
+ x: round(
34
+ data.length < 2 ? pad + innerWidth : pad + i / (data.length - 1) * innerWidth
35
+ ),
36
+ // A flat series (range 0) draws a horizontal midline instead of dividing by zero.
37
+ y: round(
38
+ range === 0 ? pad + innerHeight / 2 : pad + (1 - (value - min) / range) * innerHeight
39
+ )
40
+ }));
41
+ const lastPoint = points.length > 0 ? points[points.length - 1] : void 0;
42
+ const hasLine = points.length >= 2;
43
+ const linePath = hasLine ? `M ${points.map((p) => `${p.x} ${p.y}`).join(" L ")}` : void 0;
44
+ const baseline = round(height - pad);
45
+ const areaPath = hasLine && variant === "area" ? `${linePath} L ${points[points.length - 1].x} ${baseline} L ${points[0].x} ${baseline} Z` : void 0;
46
+ return /* @__PURE__ */ jsxs(
47
+ "svg",
48
+ {
49
+ ...rest,
50
+ ref,
51
+ className: classes,
52
+ viewBox: `0 0 ${width} ${height}`,
53
+ width,
54
+ height,
55
+ xmlns: "http://www.w3.org/2000/svg",
56
+ role: label ? "img" : void 0,
57
+ "aria-label": label,
58
+ "aria-hidden": label ? void 0 : true,
59
+ children: [
60
+ areaPath && /* @__PURE__ */ jsx("path", { className: `${baseClass}__fill`, d: areaPath }),
61
+ linePath && /* @__PURE__ */ jsx(
62
+ "path",
63
+ {
64
+ className: `${baseClass}__line`,
65
+ d: linePath,
66
+ strokeWidth
67
+ }
68
+ ),
69
+ showDot && lastPoint && /* @__PURE__ */ jsx(
70
+ "circle",
71
+ {
72
+ className: `${baseClass}__dot`,
73
+ cx: lastPoint.x,
74
+ cy: lastPoint.y,
75
+ r: dotRadius
76
+ }
77
+ )
78
+ ]
79
+ }
80
+ );
81
+ }
82
+ );
83
+ Sparkline.displayName = "Sparkline";
84
+ export {
85
+ Sparkline
86
+ };
@@ -0,0 +1,207 @@
1
+ /* ============================================
2
+ STEPPER COMPONENT
3
+ Step-by-step progress indicator for wizards
4
+ and multi-stage flows
5
+ ============================================ */
6
+
7
+ /* Base */
8
+
9
+ .ds-stepper {
10
+ display: flex;
11
+ align-items: flex-start;
12
+ list-style: none;
13
+ margin: 0;
14
+ padding: 0;
15
+ width: 100%;
16
+ }
17
+
18
+ .ds-stepper__step {
19
+ position: relative;
20
+ flex: 1 1 0;
21
+ min-width: 0;
22
+ display: flex;
23
+ justify-content: center;
24
+ }
25
+
26
+ /* Shared inner layout — a <button> when the step is clickable,
27
+ a <span> otherwise */
28
+ .ds-stepper__content {
29
+ display: flex;
30
+ flex-direction: column;
31
+ align-items: center;
32
+ gap: var(--gap-sm); /* 8px */
33
+ padding: 0;
34
+ border: none;
35
+ background-color: transparent;
36
+ font-family: inherit;
37
+ text-align: center;
38
+ color: inherit;
39
+ min-width: 0;
40
+ }
41
+
42
+ button.ds-stepper__content {
43
+ cursor: pointer;
44
+ }
45
+
46
+ button.ds-stepper__content:focus-visible {
47
+ outline: 2px solid var(--color-action-primary-bg);
48
+ outline-offset: 2px;
49
+ border-radius: var(--radius-sm);
50
+ }
51
+
52
+ /* ============================================
53
+ INDICATOR
54
+ 32px circle — check for complete, number
55
+ for active and upcoming
56
+ ============================================ */
57
+
58
+ .ds-stepper__indicator {
59
+ --icon-size: var(--icon-size-sm);
60
+ display: inline-flex;
61
+ align-items: center;
62
+ justify-content: center;
63
+ width: 32px;
64
+ height: 32px;
65
+ flex-shrink: 0;
66
+ border: var(--border-xs) solid transparent;
67
+ border-radius: var(--radius-full);
68
+ font-family: var(--font-paragraph-sm-em-family);
69
+ font-size: var(--font-paragraph-sm-em-size);
70
+ font-weight: var(--font-paragraph-sm-em-weight);
71
+ line-height: var(--font-paragraph-sm-em-line-height);
72
+ letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
73
+ transition:
74
+ background-color var(--motion-duration-base) var(--motion-ease-standard),
75
+ border-color var(--motion-duration-base) var(--motion-ease-standard),
76
+ color var(--motion-duration-base) var(--motion-ease-standard);
77
+ }
78
+
79
+ /* States */
80
+
81
+ .ds-stepper__step--complete .ds-stepper__indicator {
82
+ border-color: var(--color-action-primary-border-secondary);
83
+ color: var(--color-action-primary-text-tertiary);
84
+ }
85
+
86
+ .ds-stepper__step--active .ds-stepper__indicator {
87
+ background-color: var(--color-action-primary-bg);
88
+ border-color: var(--color-action-primary-bg);
89
+ color: var(--color-action-primary-text);
90
+ }
91
+
92
+ .ds-stepper__step--upcoming .ds-stepper__indicator {
93
+ border-color: var(--color-bg-container-border);
94
+ color: var(--color-text-tertiary);
95
+ }
96
+
97
+ button.ds-stepper__content:hover .ds-stepper__indicator {
98
+ background-color: var(--color-action-passive-bg-hover);
99
+ }
100
+
101
+ .ds-stepper__step--active button.ds-stepper__content:hover .ds-stepper__indicator {
102
+ background-color: var(--color-action-primary-bg-hover);
103
+ }
104
+
105
+ /* ============================================
106
+ LABEL + DESCRIPTION
107
+ ============================================ */
108
+
109
+ .ds-stepper__text {
110
+ display: flex;
111
+ flex-direction: column;
112
+ gap: var(--gap-xxs); /* 2px */
113
+ min-width: 0;
114
+ }
115
+
116
+ .ds-stepper__label {
117
+ font-family: var(--font-paragraph-sm-em-family);
118
+ font-size: var(--font-paragraph-sm-em-size);
119
+ font-weight: var(--font-paragraph-sm-em-weight);
120
+ line-height: var(--font-paragraph-sm-em-line-height);
121
+ letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
122
+ color: var(--color-text-secondary);
123
+ transition: color var(--motion-duration-base) var(--motion-ease-standard);
124
+ }
125
+
126
+ .ds-stepper__step--active .ds-stepper__label {
127
+ color: var(--color-text-primary);
128
+ }
129
+
130
+ .ds-stepper__step--upcoming .ds-stepper__label {
131
+ color: var(--color-text-tertiary);
132
+ }
133
+
134
+ button.ds-stepper__content:hover .ds-stepper__label {
135
+ color: var(--color-text-primary);
136
+ }
137
+
138
+ .ds-stepper__description {
139
+ font-family: var(--font-paragraph-sm-family);
140
+ font-size: var(--font-paragraph-sm-size);
141
+ font-weight: var(--font-paragraph-sm-weight);
142
+ line-height: var(--font-paragraph-sm-line-height);
143
+ letter-spacing: var(--font-paragraph-sm-letter-spacing);
144
+ color: var(--color-text-tertiary);
145
+ }
146
+
147
+ /* ============================================
148
+ CONNECTOR
149
+ Drawn between an indicator and the next;
150
+ turns action-coloured behind completed steps
151
+ ============================================ */
152
+
153
+ .ds-stepper__connector {
154
+ position: absolute;
155
+ top: 16px; /* vertical centre of the 32px indicator */
156
+ left: calc(50% + 24px);
157
+ right: calc(-50% + 24px);
158
+ height: var(--border-md);
159
+ transform: translateY(-50%);
160
+ background-color: var(--color-divider);
161
+ transition: background-color var(--motion-duration-base) var(--motion-ease-standard);
162
+ }
163
+
164
+ .ds-stepper__step--complete .ds-stepper__connector {
165
+ background-color: var(--color-action-primary-border-secondary);
166
+ }
167
+
168
+ /* ============================================
169
+ VERTICAL
170
+ ============================================ */
171
+
172
+ .ds-stepper--vertical {
173
+ flex-direction: column;
174
+ align-items: stretch;
175
+ }
176
+
177
+ .ds-stepper--vertical .ds-stepper__step {
178
+ flex: none;
179
+ justify-content: flex-start;
180
+ padding-bottom: var(--gap-lg); /* 20px */
181
+ }
182
+
183
+ .ds-stepper--vertical .ds-stepper__step:last-child {
184
+ padding-bottom: 0;
185
+ }
186
+
187
+ .ds-stepper--vertical .ds-stepper__content {
188
+ flex-direction: row;
189
+ align-items: flex-start;
190
+ gap: var(--gap-sm-md); /* 12px */
191
+ text-align: left;
192
+ }
193
+
194
+ /* Centre the first label line on the 32px indicator */
195
+ .ds-stepper--vertical .ds-stepper__text {
196
+ padding-top: calc((32px - var(--font-paragraph-sm-em-line-height)) / 2);
197
+ }
198
+
199
+ .ds-stepper--vertical .ds-stepper__connector {
200
+ top: calc(32px + var(--gap-xs));
201
+ bottom: var(--gap-xs);
202
+ left: 16px; /* horizontal centre of the 32px indicator */
203
+ right: auto;
204
+ width: var(--border-md);
205
+ height: auto;
206
+ transform: translateX(-50%);
207
+ }
@@ -0,0 +1,41 @@
1
+ import { default as React } from 'react';
2
+ /** A single step in a Stepper flow. */
3
+ export type StepperStep = {
4
+ /** Step label shown beside or below the indicator */
5
+ label: string;
6
+ /** Optional supporting line rendered under the label */
7
+ description?: string;
8
+ };
9
+ /** Props owned by Stepper itself — everything else falls through to the `<ol>`. */
10
+ type StepperOwnProps = {
11
+ /** Ordered list of steps in the flow */
12
+ steps: StepperStep[];
13
+ /**
14
+ * Index of the active step. Steps before it render as complete (check
15
+ * icon), steps after it as upcoming (step number). Pass `steps.length`
16
+ * to mark the whole flow complete.
17
+ */
18
+ activeStep: number;
19
+ /**
20
+ * Callback with the clicked step's index. When provided, completed steps
21
+ * and the active step become buttons so the reader can revisit an earlier
22
+ * stage; upcoming steps stay non-interactive either way.
23
+ */
24
+ onStepClick?: (index: number) => void;
25
+ /** Layout direction */
26
+ orientation?: 'horizontal' | 'vertical';
27
+ /** Additional CSS classes */
28
+ className?: string;
29
+ };
30
+ export interface StepperProps extends StepperOwnProps, Omit<React.ComponentPropsWithoutRef<'ol'>, keyof StepperOwnProps> {
31
+ }
32
+ /**
33
+ * Stepper component — step-by-step progress indicator for wizards and
34
+ * multi-stage flows. Renders an ordered list of steps with CSS-drawn
35
+ * connectors: completed steps show a check, the active step carries
36
+ * `aria-current="step"`, and upcoming steps show their number.
37
+ *
38
+ * Forwards a ref to the `<ol>` and spreads unrecognised props onto it.
39
+ */
40
+ export declare const Stepper: React.ForwardRefExoticComponent<StepperProps & React.RefAttributes<HTMLOListElement>>;
41
+ export {};