@heyramzi/ui 1.54.2 → 2.0.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 (361) hide show
  1. package/COMPONENTS.md +3 -2
  2. package/README.md +34 -9
  3. package/package.json +198 -6
  4. package/src/ai/generation-progress/GenerationProgress.svelte +12 -12
  5. package/src/ambient.d.ts +8 -0
  6. package/src/brand/brand.test.ts +67 -0
  7. package/src/brand/index.ts +137 -0
  8. package/src/canvas/README.md +236 -0
  9. package/src/canvas/card/card-frame.svelte +61 -0
  10. package/src/canvas/card/card.css +39 -0
  11. package/src/canvas/card/resize-handles.svelte +119 -0
  12. package/src/canvas/card/resize.ts +33 -0
  13. package/src/canvas/chrome/chrome-zoom.svelte +120 -0
  14. package/src/canvas/chrome/chrome.css +231 -0
  15. package/src/canvas/chrome/lock-badge.svelte +18 -0
  16. package/src/canvas/chrome/side-handles.svelte +30 -0
  17. package/src/canvas/chrome/size-chip.svelte +45 -0
  18. package/src/canvas/chrome/snap-guides.svelte +69 -0
  19. package/src/canvas/components/icons/ToolIcon.svelte +30 -0
  20. package/src/canvas/components/shared/svg/PreviewSvg.svelte +67 -0
  21. package/src/canvas/components/shared/svg/ShapeOutline.svelte +76 -0
  22. package/src/canvas/components/shared/svg/StrokeNode.svelte +69 -0
  23. package/src/canvas/components/shared/svg/TextRule.svelte +37 -0
  24. package/src/canvas/edges/bend-handle.svelte +76 -0
  25. package/src/canvas/edges/edge-gradient.svelte +30 -0
  26. package/src/canvas/edges/edge-path.test.ts +58 -0
  27. package/src/canvas/edges/edge-path.ts +293 -0
  28. package/src/canvas/edges/gradient-edge.svelte +43 -0
  29. package/src/canvas/editor/context-menu.test.ts +65 -0
  30. package/src/canvas/editor/context-menu.ts +48 -0
  31. package/src/canvas/editor/dispatch.svelte.test.ts +243 -0
  32. package/src/canvas/editor/dispatch.svelte.ts +205 -0
  33. package/src/canvas/editor/editor-context.ts +62 -0
  34. package/src/canvas/editor/editor-mode.ts +67 -0
  35. package/src/canvas/editor/frame.ts +98 -0
  36. package/src/canvas/editor/hand-context.ts +15 -0
  37. package/src/canvas/editor/history-core.test.ts +239 -0
  38. package/src/canvas/editor/history-core.ts +247 -0
  39. package/src/canvas/editor/keymap.test.ts +161 -0
  40. package/src/canvas/editor/keymap.ts +151 -0
  41. package/src/canvas/editor/layout-actions.test.ts +156 -0
  42. package/src/canvas/editor/layout-actions.ts +219 -0
  43. package/src/canvas/editor/lock.test.ts +68 -0
  44. package/src/canvas/editor/lock.ts +87 -0
  45. package/src/canvas/editor/rebase.test.ts +208 -0
  46. package/src/canvas/editor/rebase.ts +272 -0
  47. package/src/canvas/editor/style.ts +184 -0
  48. package/src/canvas/editor/text-field-context.ts +41 -0
  49. package/src/canvas/editor/z-order.test.ts +75 -0
  50. package/src/canvas/editor/z-order.ts +87 -0
  51. package/src/canvas/headless/alignment.test.ts +59 -0
  52. package/src/canvas/headless/alignment.ts +23 -0
  53. package/src/canvas/headless/break-apart-find.ts +418 -0
  54. package/src/canvas/headless/break-apart-pieces.ts +104 -0
  55. package/src/canvas/headless/break-apart-plan.ts +439 -0
  56. package/src/canvas/headless/break-apart-style.ts +106 -0
  57. package/src/canvas/headless/break-apart.ts +244 -0
  58. package/src/canvas/headless/canvas-cleanup.test.ts +181 -0
  59. package/src/canvas/headless/canvas-cleanup.ts +102 -0
  60. package/src/canvas/headless/edge-anchors.test.ts +70 -0
  61. package/src/canvas/headless/edge-anchors.ts +143 -0
  62. package/src/canvas/headless/edge-memo.test.ts +58 -0
  63. package/src/canvas/headless/edge-memo.ts +32 -0
  64. package/src/canvas/headless/edge-serialization.test.ts +55 -0
  65. package/src/canvas/headless/edge-serialization.ts +32 -0
  66. package/src/canvas/headless/figure-geometry.ts +293 -0
  67. package/src/canvas/headless/fit-viewport.test.ts +85 -0
  68. package/src/canvas/headless/fit-viewport.ts +137 -0
  69. package/src/canvas/headless/grid.test.ts +34 -0
  70. package/src/canvas/headless/grid.ts +94 -0
  71. package/src/canvas/headless/icon-node.test.ts +23 -0
  72. package/src/canvas/headless/icon-node.ts +66 -0
  73. package/src/canvas/headless/index.ts +9 -0
  74. package/src/canvas/headless/linkify.test.ts +23 -0
  75. package/src/canvas/headless/linkify.ts +25 -0
  76. package/src/canvas/headless/pen-marks.ts +133 -0
  77. package/src/canvas/headless/sketch.test.ts +99 -0
  78. package/src/canvas/headless/sketch.ts +302 -0
  79. package/src/canvas/headless/snap-guides.test.ts +61 -0
  80. package/src/canvas/headless/snap-guides.ts +146 -0
  81. package/src/canvas/headless/svg-figure.test.ts +49 -0
  82. package/src/canvas/headless/svg-figure.ts +176 -0
  83. package/src/canvas/headless/table-layout.test.ts +158 -0
  84. package/src/canvas/headless/table-layout.ts +309 -0
  85. package/src/canvas/index.ts +35 -0
  86. package/src/canvas/lod/lod-mode.svelte.ts +30 -0
  87. package/src/canvas/lod/lod-switch.svelte +140 -0
  88. package/src/canvas/lod/render-all.svelte.ts +52 -0
  89. package/src/canvas/ops/arrange.ts +108 -0
  90. package/src/canvas/ops/ops.test.ts +98 -0
  91. package/src/canvas/ops/ops.ts +347 -0
  92. package/src/canvas/pen/PenField.svelte +128 -0
  93. package/src/canvas/pen/PenField.test.ts +87 -0
  94. package/src/canvas/pen/PenOverlay.svelte +172 -0
  95. package/src/canvas/pen/ShapeNode.svelte +64 -0
  96. package/src/canvas/pen/TextNode.svelte +97 -0
  97. package/src/canvas/pen/colors.ts +28 -0
  98. package/src/canvas/pen/eraser.ts +46 -0
  99. package/src/canvas/pen/geometry.ts +148 -0
  100. package/src/canvas/pen/host-field.test.svelte +13 -0
  101. package/src/canvas/pen/index.ts +40 -0
  102. package/src/canvas/pen/ops.ts +16 -0
  103. package/src/canvas/pen/pen-session.ts +177 -0
  104. package/src/canvas/pen/pen.css +149 -0
  105. package/src/canvas/pen/plugin.ts +139 -0
  106. package/src/canvas/pen/shapes.ts +104 -0
  107. package/src/canvas/pen/stroke-path.ts +54 -0
  108. package/src/canvas/pen/types.ts +96 -0
  109. package/src/canvas/pen/ui.ts +26 -0
  110. package/src/canvas/plugin.test.ts +45 -0
  111. package/src/canvas/plugin.ts +159 -0
  112. package/src/canvas/screen/GlyphIcon.svelte +25 -0
  113. package/src/canvas/screen/PartView.svelte +176 -0
  114. package/src/canvas/screen/ScreenBody.svelte +142 -0
  115. package/src/canvas/screen/ScreenCard.svelte +32 -0
  116. package/src/canvas/screen/glyphs/button.svg +1 -0
  117. package/src/canvas/screen/glyphs/close.svg +1 -0
  118. package/src/canvas/screen/glyphs/down.svg +1 -0
  119. package/src/canvas/screen/glyphs/header.svg +1 -0
  120. package/src/canvas/screen/glyphs/hills.svg +1 -0
  121. package/src/canvas/screen/glyphs/image.svg +1 -0
  122. package/src/canvas/screen/glyphs/input.svg +1 -0
  123. package/src/canvas/screen/glyphs/list.svg +1 -0
  124. package/src/canvas/screen/glyphs/modal.svg +1 -0
  125. package/src/canvas/screen/glyphs/plus.svg +1 -0
  126. package/src/canvas/screen/glyphs/up.svg +1 -0
  127. package/src/canvas/screen/glyphs.ts +40 -0
  128. package/src/canvas/screen/labels.ts +41 -0
  129. package/src/canvas/screen/parts.ts +164 -0
  130. package/src/canvas/screen/plugin.ts +67 -0
  131. package/src/canvas/screen/screen.css +593 -0
  132. package/src/canvas/screen/types.ts +47 -0
  133. package/src/canvas/screen/ui.ts +13 -0
  134. package/src/canvas/test-utils.ts +18 -0
  135. package/src/canvas/types.ts +53 -0
  136. package/src/canvas/ui/CanvasToolbar.svelte +169 -0
  137. package/src/canvas/ui/FrameLayer.svelte +116 -0
  138. package/src/canvas/ui/PenLayer.svelte +39 -0
  139. package/src/canvas/ui/StylePanel.svelte +268 -0
  140. package/src/canvas/ui/canvas-grid.svelte +156 -0
  141. package/src/canvas/ui/canvas-ui.css +350 -0
  142. package/src/canvas/ui/forward-wheel.ts +29 -0
  143. package/src/canvas/ui/icons.ts +124 -0
  144. package/src/canvas/ui/place-panel.ts +80 -0
  145. package/src/canvas/ui/shortcut-tip.test.ts +16 -0
  146. package/src/canvas/ui/shortcut-tip.ts +17 -0
  147. package/src/canvas/ui/tools.ts +146 -0
  148. package/src/charts/README.md +104 -0
  149. package/src/charts/ambient.d.ts +4 -0
  150. package/src/charts/animated-value.css +7 -0
  151. package/src/charts/animated-value.svelte +110 -0
  152. package/src/charts/area-chart.css +29 -0
  153. package/src/charts/area-chart.svelte +204 -0
  154. package/src/charts/bar-chart.css +119 -0
  155. package/src/charts/bar-chart.svelte +275 -0
  156. package/src/charts/charts.css +201 -0
  157. package/src/charts/crosshair.svelte +35 -0
  158. package/src/charts/dashboard/chart.ts +5 -0
  159. package/src/charts/dashboard/dashboard-empty.svelte +55 -0
  160. package/src/charts/dashboard/dashboard-section.svelte +58 -0
  161. package/src/charts/dashboard/data-panel.svelte +81 -0
  162. package/src/charts/dashboard/data-sort-head.svelte +102 -0
  163. package/src/charts/dashboard/data-table.svelte +278 -0
  164. package/src/charts/dashboard/data-table.ts +26 -0
  165. package/src/charts/dashboard/index.ts +25 -0
  166. package/src/charts/dashboard/panel-grid.svelte +43 -0
  167. package/src/charts/dashboard/period-filter.svelte +53 -0
  168. package/src/charts/dashboard/rating-chip.svelte +35 -0
  169. package/src/charts/dashboard/stat-band.svelte +47 -0
  170. package/src/charts/dashboard/stat-tile.svelte +176 -0
  171. package/src/charts/dashboard/windows.ts +8 -0
  172. package/src/charts/donut-chart.css +56 -0
  173. package/src/charts/donut-chart.svelte +158 -0
  174. package/src/charts/edge-fade.svelte +43 -0
  175. package/src/charts/edge-fade.ts +16 -0
  176. package/src/charts/figure.css +100 -0
  177. package/src/charts/figure.svelte +122 -0
  178. package/src/charts/gauge-chart.css +94 -0
  179. package/src/charts/gauge-chart.svelte +302 -0
  180. package/src/charts/hover-band.test.ts +30 -0
  181. package/src/charts/hover-band.ts +21 -0
  182. package/src/charts/hover-focus.svelte +85 -0
  183. package/src/charts/index.ts +31 -0
  184. package/src/charts/legend.css +136 -0
  185. package/src/charts/legend.svelte +112 -0
  186. package/src/charts/line-chart.css +26 -0
  187. package/src/charts/line-chart.svelte +256 -0
  188. package/src/charts/motion.ts +19 -0
  189. package/src/charts/number-chart.css +179 -0
  190. package/src/charts/number-chart.svelte +187 -0
  191. package/src/charts/primitives/chart-container.svelte +57 -0
  192. package/src/charts/primitives/chart-style.svelte +36 -0
  193. package/src/charts/primitives/chart-tooltip.svelte +161 -0
  194. package/src/charts/primitives/chart-utils.ts +54 -0
  195. package/src/charts/primitives/index.ts +11 -0
  196. package/src/charts/reference-line.css +18 -0
  197. package/src/charts/reference-line.svelte +31 -0
  198. package/src/charts/reveal.svelte +61 -0
  199. package/src/charts/ring-chart.css +104 -0
  200. package/src/charts/ring-chart.svelte +166 -0
  201. package/src/charts/sparkline.css +45 -0
  202. package/src/charts/sparkline.svelte +141 -0
  203. package/src/charts/stat-card.css +105 -0
  204. package/src/charts/stat-card.svelte +267 -0
  205. package/src/charts/time-series-chart.css +28 -0
  206. package/src/charts/time-series-chart.svelte +179 -0
  207. package/src/charts/trend-pill.css +38 -0
  208. package/src/charts/trend-pill.svelte +48 -0
  209. package/src/components/copy/copy-button.svelte +1 -1
  210. package/src/components/docs/doc-page.svelte +1 -1
  211. package/src/components/docs/on-this-page.svelte +2 -2
  212. package/src/components/list-page/empty-state.svelte +1 -1
  213. package/src/components/list-page/filter-popover.svelte +1 -1
  214. package/src/components/list-page/section-label.svelte +1 -1
  215. package/src/components/segmented/segment.svelte +4 -4
  216. package/src/components/theme/theme-provider.svelte +6 -4
  217. package/src/components/toolbar/index.ts +5 -0
  218. package/src/components/toolbar/toolbar.svelte +168 -0
  219. package/src/components/toolbar/toolbar.test.ts +66 -0
  220. package/src/editor/README.md +58 -0
  221. package/src/editor/ambient.d.ts +1 -0
  222. package/src/editor/block-id.ts +77 -0
  223. package/src/editor/block-ops.test.ts +237 -0
  224. package/src/editor/block-ops.ts +157 -0
  225. package/src/editor/content.css +418 -0
  226. package/src/editor/editor-toolbar.svelte +303 -0
  227. package/src/editor/editor.svelte +462 -0
  228. package/src/editor/extensions.ts +145 -0
  229. package/src/editor/image-toolbar.svelte +123 -0
  230. package/src/editor/index.ts +17 -0
  231. package/src/editor/inline-editor.svelte +261 -0
  232. package/src/editor/markdown-escaping.test.ts +158 -0
  233. package/src/editor/markdown-marks.test.ts +89 -0
  234. package/src/editor/markdown-test-helpers.ts +13 -0
  235. package/src/editor/markdown-youtube.test.ts +93 -0
  236. package/src/editor/markdown.test.ts +250 -0
  237. package/src/editor/markdown.ts +511 -0
  238. package/src/editor/renderer.svelte +92 -0
  239. package/src/editor/serializer.test.ts +133 -0
  240. package/src/editor/serializer.ts +370 -0
  241. package/src/editor/slash-items.ts +115 -0
  242. package/src/editor/slash-menu.svelte +159 -0
  243. package/src/editor/youtube-url.ts +35 -0
  244. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +13 -13
  245. package/src/lib/result.ts +19 -0
  246. package/src/onboarding/README.md +120 -0
  247. package/src/onboarding/define-tours.test.ts +50 -0
  248. package/src/onboarding/define-tours.ts +31 -0
  249. package/src/onboarding/demo.svelte +86 -0
  250. package/src/onboarding/index.ts +12 -0
  251. package/src/onboarding/onboarding-provider.svelte +120 -0
  252. package/src/onboarding/onboarding.svelte.ts +24 -0
  253. package/src/onboarding/pick.test.ts +79 -0
  254. package/src/onboarding/pick.ts +36 -0
  255. package/src/onboarding/stores.test.ts +103 -0
  256. package/src/onboarding/stores.ts +58 -0
  257. package/src/onboarding/tour-card.svelte +70 -0
  258. package/src/onboarding/tour-runner.svelte +197 -0
  259. package/src/onboarding/types.ts +34 -0
  260. package/src/onboarding/wait.ts +29 -0
  261. package/src/shadcn/command/command-input.svelte +1 -1
  262. package/src/slides/Controls.svelte +138 -0
  263. package/src/slides/Deck.svelte +271 -0
  264. package/src/slides/Fragment.svelte +71 -0
  265. package/src/slides/Overview.svelte +113 -0
  266. package/src/slides/Presenter.svelte +292 -0
  267. package/src/slides/README.md +89 -0
  268. package/src/slides/Slide.svelte +179 -0
  269. package/src/slides/SlideList.svelte +19 -0
  270. package/src/slides/ambient.d.ts +1 -0
  271. package/src/slides/bridges.ts +69 -0
  272. package/src/slides/deck-controller.svelte.ts +292 -0
  273. package/src/slides/demo-slides.svelte +358 -0
  274. package/src/slides/demo.svelte +176 -0
  275. package/src/slides/export/capture.ts +113 -0
  276. package/src/slides/export/ffmpeg.ts +120 -0
  277. package/src/slides/export/index.ts +9 -0
  278. package/src/slides/export/launch.ts +10 -0
  279. package/src/slides/export/pdf.ts +152 -0
  280. package/src/slides/export/types.ts +48 -0
  281. package/src/slides/index.ts +19 -0
  282. package/src/slides/labels.ts +48 -0
  283. package/src/slides/layout.ts +51 -0
  284. package/src/slides/model.ts +47 -0
  285. package/src/slides/navigation.ts +100 -0
  286. package/src/slides/theme.css +205 -0
  287. package/src/slides/types.ts +86 -0
  288. package/src/tasks/README.md +312 -0
  289. package/src/tasks/ambient.d.ts +3 -0
  290. package/src/tasks/board-order.ts +137 -0
  291. package/src/tasks/dates.ts +31 -0
  292. package/src/tasks/define-statuses.ts +32 -0
  293. package/src/tasks/demo.svelte +389 -0
  294. package/src/tasks/detail/detail-demo.svelte +186 -0
  295. package/src/tasks/detail/detail-types.ts +54 -0
  296. package/src/tasks/detail/task-chip.svelte +28 -0
  297. package/src/tasks/detail/task-detail.svelte +315 -0
  298. package/src/tasks/detail/task-property.svelte +17 -0
  299. package/src/tasks/detail/task-thread.svelte +258 -0
  300. package/src/tasks/gantt/gantt-bar.svelte +243 -0
  301. package/src/tasks/gantt/gantt-grid.svelte +94 -0
  302. package/src/tasks/gantt/gantt-group-row.svelte +55 -0
  303. package/src/tasks/gantt/gantt-header.svelte +71 -0
  304. package/src/tasks/gantt/gantt-task-row.svelte +186 -0
  305. package/src/tasks/gantt/gantt.css +22 -0
  306. package/src/tasks/gantt/gantt.test.ts +77 -0
  307. package/src/tasks/gantt/logic.test.ts +163 -0
  308. package/src/tasks/gantt/logic.ts +248 -0
  309. package/src/tasks/inbox/inbox-card.svelte +283 -0
  310. package/src/tasks/inbox/inbox-draft.svelte +84 -0
  311. package/src/tasks/inbox/inbox-logic.ts +55 -0
  312. package/src/tasks/inbox/inbox-statuses.ts +48 -0
  313. package/src/tasks/inbox/inbox-trace.svelte +45 -0
  314. package/src/tasks/inbox/inbox-types.ts +108 -0
  315. package/src/tasks/inbox/inbox.svelte +203 -0
  316. package/src/tasks/inbox/index.ts +23 -0
  317. package/src/tasks/index.ts +85 -0
  318. package/src/tasks/kanban-board.svelte +561 -0
  319. package/src/tasks/kanban-column.svelte +117 -0
  320. package/src/tasks/kanban-drag.ts +38 -0
  321. package/src/tasks/kanban-types.ts +65 -0
  322. package/src/tasks/presets/sales.ts +16 -0
  323. package/src/tasks/record/demo-data.ts +286 -0
  324. package/src/tasks/record/field-section.svelte +53 -0
  325. package/src/tasks/record/fields.ts +142 -0
  326. package/src/tasks/record/format.ts +87 -0
  327. package/src/tasks/record/gutter.ts +2 -0
  328. package/src/tasks/record/index.ts +54 -0
  329. package/src/tasks/record/info-list.svelte +316 -0
  330. package/src/tasks/record/kinds.ts +42 -0
  331. package/src/tasks/record/logic.ts +324 -0
  332. package/src/tasks/record/next-up.svelte +70 -0
  333. package/src/tasks/record/project-list.svelte +133 -0
  334. package/src/tasks/record/record-demo.svelte +324 -0
  335. package/src/tasks/record/record-header.svelte +70 -0
  336. package/src/tasks/record/record-shell.svelte +138 -0
  337. package/src/tasks/record/record-stats.svelte +48 -0
  338. package/src/tasks/record/record-timeline.svelte +231 -0
  339. package/src/tasks/record/record.test.ts +350 -0
  340. package/src/tasks/record/steps-rail.svelte +474 -0
  341. package/src/tasks/record/types.ts +280 -0
  342. package/src/tasks/record/where-we-are.svelte +121 -0
  343. package/src/tasks/reorder.ts +19 -0
  344. package/src/tasks/side-panel-types.ts +16 -0
  345. package/src/tasks/side-panel.svelte +193 -0
  346. package/src/tasks/status-badge.svelte +63 -0
  347. package/src/tasks/status-dot.svelte +228 -0
  348. package/src/tasks/status-icon.svelte +30 -0
  349. package/src/tasks/status-types.ts +40 -0
  350. package/src/tasks/task-gantt.svelte +230 -0
  351. package/src/tasks/task-list.svelte +470 -0
  352. package/src/tasks/task-types.ts +33 -0
  353. package/src/tasks/time/duration.ts +69 -0
  354. package/src/tasks/time/time-chip.svelte +62 -0
  355. package/src/tasks/time/time-dot.svelte +58 -0
  356. package/src/tasks/time/time-ring.svelte +74 -0
  357. package/src/tasks/time/time-tracker.svelte +565 -0
  358. package/src/tasks/time/time-types.ts +74 -0
  359. package/src/tasks/use-kanban-dnd.svelte.ts +173 -0
  360. package/src/tokens/base.css +5 -5
  361. package/src/tokens/palette.css +115 -120
@@ -0,0 +1,104 @@
1
+ /* Styles for RingChart. Plain stylesheet (see area-chart.css for why).
2
+ Selectors are namespaced under `.hr-ring` (the generic ones below would
3
+ otherwise collide with GaugeChart's identical names once they are no longer
4
+ Svelte-scoped). */
5
+
6
+ .hr-ring {
7
+ position: relative;
8
+ display: inline-grid;
9
+ place-items: center;
10
+ aspect-ratio: 1;
11
+ container-type: size;
12
+ }
13
+
14
+ .hr-ring svg {
15
+ grid-area: 1 / 1;
16
+ width: 100%;
17
+ height: 100%;
18
+ overflow: visible;
19
+ }
20
+
21
+ .hr-ring .track {
22
+ opacity: 0.14;
23
+ }
24
+
25
+ .hr-ring .fill {
26
+ stroke-dasharray: 100;
27
+ cursor: pointer;
28
+ outline: none;
29
+ transition:
30
+ stroke-dashoffset var(--chart-motion-slow, 1.1s)
31
+ var(--chart-ease-out, cubic-bezier(0.22, 1, 0.36, 1)),
32
+ opacity 200ms ease;
33
+ }
34
+
35
+ .hr-ring .fill.animate {
36
+ animation: hr-ring-sweep var(--chart-motion-reveal, 1100ms)
37
+ var(--chart-ease-reveal, cubic-bezier(0.85, 0, 0.15, 1)) both;
38
+ }
39
+
40
+ @media (hover: hover) and (pointer: fine) {
41
+ .hr-ring.glow .fill:hover {
42
+ filter: drop-shadow(0 0 5px var(--ring-color));
43
+ }
44
+ }
45
+
46
+ @keyframes hr-ring-sweep {
47
+ from {
48
+ stroke-dashoffset: 100;
49
+ }
50
+ }
51
+
52
+ .hr-ring .center {
53
+ grid-area: 1 / 1;
54
+ display: flex;
55
+ flex-direction: column;
56
+ align-items: center;
57
+ justify-content: center;
58
+ /* WHY: keep the readout inside the innermost ring's clear center so the
59
+ number never collides with the green ring. */
60
+ max-width: 40cqmin;
61
+ pointer-events: none;
62
+ text-align: center;
63
+ }
64
+
65
+ /* WHY: a custom center fills the innermost ring's clear area instead of the
66
+ readout's narrower cap. */
67
+ .hr-ring .center.custom {
68
+ max-width: none;
69
+ }
70
+
71
+ .hr-ring .value {
72
+ font-size: max(0.75rem, 9.5cqmin);
73
+ font-weight: 650;
74
+ line-height: 1;
75
+ letter-spacing: -0.02em;
76
+ font-variant-numeric: tabular-nums;
77
+ color: var(--foreground);
78
+ }
79
+
80
+ .hr-ring .label {
81
+ margin-top: 1.5cqmin;
82
+ font-size: max(0.5rem, 5cqmin);
83
+ font-weight: 500;
84
+ color: var(--muted-foreground);
85
+ animation: hr-ring-label-in 180ms ease-out both;
86
+ }
87
+
88
+ @keyframes hr-ring-label-in {
89
+ from {
90
+ opacity: 0;
91
+ transform: translateY(2px);
92
+ }
93
+ }
94
+
95
+ @media (prefers-reduced-motion: reduce) {
96
+ .hr-ring .fill,
97
+ .hr-ring .fill.animate {
98
+ transition: opacity 120ms ease;
99
+ animation: none;
100
+ }
101
+ .hr-ring .label {
102
+ animation: none;
103
+ }
104
+ }
@@ -0,0 +1,166 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import "./ring-chart.css";
6
+ import type { Snippet } from "svelte";
7
+ import { cn } from "../lib/utils.js";
8
+ import AnimatedValue from "./animated-value.svelte";
9
+
10
+ // =========================
11
+ // TYPES
12
+ // =========================
13
+ type RingData = {
14
+ label: string;
15
+ value: number;
16
+ /** Denominator for the progress arc. */
17
+ maxValue: number;
18
+ /** CSS color or var. Falls back to --chart-1..5. */
19
+ color?: string;
20
+ };
21
+
22
+ interface Props {
23
+ data: RingData[];
24
+ /** Fixed square size in px. Omit to fill the parent (responsive). */
25
+ size?: number;
26
+ /** Ring thickness in viewBox units. */
27
+ strokeWidth?: number;
28
+ /** Gap between concentric rings in viewBox units. */
29
+ ringGap?: number;
30
+ /** Inner radius of the innermost ring in viewBox units. */
31
+ baseInnerRadius?: number;
32
+ lineCap?: "round" | "butt";
33
+ /** Center label when nothing is hovered. */
34
+ defaultLabel?: string;
35
+ formatValue?: (v: number) => string;
36
+ /** Animate arcs on mount. */
37
+ animate?: boolean;
38
+ /** Glow the hovered ring. */
39
+ showGlow?: boolean;
40
+ /** Hovered ring index, bindable for legend hover-sync. */
41
+ hoveredIndex?: number | null;
42
+ onHoverChange?: (index: number | null) => void;
43
+ minWidth?: number;
44
+ ariaLabel?: string;
45
+ /** Replaces the center readout, for a number that isn't the ring's own value. */
46
+ children?: Snippet;
47
+ class?: string;
48
+ };
49
+
50
+ // =========================
51
+ // PROPS
52
+ // =========================
53
+ let {
54
+ data,
55
+ size,
56
+ strokeWidth = 11,
57
+ ringGap = 6,
58
+ baseInnerRadius = 48,
59
+ lineCap = "round",
60
+ defaultLabel = "Total",
61
+ formatValue = (v) => v.toLocaleString(),
62
+ animate = true,
63
+ showGlow = true,
64
+ hoveredIndex = $bindable(null),
65
+ onHoverChange,
66
+ minWidth = 170,
67
+ ariaLabel,
68
+ children,
69
+ class: className,
70
+ }: Props = $props();
71
+
72
+ // =========================
73
+ // DERIVED
74
+ // =========================
75
+ const rings = $derived.by(() =>
76
+ data.map((d, i) => {
77
+ const radius = baseInnerRadius + i * (strokeWidth + ringGap) + strokeWidth / 2;
78
+ const progress = d.maxValue > 0 ? Math.max(0, Math.min(1, d.value / d.maxValue)) : 0;
79
+ return {
80
+ ...d,
81
+ index: i,
82
+ radius,
83
+ progress,
84
+ color: d.color ?? `var(--chart-${(i % 5) + 1})`,
85
+ };
86
+ }),
87
+ );
88
+ const maxRadius = $derived(
89
+ rings.length ? rings[rings.length - 1].radius + strokeWidth / 2 : baseInnerRadius,
90
+ );
91
+ const vb = $derived(maxRadius * 2 + 4);
92
+ const center = $derived(vb / 2);
93
+ const total = $derived(data.reduce((sum, d) => sum + d.value, 0));
94
+ const hoveredRing = $derived(hoveredIndex != null ? (rings[hoveredIndex] ?? null) : null);
95
+
96
+ // =========================
97
+ // FUNCTIONS
98
+ // =========================
99
+ function setHover(index: number | null) {
100
+ hoveredIndex = index;
101
+ onHoverChange?.(index);
102
+ }
103
+ </script>
104
+
105
+ <!-- =========================== -->
106
+ <!-- MARKUP -->
107
+ <!-- =========================== -->
108
+ <div
109
+ class={cn("hr-ring", className)}
110
+ class:glow={showGlow}
111
+ style:width={size ? `${size}px` : "100%"}
112
+ style:min-width="{minWidth}px"
113
+ role="img"
114
+ aria-label={ariaLabel ?? `${defaultLabel}: ${formatValue(total)}`}
115
+ >
116
+ <svg viewBox="0 0 {vb} {vb}" aria-hidden="true">
117
+ <g transform="rotate(-90 {center} {center})">
118
+ {#each rings as ring (ring.index)}
119
+ <circle
120
+ class="track"
121
+ cx={center}
122
+ cy={center}
123
+ r={ring.radius}
124
+ fill="none"
125
+ stroke={ring.color}
126
+ stroke-width={strokeWidth}
127
+ />
128
+ {/each}
129
+ {#each rings as ring (ring.index)}
130
+ {@const dimmed = hoveredIndex !== null && hoveredIndex !== ring.index}
131
+ <circle
132
+ class="fill"
133
+ role="presentation"
134
+ class:animate
135
+ cx={center}
136
+ cy={center}
137
+ r={ring.radius}
138
+ pathLength="100"
139
+ fill="none"
140
+ stroke={ring.color}
141
+ stroke-width={strokeWidth}
142
+ stroke-linecap={lineCap}
143
+ style:stroke-dashoffset={100 - ring.progress * 100}
144
+ style:opacity={dimmed ? 0.25 : 1}
145
+ style:animation-delay="{160 + ring.index * 120}ms"
146
+ style:--ring-color={ring.color}
147
+ onmouseenter={() => setHover(ring.index)}
148
+ onmouseleave={() => setHover(null)}
149
+ ></circle>
150
+ {/each}
151
+ </g>
152
+ </svg>
153
+
154
+ <div class="center" class:custom={children}>
155
+ {#if children}
156
+ {@render children()}
157
+ {:else}
158
+ <span class="value">
159
+ <AnimatedValue value={hoveredRing ? hoveredRing.value : total} format={formatValue} />
160
+ </span>
161
+ {#key hoveredRing?.label ?? defaultLabel}
162
+ <span class="label">{hoveredRing ? hoveredRing.label : defaultLabel}</span>
163
+ {/key}
164
+ {/if}
165
+ </div>
166
+ </div>
@@ -0,0 +1,45 @@
1
+ /* Styles for Sparkline. Plain stylesheet (see area-chart.css for why).
2
+ Selectors are namespaced under `.hr-sparkline`. */
3
+
4
+ .hr-sparkline {
5
+ display: flex;
6
+ align-items: center;
7
+ gap: 0.625rem;
8
+ width: 100%;
9
+ }
10
+ .hr-sparkline-chart {
11
+ flex: 1 1 auto;
12
+ min-width: 0;
13
+ }
14
+ /* Bare is an ornament inside another control: no inline value, so no gap. */
15
+ .hr-sparkline[data-bare] {
16
+ gap: 0;
17
+ }
18
+ .hr-sparkline-value {
19
+ flex-shrink: 0;
20
+ font-size: 0.8125rem;
21
+ font-weight: 600;
22
+ font-feature-settings: "tnum";
23
+ font-family: ui-monospace, SFMono-Regular, monospace;
24
+ }
25
+
26
+ .hr-sparkline .hr-sparkline-end {
27
+ filter: drop-shadow(0 0 3px currentColor);
28
+ opacity: 0.9;
29
+ transform-box: fill-box;
30
+ transform-origin: center;
31
+ /* WHY: the dot lands as the reveal reaches the end of the line. */
32
+ animation: hr-sparkline-pop var(--duration-fast, 250ms) var(--ease-smooth-out)
33
+ calc(var(--chart-motion-reveal, 1100ms) * 0.85) both;
34
+ }
35
+ @keyframes hr-sparkline-pop {
36
+ from {
37
+ opacity: 0;
38
+ transform: scale(0.9);
39
+ }
40
+ }
41
+ @media (prefers-reduced-motion: reduce) {
42
+ .hr-sparkline .hr-sparkline-end {
43
+ animation: none;
44
+ }
45
+ }
@@ -0,0 +1,141 @@
1
+ <script lang="ts" generics="TData extends Record<string, unknown>">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import "./sparkline.css";
6
+ import { Area, Chart, Highlight, LinearGradient, Points, Spline, Svg, Tooltip } from "layerchart";
7
+ import { curveMonotoneX } from "d3-shape";
8
+ import Reveal from "./reveal.svelte";
9
+
10
+ // =========================
11
+ // TYPES
12
+ // =========================
13
+ interface Props {
14
+ data: TData[];
15
+ xKey: keyof TData & string;
16
+ yKey: keyof TData & string;
17
+ color?: string;
18
+ /** Pixels, or any CSS length such as `calc(30px * var(--scale))`. */
19
+ height?: number | string;
20
+ smooth?: boolean;
21
+ /** Show a dot at the latest point */
22
+ endCap?: boolean;
23
+ /** Show the latest value as inline text */
24
+ showLast?: boolean;
25
+ /**
26
+ * Ornament mode: no tooltip, no highlight, no end cap, and the plot runs to the edges of its box.
27
+ */
28
+
29
+ /*
30
+ * For a line inside a control that already states the number and owns the pointer, such as a stat card that links.
31
+ */
32
+ bare?: boolean;
33
+ formatValue?: (v: number) => string;
34
+ class?: string;
35
+ };
36
+
37
+ // =========================
38
+ // PROPS
39
+ // =========================
40
+ let {
41
+ data,
42
+ xKey,
43
+ yKey,
44
+ color = "var(--chart-1)",
45
+ height = 44,
46
+ smooth = true,
47
+ endCap = true,
48
+ showLast = false,
49
+ bare = false,
50
+ formatValue = (v) => v.toLocaleString(),
51
+ class: className,
52
+ }: Props = $props();
53
+
54
+ // =========================
55
+ // STATE
56
+ // =========================
57
+ let active = $state<TData | null>(null);
58
+
59
+ // ========================= FUNCTIONS ========================= WHY: called from the tooltip header's value expression so it re-runs on every active-point change; an unread {@const} side effect only evaluates once.
60
+
61
+ // The microtask defers the state write out of template evaluation.
62
+ function trackActive(d: TData): string {
63
+ queueMicrotask(() => {
64
+ active = d;
65
+ });
66
+ return String(d[xKey]);
67
+ }
68
+
69
+ // =========================
70
+ // DERIVED
71
+ // =========================
72
+ const uid = `hr-spark-${Math.random().toString(36).slice(2, 8)}`;
73
+ const curve = $derived(smooth ? curveMonotoneX : undefined);
74
+ const last = $derived(data.length > 0 ? data[data.length - 1] : null);
75
+ const display = $derived(active ?? last);
76
+ const dot = $derived(endCap && !bare);
77
+ // Bare runs to the edges: the fill reaches the bottom of the box and the line keeps three pixels of head so its stroke is not cut at the series
78
+
79
+ // maximum.
80
+ const padding = $derived(
81
+ bare
82
+ ? { top: 3, bottom: 0, left: 0, right: 0 }
83
+ : { top: 4, bottom: 4, left: 0, right: dot ? 6 : 0 },
84
+ );
85
+ </script>
86
+
87
+ <!-- =========================== -->
88
+ <!-- SPARKLINE -->
89
+ <!-- =========================== -->
90
+ <div
91
+ class="hr-sparkline {className ?? ''}"
92
+ role={bare ? undefined : "img"}
93
+ aria-label={bare ? undefined : "Sparkline"}
94
+ aria-hidden={bare ? "true" : undefined}
95
+ data-bare={bare ? "" : undefined}
96
+ onpointerleave={() => (active = null)}
97
+ >
98
+ {#if data.length > 0}
99
+ <div class="hr-sparkline-chart" style:height={typeof height === "number" ? `${height}px` : height}>
100
+ <Chart {data} x={xKey} y={yKey} {padding} tooltipContext={{ mode: "bisect-x" }}>
101
+ <Svg>
102
+ <LinearGradient id={uid} vertical>
103
+ {#snippet stopsContent()}
104
+ <stop offset="0%" stop-color={color} stop-opacity="0.38" />
105
+ <stop offset="100%" stop-color={color} stop-opacity="0" />
106
+ {/snippet}
107
+ </LinearGradient>
108
+ <Reveal bleed={3}>
109
+ <!-- WHY no `y0`: it defaults to the bottom of the plot, which is what
110
+ a fill under a sparkline wants whatever its domain is. -->
111
+ <Area y1={yKey} {curve} fill="url(#{uid})" />
112
+ <Spline {curve} style="stroke: {color}; fill: none" stroke-width="1.5" />
113
+ </Reveal>
114
+ {#if dot && last}
115
+ <!-- WHY: Points resolves its own data and hands the inner circle pixel coords; Circle's data mode clobbers the sibling Spline's data in -->
116
+ <!-- this layerchart prerelease (and a bare object left the dot at the chart origin). -->
117
+ <Points data={[last]} r={3} fill={color} class="hr-sparkline-end" />
118
+ {/if}
119
+ {#if !bare}
120
+ <Highlight points lines={false} />
121
+ {/if}
122
+ </Svg>
123
+ {#if !bare}
124
+ <Tooltip.Root>
125
+ {#snippet children({ data: d })}
126
+ <Tooltip.Header value={trackActive(d as TData)} />
127
+ <Tooltip.List>
128
+ <Tooltip.Item label={String(yKey)} value={formatValue(Number(d[yKey]))} />
129
+ </Tooltip.List>
130
+ {/snippet}
131
+ </Tooltip.Root>
132
+ {/if}
133
+ </Chart>
134
+ </div>
135
+ {#if showLast && display}
136
+ <span class="hr-sparkline-value" style:color>
137
+ {formatValue(Number(display[yKey]))}
138
+ </span>
139
+ {/if}
140
+ {/if}
141
+ </div>
@@ -0,0 +1,105 @@
1
+ /* Styles for StatCard. Plain stylesheet (see area-chart.css for why). All
2
+ selectors are already namespaced under `.hr-stat-card`. */
3
+
4
+ .hr-stat-card {
5
+ display: flex;
6
+ flex-direction: column;
7
+ gap: 0.375rem;
8
+ padding: 1.25rem 1.5rem;
9
+ border: 1px solid var(--border, oklch(0.92 0.004 286));
10
+ border-radius: 1rem;
11
+ background: var(--card, white);
12
+ box-shadow: var(--chart-card-shadow);
13
+ min-width: 0;
14
+ /* WHY: clips the edge-to-edge chart to the card's rounded corners. */
15
+ overflow: hidden;
16
+ transition: border-color var(--chart-motion, 250ms) var(--chart-ease-out, ease);
17
+ }
18
+ @media (hover: hover) and (pointer: fine) {
19
+ .hr-stat-card:hover {
20
+ border-color: color-mix(in srgb, var(--ring, var(--chart-1)) 28%, var(--border, transparent));
21
+ }
22
+ }
23
+ /* WHY: a bare figure, not a card. Dropping the chrome avoids a card-in-card
24
+ when the figure is placed inside another surface, and lets the chart be the
25
+ whole element. */
26
+ .hr-stat-figure-only {
27
+ padding: 0;
28
+ gap: 0;
29
+ border: 0;
30
+ border-radius: 0;
31
+ background: none;
32
+ box-shadow: none;
33
+ }
34
+
35
+ /* WHY: the gauge variant sits the figure and a radial gauge side by side
36
+ instead of bleeding a chart to the bottom edge; no negative margins here. */
37
+ .hr-stat-gauge-row {
38
+ display: flex;
39
+ flex-wrap: wrap;
40
+ align-items: center;
41
+ gap: 1rem;
42
+ }
43
+ /* WHY the floor: below the width a gauge and a figure share, the row wraps the
44
+ gauge to its own line rather than squeezing the figure to nothing. */
45
+ .hr-stat-gauge-row .hr-stat-gauge-figure {
46
+ flex: 1 1 auto;
47
+ min-width: 6rem;
48
+ }
49
+ .hr-stat-gauge-row .hr-gauge {
50
+ flex: 0 0 auto;
51
+ }
52
+
53
+ /* WHY: the chart bleeds past the card padding to the left/right/bottom edges,
54
+ matching bklit's edge-to-edge stat-card chart; overflow:hidden on the card
55
+ clips the corners to the radius. */
56
+ .hr-stat-chart {
57
+ margin: 0.5rem -1.5rem -1.25rem;
58
+ width: auto;
59
+ }
60
+ .hr-stat-figure-only .hr-stat-chart {
61
+ margin: 0;
62
+ }
63
+
64
+ .hr-stat-card .hr-stat-spline {
65
+ transition: opacity var(--chart-motion-fast, 150ms) ease;
66
+ }
67
+ .hr-stat-card .hr-stat-bars {
68
+ transform-box: fill-box;
69
+ transform-origin: bottom;
70
+ animation: hr-stat-grow var(--chart-motion-reveal, 1100ms)
71
+ var(--chart-ease-reveal, cubic-bezier(0.85, 0, 0.15, 1)) both;
72
+ }
73
+ .hr-stat-card .hr-stat-bars:nth-of-type(2) {
74
+ animation-delay: var(--chart-stagger, 30ms);
75
+ }
76
+ .hr-stat-card .hr-stat-bars:nth-of-type(3) {
77
+ animation-delay: calc(var(--chart-stagger, 30ms) * 2);
78
+ }
79
+ .hr-stat-card .hr-stat-bars:nth-of-type(4) {
80
+ animation-delay: calc(var(--chart-stagger, 30ms) * 3);
81
+ }
82
+ .hr-stat-card .hr-stat-bars:nth-of-type(5) {
83
+ animation-delay: calc(var(--chart-stagger, 30ms) * 4);
84
+ }
85
+ .hr-stat-card .hr-stat-bars:nth-of-type(6) {
86
+ animation-delay: calc(var(--chart-stagger, 30ms) * 5);
87
+ }
88
+ .hr-stat-card .hr-stat-bars:nth-of-type(n + 7) {
89
+ animation-delay: calc(var(--chart-stagger, 30ms) * 6);
90
+ }
91
+ @keyframes hr-stat-grow {
92
+ from {
93
+ transform: scaleY(0);
94
+ }
95
+ }
96
+ .hr-stat-card .hr-stat-bar-active {
97
+ filter: brightness(1.08);
98
+ pointer-events: none;
99
+ }
100
+
101
+ @media (prefers-reduced-motion: reduce) {
102
+ .hr-stat-card .hr-stat-bars {
103
+ animation: none;
104
+ }
105
+ }