@hraness/design-kit 0.31.1

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 (345) hide show
  1. package/ARTICLE_COPY.md +208 -0
  2. package/HERO_FIELDS.md +55 -0
  3. package/ICONS.md +56 -0
  4. package/LANTERN_MATERIAL.md +60 -0
  5. package/LICENSE +21 -0
  6. package/MARKETING_COPY.md +49 -0
  7. package/MARKETING_PRESET.md +108 -0
  8. package/PALETTES.md +90 -0
  9. package/PAPER_THEME.md +128 -0
  10. package/README.md +745 -0
  11. package/STATUS_PAGES.md +67 -0
  12. package/STYLE.md +245 -0
  13. package/WRITING.md +72 -0
  14. package/dist/browser/index.js +2345 -0
  15. package/dist/chunk-44djxk16.js +6872 -0
  16. package/dist/chunk-5gtx3pza.js +9 -0
  17. package/dist/chunk-77391vmq.js +454 -0
  18. package/dist/chunk-7b1586eb.js +1209 -0
  19. package/dist/chunk-8834fh4n.js +6 -0
  20. package/dist/chunk-9t8xyqte.js +870 -0
  21. package/dist/chunk-cejpzyfh.js +446 -0
  22. package/dist/chunk-eh71jz57.js +429 -0
  23. package/dist/chunk-evb02bc1.js +516 -0
  24. package/dist/chunk-h4k7yv6x.js +368 -0
  25. package/dist/chunk-he8eznb1.js +760 -0
  26. package/dist/chunk-mr1vdcjq.js +404 -0
  27. package/dist/chunk-wzvdn8ey.js +116 -0
  28. package/dist/fonts/nebula-sans/social-fonts.generated.js +53 -0
  29. package/dist/icons.js +48 -0
  30. package/dist/index.js +237 -0
  31. package/dist/launch.js +41 -0
  32. package/dist/mockups/client.js +422 -0
  33. package/dist/mockups/index.js +1850 -0
  34. package/dist/portfolio.js +1549 -0
  35. package/dist/provider-marks.js +19 -0
  36. package/dist/react/charts.js +11 -0
  37. package/dist/react/hero-backdrop.js +8 -0
  38. package/dist/react/index.js +7252 -0
  39. package/dist/react/platform-install.js +10 -0
  40. package/dist/react/server.js +137 -0
  41. package/dist/stylex-manifest.json +1 -0
  42. package/dist/stylex.css +5392 -0
  43. package/dist/syntax-highlighting.js +15 -0
  44. package/dist/testing.js +250 -0
  45. package/package.json +377 -0
  46. package/portfolio-inventory.json +31 -0
  47. package/scripts/check-lantern-material-snapshot.d.mts +26 -0
  48. package/scripts/check-lantern-material-snapshot.mjs +126 -0
  49. package/scripts/check-marketing-snapshot.d.mts +12 -0
  50. package/scripts/check-marketing-snapshot.mjs +58 -0
  51. package/scripts/lantern-material-snapshot.ts +101 -0
  52. package/scripts/marketing-textures.ts +48 -0
  53. package/scripts/paper-theme-snapshot.ts +97 -0
  54. package/scripts/product-marketing-snapshot.ts +149 -0
  55. package/src/appearance-menu.css +266 -0
  56. package/src/appearance.ts +32 -0
  57. package/src/article-html.ts +269 -0
  58. package/src/article.ts +686 -0
  59. package/src/browser/appearance-menu.ts +604 -0
  60. package/src/browser/artifact-share.ts +164 -0
  61. package/src/browser/design-palette.ts +197 -0
  62. package/src/browser/foil.ts +124 -0
  63. package/src/browser/hero-light.ts +11 -0
  64. package/src/browser/index.ts +29 -0
  65. package/src/browser/status-field.ts +432 -0
  66. package/src/browser/status-page.ts +100 -0
  67. package/src/browser/sticky-offset.ts +82 -0
  68. package/src/browser/theme-color-sync.ts +213 -0
  69. package/src/charts.css +7 -0
  70. package/src/compiler-components.css +34 -0
  71. package/src/compiler-foundation.css +18 -0
  72. package/src/compiler-palettes.css +7 -0
  73. package/src/compiler-tokens.css +342 -0
  74. package/src/components.css +3 -0
  75. package/src/design-gallery.css +431 -0
  76. package/src/effects.css +14 -0
  77. package/src/fonts/geist-mono/GeistMono[wght].woff2 +0 -0
  78. package/src/fonts/geist-mono/OFL.txt +93 -0
  79. package/src/fonts/geist-mono/PROVENANCE.md +12 -0
  80. package/src/fonts/instrument-serif/OFL.txt +93 -0
  81. package/src/fonts/instrument-serif/UPSTREAM.md +14 -0
  82. package/src/fonts/instrument-serif/instrument-serif-latin-400.woff2 +0 -0
  83. package/src/fonts/nebula-sans/LICENSE.txt +96 -0
  84. package/src/fonts/nebula-sans/NebulaSans-Black.woff2 +0 -0
  85. package/src/fonts/nebula-sans/NebulaSans-BlackItalic.woff2 +0 -0
  86. package/src/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
  87. package/src/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
  88. package/src/fonts/nebula-sans/NebulaSans-BoldItalic.woff2 +0 -0
  89. package/src/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
  90. package/src/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
  91. package/src/fonts/nebula-sans/NebulaSans-BookItalic.woff2 +0 -0
  92. package/src/fonts/nebula-sans/NebulaSans-Light.woff2 +0 -0
  93. package/src/fonts/nebula-sans/NebulaSans-LightItalic.woff2 +0 -0
  94. package/src/fonts/nebula-sans/NebulaSans-Medium.woff2 +0 -0
  95. package/src/fonts/nebula-sans/NebulaSans-MediumItalic.woff2 +0 -0
  96. package/src/fonts/nebula-sans/NebulaSans-Semibold.woff2 +0 -0
  97. package/src/fonts/nebula-sans/NebulaSans-SemiboldItalic.woff2 +0 -0
  98. package/src/fonts/nebula-sans/PROVENANCE.md +14 -0
  99. package/src/fonts/nebula-sans/social-fonts.generated.ts +3873 -0
  100. package/src/fonts.css +106 -0
  101. package/src/icons/act60/act60.svg +5 -0
  102. package/src/icons/act60/application.svg +9 -0
  103. package/src/icons/act60/chapter-compare.svg +3 -0
  104. package/src/icons/act60/decree-residence.svg +6 -0
  105. package/src/icons/act60/estimate.svg +7 -0
  106. package/src/icons/act60/export-services.svg +4 -0
  107. package/src/icons/act60/investor.svg +13 -0
  108. package/src/icons/act60/presence.svg +7 -0
  109. package/src/icons/aicharts/aicharts.svg +6 -0
  110. package/src/icons/aicharts/cost-compare.svg +4 -0
  111. package/src/icons/aicharts/task-audio.svg +7 -0
  112. package/src/icons/aicharts/task-coding.svg +6 -0
  113. package/src/icons/aicharts/task-images.svg +3 -0
  114. package/src/icons/aicharts/task-reasoning.svg +9 -0
  115. package/src/icons/aicharts/task-research.svg +5 -0
  116. package/src/icons/aicharts/task-video.svg +4 -0
  117. package/src/icons/manifest.json +1371 -0
  118. package/src/icons/platonik/conversation.svg +3 -0
  119. package/src/icons/platonik/creature.svg +7 -0
  120. package/src/icons/platonik/economy.svg +4 -0
  121. package/src/icons/platonik/keep-light.svg +6 -0
  122. package/src/icons/platonik/platonik.svg +7 -0
  123. package/src/icons/platonik/spark-route.svg +15 -0
  124. package/src/icons/receipts/act60-retry.receipt.json +888 -0
  125. package/src/icons/receipts/act60.receipt.json +1366 -0
  126. package/src/icons/receipts/aicharts.receipt.json +1546 -0
  127. package/src/icons/receipts/platonik.receipt.json +1011 -0
  128. package/src/icons/receipts/roughday.receipt.json +770 -0
  129. package/src/icons/receipts/shared.receipt.json +1296 -0
  130. package/src/icons/receipts/slopcamera.receipt.json +1495 -0
  131. package/src/icons/receipts/soundfish-retry.receipt.json +460 -0
  132. package/src/icons/receipts/soundfish.receipt.json +1709 -0
  133. package/src/icons/receipts/sponge.receipt.json +1743 -0
  134. package/src/icons/receipts/stripe-history-retry.receipt.json +145 -0
  135. package/src/icons/receipts/stripe-history.receipt.json +1202 -0
  136. package/src/icons/receipts/wordcell.receipt.json +1924 -0
  137. package/src/icons/roughday/finance.svg +7 -0
  138. package/src/icons/roughday/roughday.svg +6 -0
  139. package/src/icons/roughday/technology.svg +4 -0
  140. package/src/icons/roughday/world.svg +4 -0
  141. package/src/icons/sets/act60.json +63 -0
  142. package/src/icons/sets/aicharts.json +63 -0
  143. package/src/icons/sets/platonik.json +39 -0
  144. package/src/icons/sets/roughday.json +25 -0
  145. package/src/icons/sets/shared.json +12 -0
  146. package/src/icons/sets/slopcamera.json +47 -0
  147. package/src/icons/sets/soundfish.json +43 -0
  148. package/src/icons/sets/sponge.json +51 -0
  149. package/src/icons/sets/stripe-history.json +39 -0
  150. package/src/icons/sets/wordcell.json +51 -0
  151. package/src/icons/shared/agent-skill.svg +4 -0
  152. package/src/icons/shared/cli.svg +5 -0
  153. package/src/icons/shared/privacy.svg +3 -0
  154. package/src/icons/shared/research.svg +7 -0
  155. package/src/icons/shared/sdk.svg +3 -0
  156. package/src/icons/slopcamera/diagram.svg +3 -0
  157. package/src/icons/slopcamera/direct-scene.svg +5 -0
  158. package/src/icons/slopcamera/edit-video.svg +17 -0
  159. package/src/icons/slopcamera/mcp.svg +3 -0
  160. package/src/icons/slopcamera/native-world.svg +3 -0
  161. package/src/icons/slopcamera/slopcamera.svg +11 -0
  162. package/src/icons/soundfish/agent-ui.svg +7 -0
  163. package/src/icons/soundfish/collaborate.svg +5 -0
  164. package/src/icons/soundfish/compose.svg +3 -0
  165. package/src/icons/soundfish/hear.svg +6 -0
  166. package/src/icons/soundfish/human-ui.svg +6 -0
  167. package/src/icons/soundfish/share.svg +3 -0
  168. package/src/icons/soundfish/soundfish.svg +6 -0
  169. package/src/icons/sponge/api.svg +3 -0
  170. package/src/icons/sponge/documents.svg +3 -0
  171. package/src/icons/sponge/library.svg +3 -0
  172. package/src/icons/sponge/one-question.svg +5 -0
  173. package/src/icons/sponge/people-direct.svg +5 -0
  174. package/src/icons/sponge/read.svg +3 -0
  175. package/src/icons/sponge/save.svg +4 -0
  176. package/src/icons/sponge/sponge.svg +7 -0
  177. package/src/icons/stripe-history/company-history.svg +3 -0
  178. package/src/icons/stripe-history/evidence.svg +4 -0
  179. package/src/icons/stripe-history/independence.svg +7 -0
  180. package/src/icons/stripe-history/publications.svg +11 -0
  181. package/src/icons/stripe-history/sources.svg +6 -0
  182. package/src/icons/stripe-history/stripe-history.svg +3 -0
  183. package/src/icons/wordcell/backlinks.svg +6 -0
  184. package/src/icons/wordcell/capture.svg +6 -0
  185. package/src/icons/wordcell/git-provenance.svg +4 -0
  186. package/src/icons/wordcell/kb.svg +4 -0
  187. package/src/icons/wordcell/markdown.svg +4 -0
  188. package/src/icons/wordcell/scopes.svg +3 -0
  189. package/src/icons/wordcell/search.svg +7 -0
  190. package/src/icons.generated.ts +734 -0
  191. package/src/icons.ts +51 -0
  192. package/src/index.ts +275 -0
  193. package/src/lantern-material.css +213 -0
  194. package/src/launch.ts +624 -0
  195. package/src/marketing-assets/UPSTREAM.md +18 -0
  196. package/src/marketing-assets/cells.svg +1 -0
  197. package/src/marketing-assets/grain.svg +1 -0
  198. package/src/marketing-forced-colors.css +12 -0
  199. package/src/mockups/client.tsx +415 -0
  200. package/src/mockups/core.tsx +318 -0
  201. package/src/mockups/frames.tsx +368 -0
  202. package/src/mockups/index.ts +14 -0
  203. package/src/mockups/surfaces.tsx +681 -0
  204. package/src/mockups.css +2607 -0
  205. package/src/palette-appearance.ts +45 -0
  206. package/src/palette-bridge.css +124 -0
  207. package/src/palette-color.ts +41 -0
  208. package/src/palette-system.css +721 -0
  209. package/src/palette-themes.ts +23 -0
  210. package/src/palette-tokens.stylex.ts +519 -0
  211. package/src/palettes.css +3 -0
  212. package/src/palettes.ts +161 -0
  213. package/src/paper-theme.css +244 -0
  214. package/src/plain-publication.css +1241 -0
  215. package/src/plain-site.css +397 -0
  216. package/src/platforms.ts +151 -0
  217. package/src/portfolio.generated.json +1595 -0
  218. package/src/portfolio.generated.ts +1596 -0
  219. package/src/portfolio.ts +226 -0
  220. package/src/product-marketing-foundation.css +433 -0
  221. package/src/product-marketing-preset.css +261 -0
  222. package/src/product-marketing.css +2186 -0
  223. package/src/provider-marks.generated.ts +112 -0
  224. package/src/provider-marks.ts +143 -0
  225. package/src/react/animated-rail-stage.stylex.ts +15 -0
  226. package/src/react/animated-rail-stage.tsx +51 -0
  227. package/src/react/app-shell.stylex.ts +104 -0
  228. package/src/react/app-shell.tsx +121 -0
  229. package/src/react/article.tsx +602 -0
  230. package/src/react/aurora-dots-background.tsx +34 -0
  231. package/src/react/charts.stylex.ts +563 -0
  232. package/src/react/charts.tsx +610 -0
  233. package/src/react/chat.stylex.ts +34 -0
  234. package/src/react/chat.tsx +155 -0
  235. package/src/react/design-gallery.tsx +943 -0
  236. package/src/react/design-palette.stylex.ts +124 -0
  237. package/src/react/design-palette.tsx +196 -0
  238. package/src/react/design-theme-context.tsx +41 -0
  239. package/src/react/effects.stylex.ts +325 -0
  240. package/src/react/fader.stylex.ts +71 -0
  241. package/src/react/fader.tsx +151 -0
  242. package/src/react/foil-card-math.ts +100 -0
  243. package/src/react/foil-card-surface.stylex.ts +326 -0
  244. package/src/react/foil-card-surface.tsx +742 -0
  245. package/src/react/foil-mark.tsx +35 -0
  246. package/src/react/foil.stylex.ts +163 -0
  247. package/src/react/haptics.ts +228 -0
  248. package/src/react/hero-backdrop.tsx +22 -0
  249. package/src/react/index.ts +38 -0
  250. package/src/react/keyboard-shortcuts.ts +228 -0
  251. package/src/react/lantern-material-gallery.tsx +166 -0
  252. package/src/react/lantern-material.stylex.ts +39 -0
  253. package/src/react/launch-beats.tsx +73 -0
  254. package/src/react/navigation-rail.stylex.ts +130 -0
  255. package/src/react/navigation-rail.tsx +228 -0
  256. package/src/react/particle-halo.tsx +115 -0
  257. package/src/react/phaser-dots.tsx +501 -0
  258. package/src/react/platform-icons.tsx +94 -0
  259. package/src/react/platform-install.stylex.ts +385 -0
  260. package/src/react/platform-install.tsx +389 -0
  261. package/src/react/playback-transport.stylex.ts +14 -0
  262. package/src/react/playback-transport.tsx +112 -0
  263. package/src/react/procedural-backdrop.tsx +192 -0
  264. package/src/react/procedural-recipe.ts +323 -0
  265. package/src/react/product-marketing.stylex.ts +4469 -0
  266. package/src/react/product-marketing.tsx +1611 -0
  267. package/src/react/production-data-preview-notice.stylex.ts +40 -0
  268. package/src/react/production-data-preview-notice.tsx +34 -0
  269. package/src/react/provider-mark.stylex.ts +132 -0
  270. package/src/react/provider-mark.tsx +120 -0
  271. package/src/react/relative-time.tsx +117 -0
  272. package/src/react/route-state.stylex.ts +39 -0
  273. package/src/react/route-state.tsx +350 -0
  274. package/src/react/server.ts +11 -0
  275. package/src/react/social-kit-panel.tsx +103 -0
  276. package/src/react/sticky-offset.tsx +33 -0
  277. package/src/react/surfaces.stylex.ts +179 -0
  278. package/src/react/surfaces.tsx +330 -0
  279. package/src/react/syntax-code.tsx +33 -0
  280. package/src/react/theme-color-sync.ts +1 -0
  281. package/src/react/theme-resolution.ts +11 -0
  282. package/src/react/theme.stylex.ts +276 -0
  283. package/src/react/theme.tsx +402 -0
  284. package/src/reading.css +72 -0
  285. package/src/relative-time.ts +185 -0
  286. package/src/reset.css +2 -0
  287. package/src/site-shell.css +16 -0
  288. package/src/status-page-html.ts +63 -0
  289. package/src/status-page.css +276 -0
  290. package/src/status-page.ts +323 -0
  291. package/src/styles.css +21 -0
  292. package/src/syntax-code-lexers.ts +303 -0
  293. package/src/syntax-highlighting.css +78 -0
  294. package/src/syntax-highlighting.ts +477 -0
  295. package/src/syntax-tokens.ts +33 -0
  296. package/src/testing.ts +307 -0
  297. package/src/tokens.css +3 -0
  298. package/src/typography.css +86 -0
  299. package/vendor/evilcharts/LICENSE +21 -0
  300. package/vendor/evilcharts/UPSTREAM.md +14 -0
  301. package/vendor/platform-marks/LICENSE +30 -0
  302. package/vendor/platform-marks/UPSTREAM.md +15 -0
  303. package/vendor/platform-marks/apple.svg +1 -0
  304. package/vendor/platform-marks/linux.svg +1 -0
  305. package/vendor/provider-marks/CRUSH-LICENSE.md +134 -0
  306. package/vendor/provider-marks/LICENSE +21 -0
  307. package/vendor/provider-marks/UPSTREAM.md +27 -0
  308. package/vendor/provider-marks/aider.svg +3 -0
  309. package/vendor/provider-marks/crush-heartbit.svg +8 -0
  310. package/vendor/provider-marks/lobehub/alibabacloud-color.svg +1 -0
  311. package/vendor/provider-marks/lobehub/alibabacloud.svg +1 -0
  312. package/vendor/provider-marks/lobehub/amp-color.svg +1 -0
  313. package/vendor/provider-marks/lobehub/amp.svg +1 -0
  314. package/vendor/provider-marks/lobehub/anthropic.svg +1 -0
  315. package/vendor/provider-marks/lobehub/claude-color.svg +1 -0
  316. package/vendor/provider-marks/lobehub/claude.svg +1 -0
  317. package/vendor/provider-marks/lobehub/claudecode-color.svg +1 -0
  318. package/vendor/provider-marks/lobehub/claudecode.svg +1 -0
  319. package/vendor/provider-marks/lobehub/codex-color.svg +1 -0
  320. package/vendor/provider-marks/lobehub/codex.svg +1 -0
  321. package/vendor/provider-marks/lobehub/cursor.svg +1 -0
  322. package/vendor/provider-marks/lobehub/deepseek-color.svg +1 -0
  323. package/vendor/provider-marks/lobehub/deepseek.svg +1 -0
  324. package/vendor/provider-marks/lobehub/devin-color.svg +1 -0
  325. package/vendor/provider-marks/lobehub/devin.svg +1 -0
  326. package/vendor/provider-marks/lobehub/gemini-color.svg +1 -0
  327. package/vendor/provider-marks/lobehub/gemini.svg +1 -0
  328. package/vendor/provider-marks/lobehub/geminicli-color.svg +1 -0
  329. package/vendor/provider-marks/lobehub/geminicli.svg +1 -0
  330. package/vendor/provider-marks/lobehub/goose.svg +1 -0
  331. package/vendor/provider-marks/lobehub/meta-color.svg +1 -0
  332. package/vendor/provider-marks/lobehub/meta.svg +1 -0
  333. package/vendor/provider-marks/lobehub/mistral-color.svg +1 -0
  334. package/vendor/provider-marks/lobehub/mistral.svg +1 -0
  335. package/vendor/provider-marks/lobehub/moonshot.svg +1 -0
  336. package/vendor/provider-marks/lobehub/nvidia-color.svg +1 -0
  337. package/vendor/provider-marks/lobehub/nvidia.svg +1 -0
  338. package/vendor/provider-marks/lobehub/openai.svg +1 -0
  339. package/vendor/provider-marks/lobehub/opencode.svg +1 -0
  340. package/vendor/provider-marks/lobehub/perplexity-color.svg +1 -0
  341. package/vendor/provider-marks/lobehub/perplexity.svg +1 -0
  342. package/vendor/provider-marks/lobehub/qwen-color.svg +1 -0
  343. package/vendor/provider-marks/lobehub/qwen.svg +1 -0
  344. package/vendor/provider-marks/lobehub/xai.svg +1 -0
  345. package/vendor/provider-marks/lobehub/zai.svg +1 -0
@@ -0,0 +1,610 @@
1
+ "use client";
2
+
3
+ import {
4
+ PolarAngleAxis,
5
+ PolarGrid,
6
+ PolarRadiusAxis,
7
+ Radar,
8
+ RadarChart,
9
+ ResponsiveContainer,
10
+ Tooltip as RechartsTooltip,
11
+ } from "recharts";
12
+ import {
13
+ useId,
14
+ type CSSProperties,
15
+ type ReactNode,
16
+ } from "react";
17
+ import * as stylex from "@stylexjs/stylex";
18
+
19
+ import { cn } from "@hraness/ui";
20
+
21
+ import { chartStyles } from "./charts.stylex.js";
22
+
23
+ export type BarListChartDatum = Readonly<{
24
+ color?: string;
25
+ detail?: string;
26
+ id: string;
27
+ label: string;
28
+ value: number;
29
+ }>;
30
+
31
+ export type RadarProfileChartAxis = Readonly<{
32
+ id: string;
33
+ label: string;
34
+ }>;
35
+
36
+ export type RadarProfileChartSeries = Readonly<{
37
+ color: string;
38
+ id: string;
39
+ label: string;
40
+ values: Readonly<Record<string, number>>;
41
+ }>;
42
+
43
+ export type RangePlotChartDatum = Readonly<{
44
+ color?: string;
45
+ detail?: string;
46
+ id: string;
47
+ label: string;
48
+ maximum: number;
49
+ median: number;
50
+ minimum: number;
51
+ }>;
52
+
53
+ type SelectableChartProps = Readonly<{
54
+ onSelectionChange?: (id: string) => void;
55
+ selectedId?: string | null;
56
+ }>;
57
+
58
+ type BarChartStyle = CSSProperties & Readonly<{
59
+ "--hraness-design-chart-color": string;
60
+ "--hraness-design-chart-value": string;
61
+ }>;
62
+
63
+ type RangeChartStyle = CSSProperties & Readonly<{
64
+ "--hraness-design-chart-color": string;
65
+ "--hraness-design-chart-median": string;
66
+ "--hraness-design-chart-range-left": string;
67
+ "--hraness-design-chart-range-width": string;
68
+ }>;
69
+
70
+ function finiteOr(value: number, fallback: number): number {
71
+ return Number.isFinite(value) ? value : fallback;
72
+ }
73
+
74
+ function normalizedPercent(value: number, minimum: number, maximum: number): number {
75
+ const span = maximum - minimum;
76
+ if (!Number.isFinite(span) || span <= 0) return 0;
77
+ const bounded = Math.max(0, Math.min(100, ((finiteOr(value, minimum) - minimum) / span) * 100));
78
+ return Math.round(bounded * 10_000) / 10_000;
79
+ }
80
+
81
+ function accessibleChartCaption({ ariaLabel }: { ariaLabel: string }) {
82
+ if (ariaLabel.trim() === "") throw new TypeError("Charts require a nonblank accessible label.");
83
+ return <figcaption className="hraness-design-visually-hidden">{ariaLabel}</figcaption>;
84
+ }
85
+
86
+ function ChartRow({
87
+ children,
88
+ id,
89
+ isSelected,
90
+ onSelectionChange,
91
+ variant = "default",
92
+ }: Readonly<{
93
+ children: ReactNode;
94
+ id: string;
95
+ isSelected: boolean;
96
+ onSelectionChange: ((id: string) => void) | undefined;
97
+ variant?: "default" | "legend";
98
+ }>) {
99
+ const presentation = stylex.props(
100
+ chartStyles.row,
101
+ onSelectionChange !== undefined && chartStyles.selectableRow,
102
+ isSelected && chartStyles.selectedRow,
103
+ variant === "legend" && chartStyles.legendRow,
104
+ );
105
+ if (onSelectionChange === undefined) {
106
+ return (
107
+ <div
108
+ {...presentation}
109
+ className={cn(
110
+ "hraness-design-chart-row",
111
+ presentation.className,
112
+ )}
113
+ data-selected={isSelected || undefined}
114
+ >
115
+ {children}
116
+ </div>
117
+ );
118
+ }
119
+ return (
120
+ <button
121
+ {...presentation}
122
+ aria-pressed={isSelected}
123
+ className={cn(
124
+ "hraness-design-chart-row",
125
+ "hraness-design-chart-row--selectable",
126
+ presentation.className,
127
+ )}
128
+ data-selected={isSelected || undefined}
129
+ onClick={() => onSelectionChange(id)}
130
+ type="button"
131
+ >
132
+ {children}
133
+ </button>
134
+ );
135
+ }
136
+
137
+ export function BarListChart({
138
+ "aria-label": ariaLabel,
139
+ className,
140
+ data,
141
+ domain = [0, Math.max(1, ...data.map(({ value }) => finiteOr(value, 0)))],
142
+ formatValue = (value) => String(value),
143
+ onSelectionChange,
144
+ selectedId = null,
145
+ }: Readonly<{
146
+ "aria-label": string;
147
+ className?: string;
148
+ data: readonly BarListChartDatum[];
149
+ domain?: readonly [number, number];
150
+ formatValue?: (value: number) => string;
151
+ }> & SelectableChartProps) {
152
+ const [minimum, maximum] = domain;
153
+ if (!Number.isFinite(minimum) || !Number.isFinite(maximum) || minimum >= maximum) {
154
+ throw new RangeError("Bar chart domain must be finite and ascending.");
155
+ }
156
+ const rootPresentation = stylex.props(chartStyles.root);
157
+ const rowsPresentation = stylex.props(chartStyles.rows);
158
+ const headingPresentation = stylex.props(chartStyles.heading);
159
+ const labelPresentation = stylex.props(chartStyles.label);
160
+ const valuePresentation = stylex.props(chartStyles.value);
161
+ const detailPresentation = stylex.props(chartStyles.detail);
162
+ const trackPresentation = stylex.props(chartStyles.track);
163
+ const barPresentation = stylex.props(chartStyles.bar);
164
+
165
+ return (
166
+ <figure
167
+ {...rootPresentation}
168
+ className={cn(
169
+ "hraness-design-bar-list-chart",
170
+ rootPresentation.className,
171
+ className,
172
+ )}
173
+ >
174
+ {accessibleChartCaption({ ariaLabel })}
175
+ <div
176
+ {...rowsPresentation}
177
+ className={cn(
178
+ "hraness-design-bar-list-chart__rows",
179
+ rowsPresentation.className,
180
+ )}
181
+ >
182
+ {data.map((datum) => {
183
+ const value = finiteOr(datum.value, minimum);
184
+ const width = normalizedPercent(value, minimum, maximum);
185
+ const style: BarChartStyle = {
186
+ "--hraness-design-chart-color": datum.color ?? "var(--info)",
187
+ "--hraness-design-chart-value": `${String(width)}%`,
188
+ };
189
+ return (
190
+ <ChartRow
191
+ id={datum.id}
192
+ isSelected={selectedId === datum.id}
193
+ key={datum.id}
194
+ onSelectionChange={onSelectionChange}
195
+ >
196
+ <span
197
+ {...headingPresentation}
198
+ className={cn(
199
+ "hraness-design-chart-row__heading",
200
+ headingPresentation.className,
201
+ )}
202
+ >
203
+ <span
204
+ {...labelPresentation}
205
+ className={cn(
206
+ "hraness-design-chart-row__label",
207
+ labelPresentation.className,
208
+ )}
209
+ >
210
+ {datum.label}
211
+ </span>
212
+ <span
213
+ {...valuePresentation}
214
+ className={cn(
215
+ "hraness-design-chart-row__value",
216
+ valuePresentation.className,
217
+ )}
218
+ >
219
+ {formatValue(value)}
220
+ </span>
221
+ </span>
222
+ <span
223
+ {...trackPresentation}
224
+ aria-hidden="true"
225
+ className={cn(
226
+ "hraness-design-bar-list-chart__track",
227
+ trackPresentation.className,
228
+ )}
229
+ style={style}
230
+ >
231
+ <span
232
+ {...barPresentation}
233
+ className={cn(
234
+ "hraness-design-bar-list-chart__bar",
235
+ barPresentation.className,
236
+ )}
237
+ />
238
+ </span>
239
+ {datum.detail === undefined ? null : (
240
+ <span
241
+ {...detailPresentation}
242
+ className={cn(
243
+ "hraness-design-chart-row__detail",
244
+ detailPresentation.className,
245
+ )}
246
+ >
247
+ {datum.detail}
248
+ </span>
249
+ )}
250
+ </ChartRow>
251
+ );
252
+ })}
253
+ </div>
254
+ </figure>
255
+ );
256
+ }
257
+
258
+ type RadarTooltipPayload = Readonly<{
259
+ color?: string;
260
+ dataKey?: string | number;
261
+ name?: string | number;
262
+ value?: unknown;
263
+ }>;
264
+
265
+ function RadarProfileTooltip({
266
+ active,
267
+ label,
268
+ payload,
269
+ series,
270
+ }: Readonly<{
271
+ active?: boolean;
272
+ label?: unknown;
273
+ payload?: readonly RadarTooltipPayload[];
274
+ series: readonly RadarProfileChartSeries[];
275
+ }>) {
276
+ if (active !== true || payload === undefined || payload.length === 0) return null;
277
+ const labels = new Map(series.map((item) => [item.id, item.label]));
278
+ const tooltipPresentation = stylex.props(chartStyles.tooltip);
279
+ const titlePresentation = stylex.props(chartStyles.tooltipTitle);
280
+ const listPresentation = stylex.props(chartStyles.tooltipList);
281
+ const itemPresentation = stylex.props(chartStyles.tooltipItem);
282
+ const termPresentation = stylex.props(chartStyles.tooltipTerm);
283
+ const indicatorPresentation = stylex.props(chartStyles.indicator);
284
+ const valuePresentation = stylex.props(chartStyles.tooltipValue);
285
+ return (
286
+ <div
287
+ {...tooltipPresentation}
288
+ className={cn(
289
+ "hraness-design-chart-tooltip",
290
+ tooltipPresentation.className,
291
+ )}
292
+ >
293
+ <strong
294
+ {...titlePresentation}
295
+ className={titlePresentation.className}
296
+ >
297
+ {typeof label === "string" ? label : "Benchmark"}
298
+ </strong>
299
+ <dl {...listPresentation} className={listPresentation.className}>
300
+ {payload.map((item, index) => {
301
+ const key = String(item.dataKey ?? item.name ?? index);
302
+ const value = typeof item.value === "number" && Number.isFinite(item.value)
303
+ ? item.value.toFixed(1)
304
+ : "–";
305
+ return (
306
+ <div
307
+ {...itemPresentation}
308
+ className={itemPresentation.className}
309
+ key={key}
310
+ >
311
+ <dt {...termPresentation} className={termPresentation.className}>
312
+ <i
313
+ {...indicatorPresentation}
314
+ aria-hidden="true"
315
+ className={indicatorPresentation.className}
316
+ style={{ backgroundColor: item.color }}
317
+ />
318
+ {labels.get(key) ?? key}
319
+ </dt>
320
+ <dd {...valuePresentation} className={valuePresentation.className}>
321
+ {value}
322
+ </dd>
323
+ </div>
324
+ );
325
+ })}
326
+ </dl>
327
+ </div>
328
+ );
329
+ }
330
+
331
+ export function RadarProfileChart({
332
+ "aria-label": ariaLabel,
333
+ axes,
334
+ className,
335
+ onSelectionChange,
336
+ selectedId = null,
337
+ series,
338
+ }: Readonly<{
339
+ "aria-label": string;
340
+ axes: readonly RadarProfileChartAxis[];
341
+ className?: string;
342
+ series: readonly RadarProfileChartSeries[];
343
+ }> & SelectableChartProps) {
344
+ const gradientPrefix = useId().replaceAll(":", "");
345
+ const effectiveSelectedId = series.some(({ id }) => id === selectedId) ? selectedId : null;
346
+ const data = axes.map((axis) => {
347
+ const row: Record<string, number | string> = { axis: axis.label };
348
+ for (const item of series) row[item.id] = finiteOr(item.values[axis.id] ?? 0, 0);
349
+ return row;
350
+ });
351
+ const rootPresentation = stylex.props(chartStyles.root);
352
+ const plotPresentation = stylex.props(chartStyles.plot);
353
+ const legendPresentation = stylex.props(chartStyles.legend);
354
+ const indicatorPresentation = stylex.props(chartStyles.indicator);
355
+
356
+ return (
357
+ <figure
358
+ {...rootPresentation}
359
+ className={cn(
360
+ "hraness-design-radar-profile-chart",
361
+ rootPresentation.className,
362
+ className,
363
+ )}
364
+ >
365
+ {accessibleChartCaption({ ariaLabel })}
366
+ <div
367
+ {...plotPresentation}
368
+ aria-hidden="true"
369
+ className={cn(
370
+ "hraness-design-radar-profile-chart__plot",
371
+ plotPresentation.className,
372
+ )}
373
+ >
374
+ <ResponsiveContainer
375
+ height="100%"
376
+ initialDimension={{ height: 280, width: 360 }}
377
+ width="100%"
378
+ >
379
+ <RadarChart data={data} margin={{ bottom: 22, left: 28, right: 28, top: 22 }}>
380
+ <PolarGrid
381
+ gridType="polygon"
382
+ stroke="var(--grid)"
383
+ strokeDasharray="2 5"
384
+ />
385
+ <PolarAngleAxis
386
+ dataKey="axis"
387
+ tick={{ fill: "var(--muted)", fontFamily: "var(--font-text)", fontSize: 11 }}
388
+ tickLine={false}
389
+ />
390
+ <PolarRadiusAxis axisLine={false} domain={[0, 100]} tick={false} />
391
+ <RechartsTooltip
392
+ content={<RadarProfileTooltip series={series} />}
393
+ cursor={false}
394
+ isAnimationActive={false}
395
+ />
396
+ <defs>
397
+ {series.map((item, index) => {
398
+ const gradientId = `${gradientPrefix}-${String(index)}`;
399
+ return (
400
+ <radialGradient id={gradientId} key={item.id}>
401
+ <stop offset="0%" stopColor={item.color} stopOpacity="0.06" />
402
+ <stop offset="100%" stopColor={item.color} stopOpacity="0.72" />
403
+ </radialGradient>
404
+ );
405
+ })}
406
+ </defs>
407
+ {series.map((item, index) => {
408
+ const gradientId = `${gradientPrefix}-${String(index)}`;
409
+ const dimmed = effectiveSelectedId !== null && effectiveSelectedId !== item.id;
410
+ return (
411
+ <Radar
412
+ dataKey={item.id}
413
+ fill={`url(#${gradientId})`}
414
+ fillOpacity={dimmed ? 0.05 : 0.17}
415
+ isAnimationActive={false}
416
+ key={item.id}
417
+ name={item.label}
418
+ stroke={item.color}
419
+ strokeOpacity={dimmed ? 0.2 : 0.88}
420
+ strokeWidth={dimmed ? 1 : 1.75}
421
+ />
422
+ );
423
+ })}
424
+ </RadarChart>
425
+ </ResponsiveContainer>
426
+ </div>
427
+ <div
428
+ {...legendPresentation}
429
+ aria-label="Profiles"
430
+ className={cn(
431
+ "hraness-design-radar-profile-chart__legend",
432
+ legendPresentation.className,
433
+ )}
434
+ role="group"
435
+ >
436
+ {series.map((item) => (
437
+ <ChartRow
438
+ id={item.id}
439
+ isSelected={selectedId === item.id}
440
+ key={item.id}
441
+ onSelectionChange={onSelectionChange}
442
+ variant="legend"
443
+ >
444
+ <i
445
+ {...indicatorPresentation}
446
+ aria-hidden="true"
447
+ className={indicatorPresentation.className}
448
+ style={{ backgroundColor: item.color }}
449
+ />
450
+ <span>{item.label}</span>
451
+ </ChartRow>
452
+ ))}
453
+ </div>
454
+ <table className="hraness-design-visually-hidden">
455
+ <caption>{ariaLabel}</caption>
456
+ <thead>
457
+ <tr>
458
+ <th scope="col">Benchmark</th>
459
+ {series.map((item) => <th key={item.id} scope="col">{item.label}</th>)}
460
+ </tr>
461
+ </thead>
462
+ <tbody>
463
+ {axes.map((axis) => (
464
+ <tr key={axis.id}>
465
+ <th scope="row">{axis.label}</th>
466
+ {series.map((item) => (
467
+ <td key={item.id}>{finiteOr(item.values[axis.id] ?? 0, 0).toFixed(1)}</td>
468
+ ))}
469
+ </tr>
470
+ ))}
471
+ </tbody>
472
+ </table>
473
+ </figure>
474
+ );
475
+ }
476
+
477
+ export function RangePlotChart({
478
+ "aria-label": ariaLabel,
479
+ className,
480
+ data,
481
+ domain = [0, 100],
482
+ formatValue = (value) => String(value),
483
+ onSelectionChange,
484
+ selectedId = null,
485
+ }: Readonly<{
486
+ "aria-label": string;
487
+ className?: string;
488
+ data: readonly RangePlotChartDatum[];
489
+ domain?: readonly [number, number];
490
+ formatValue?: (value: number) => string;
491
+ }> & SelectableChartProps) {
492
+ const [domainMinimum, domainMaximum] = domain;
493
+ if (!Number.isFinite(domainMinimum) || !Number.isFinite(domainMaximum) || domainMinimum >= domainMaximum) {
494
+ throw new RangeError("Range plot domain must be finite and ascending.");
495
+ }
496
+ const rootPresentation = stylex.props(chartStyles.root);
497
+ const rowsPresentation = stylex.props(chartStyles.rows);
498
+ const headingPresentation = stylex.props(chartStyles.heading);
499
+ const labelPresentation = stylex.props(chartStyles.label);
500
+ const valuePresentation = stylex.props(chartStyles.value);
501
+ const detailPresentation = stylex.props(chartStyles.detail);
502
+ const trackPresentation = stylex.props(chartStyles.track, chartStyles.rangeTrack);
503
+ const rangePresentation = stylex.props(chartStyles.range);
504
+ const medianPresentation = stylex.props(chartStyles.median);
505
+
506
+ return (
507
+ <figure
508
+ {...rootPresentation}
509
+ className={cn(
510
+ "hraness-design-range-plot-chart",
511
+ rootPresentation.className,
512
+ className,
513
+ )}
514
+ >
515
+ {accessibleChartCaption({ ariaLabel })}
516
+ <div
517
+ {...rowsPresentation}
518
+ className={cn(
519
+ "hraness-design-range-plot-chart__rows",
520
+ rowsPresentation.className,
521
+ )}
522
+ >
523
+ {data.map((datum) => {
524
+ const minimum = finiteOr(datum.minimum, domainMinimum);
525
+ const maximum = finiteOr(datum.maximum, domainMaximum);
526
+ const median = finiteOr(datum.median, minimum);
527
+ const left = normalizedPercent(Math.min(minimum, maximum), domainMinimum, domainMaximum);
528
+ const right = normalizedPercent(Math.max(minimum, maximum), domainMinimum, domainMaximum);
529
+ const middle = normalizedPercent(median, domainMinimum, domainMaximum);
530
+ const style: RangeChartStyle = {
531
+ "--hraness-design-chart-color": datum.color ?? "var(--info)",
532
+ "--hraness-design-chart-range-left": `${String(left)}%`,
533
+ "--hraness-design-chart-range-width": `${String(Math.max(0, right - left))}%`,
534
+ "--hraness-design-chart-median": `${String(middle)}%`,
535
+ };
536
+ return (
537
+ <ChartRow
538
+ id={datum.id}
539
+ isSelected={selectedId === datum.id}
540
+ key={datum.id}
541
+ onSelectionChange={onSelectionChange}
542
+ >
543
+ <span
544
+ {...headingPresentation}
545
+ className={cn(
546
+ "hraness-design-chart-row__heading",
547
+ headingPresentation.className,
548
+ )}
549
+ >
550
+ <span
551
+ {...labelPresentation}
552
+ className={cn(
553
+ "hraness-design-chart-row__label",
554
+ labelPresentation.className,
555
+ )}
556
+ >
557
+ {datum.label}
558
+ </span>
559
+ <span
560
+ {...valuePresentation}
561
+ className={cn(
562
+ "hraness-design-chart-row__value",
563
+ valuePresentation.className,
564
+ )}
565
+ >
566
+ {formatValue(minimum)}–{formatValue(maximum)}
567
+ </span>
568
+ </span>
569
+ <span
570
+ {...trackPresentation}
571
+ aria-hidden="true"
572
+ className={cn(
573
+ "hraness-design-range-plot-chart__track",
574
+ trackPresentation.className,
575
+ )}
576
+ style={style}
577
+ >
578
+ <span
579
+ {...rangePresentation}
580
+ className={cn(
581
+ "hraness-design-range-plot-chart__range",
582
+ rangePresentation.className,
583
+ )}
584
+ />
585
+ <span
586
+ {...medianPresentation}
587
+ className={cn(
588
+ "hraness-design-range-plot-chart__median",
589
+ medianPresentation.className,
590
+ )}
591
+ />
592
+ </span>
593
+ {datum.detail === undefined ? null : (
594
+ <span
595
+ {...detailPresentation}
596
+ className={cn(
597
+ "hraness-design-chart-row__detail",
598
+ detailPresentation.className,
599
+ )}
600
+ >
601
+ {datum.detail}
602
+ </span>
603
+ )}
604
+ </ChartRow>
605
+ );
606
+ })}
607
+ </div>
608
+ </figure>
609
+ );
610
+ }
@@ -0,0 +1,34 @@
1
+ import * as stylex from "@stylexjs/stylex";
2
+
3
+ const compactViewport = "@media (max-width: 48rem)";
4
+
5
+ export const chatStyles = stylex.create({
6
+ composer: {
7
+ alignItems: "end",
8
+ display: "grid",
9
+ gap: "var(--space-2)",
10
+ gridTemplateColumns: {
11
+ default: "minmax(0, 1fr) auto",
12
+ [compactViewport]: "1fr",
13
+ },
14
+ },
15
+ message: {
16
+ display: "grid",
17
+ gap: "var(--space-3)",
18
+ gridTemplateColumns: "auto minmax(0, 1fr)",
19
+ },
20
+ messageHeader: {
21
+ color: "var(--muted)",
22
+ fontSize: "var(--text-caption)",
23
+ "margin-block-end": "var(--space-1)",
24
+ },
25
+ messageMinInline: {
26
+ "min-inline-size": 0,
27
+ },
28
+ messageRow: {
29
+ alignItems: "center",
30
+ display: "flex",
31
+ flexWrap: "wrap",
32
+ gap: "var(--space-2)",
33
+ },
34
+ });