@heyramzi/ui 6.1.0 → 8.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 (332) hide show
  1. package/COMPONENTS.md +32 -40
  2. package/README.md +10 -4
  3. package/package.json +31 -29
  4. package/scripts/sync-shadcn.mjs +29 -16
  5. package/scripts/sync-transitions.mjs +11 -11
  6. package/src/ai/ai-button/AiButton.svelte +86 -0
  7. package/src/ai/ai-button/index.ts +4 -0
  8. package/src/ai/confirmation/Confirmation.svelte +8 -8
  9. package/src/ai/confirmation/confirmation-context.svelte.ts +2 -2
  10. package/src/ai/generation-progress/GenerationProgress.svelte +354 -0
  11. package/src/ai/generation-progress/index.ts +7 -0
  12. package/src/ai/index.ts +16 -24
  13. package/src/ai/prompt-input/PromptInput.svelte +19 -1
  14. package/src/ai/prompt-input/PromptInputSubmit.svelte +9 -3
  15. package/src/ai/reasoning/Reasoning.svelte +11 -8
  16. package/src/ambient.d.ts +4 -0
  17. package/src/animations/README.md +17 -17
  18. package/src/animations/index.css +3 -14
  19. package/src/animations/upstream-coverage.test.ts +1 -1
  20. package/src/components/copy/clipboard.test.ts +206 -0
  21. package/src/components/copy/clipboard.ts +49 -0
  22. package/src/components/copy/copied.svelte.ts +39 -0
  23. package/src/components/copy/copy-button.svelte +175 -0
  24. package/src/components/copy/copy-button.test.ts +110 -0
  25. package/src/components/copy/index.ts +3 -0
  26. package/src/components/data-table/data-table.test.ts +134 -0
  27. package/src/components/data-table/data-table.ts +77 -0
  28. package/src/components/data-table/index.ts +20 -0
  29. package/src/components/data-table/infinite-scroll.svelte +69 -0
  30. package/src/components/data-table/pagination.svelte +62 -0
  31. package/src/components/data-table/row-arrow.svelte +28 -0
  32. package/src/components/data-table/row-context-menu.svelte +106 -0
  33. package/src/components/data-table/row-context-menu.test.ts +18 -0
  34. package/src/components/data-table/sortable-head.svelte +56 -0
  35. package/src/components/data-table/table-skeleton.svelte +83 -0
  36. package/src/components/hold-to-confirm/hold-to-confirm.svelte +232 -0
  37. package/src/components/hold-to-confirm/index.ts +1 -0
  38. package/src/components/icon/icon.svelte +23 -0
  39. package/src/components/icon/index.ts +1 -0
  40. package/src/components/language-flag/index.ts +1 -0
  41. package/src/components/language-flag/language-flag.svelte +60 -0
  42. package/src/components/list-page/detail-empty.svelte +50 -0
  43. package/src/components/list-page/detail-header.svelte +107 -0
  44. package/src/components/list-page/detail-panel.svelte +90 -0
  45. package/src/components/list-page/detail-row.svelte +36 -0
  46. package/src/components/list-page/detail-stack.svelte +33 -0
  47. package/src/components/list-page/empty-state.svelte +91 -0
  48. package/src/components/list-page/filter-item.svelte +49 -0
  49. package/src/components/list-page/filter-popover.svelte +88 -0
  50. package/src/components/list-page/filter-section.ts +12 -0
  51. package/src/components/list-page/grouped-list.svelte +116 -0
  52. package/src/components/list-page/index.ts +28 -0
  53. package/src/components/list-page/list-page-shell.svelte +195 -0
  54. package/src/components/list-page/list-table.ts +19 -0
  55. package/src/components/list-page/master-detail.svelte +206 -0
  56. package/src/components/list-page/master-detail.test.ts +37 -0
  57. package/src/components/list-page/notice-bar.svelte +68 -0
  58. package/src/components/list-page/prev-next.svelte +104 -0
  59. package/src/components/list-page/queue-rail.ts +25 -0
  60. package/src/components/list-page/rail-body.svelte +29 -0
  61. package/src/components/list-page/rail-empty.svelte +51 -0
  62. package/src/components/list-page/rail-head.svelte +49 -0
  63. package/src/components/list-page/rail-row.svelte +163 -0
  64. package/src/components/list-page/section-host.ts +24 -0
  65. package/src/components/list-page/section-label.svelte +39 -0
  66. package/src/components/list-page/step.test.ts +21 -0
  67. package/src/components/list-page/step.ts +10 -0
  68. package/src/components/list-page/toolbar-search.svelte +63 -0
  69. package/src/components/list-page/toolbar-trigger.ts +23 -0
  70. package/src/components/list-page/types.ts +1 -0
  71. package/src/components/locale-switch/index.ts +1 -0
  72. package/src/components/locale-switch/locale-switch.svelte +59 -0
  73. package/src/components/mark/index.ts +1 -0
  74. package/src/components/mark/mark.svelte +70 -0
  75. package/src/components/open-in/index.ts +6 -0
  76. package/src/components/open-in/open-in.svelte +106 -0
  77. package/src/components/picker/index.ts +4 -0
  78. package/src/components/picker/mark-picker.svelte +84 -0
  79. package/src/components/picker/picker.svelte +143 -0
  80. package/src/components/picker/trigger.ts +42 -0
  81. package/src/components/picker/types.ts +26 -0
  82. package/src/components/segmented/animated-tabs.svelte +175 -0
  83. package/src/components/segmented/collapse.test.ts +49 -0
  84. package/src/components/segmented/collapse.ts +39 -0
  85. package/src/components/segmented/index.ts +41 -0
  86. package/src/components/segmented/section-runs.svelte +79 -0
  87. package/src/components/segmented/segment.svelte +106 -0
  88. package/src/components/segmented/segmented-filter.svelte +125 -0
  89. package/src/components/segmented/segmented-nav.svelte +82 -0
  90. package/src/components/segmented/segmented-overflow.svelte +85 -0
  91. package/src/components/segmented/segmented-track.svelte +181 -0
  92. package/src/components/segmented/segmented.test.ts +20 -0
  93. package/src/components/segmented/segmented.ts +80 -0
  94. package/src/components/star-rating-input/index.ts +9 -0
  95. package/src/components/star-rating-input/star-rating-input.svelte +256 -0
  96. package/src/components/svg/index.ts +1 -0
  97. package/src/components/svg/svg.svelte +9 -0
  98. package/src/effects/border-beam/BorderBeam.svelte +1 -1
  99. package/src/effects/image-comparison/ImageComparison.css +42 -8
  100. package/src/effects/image-comparison/ImageComparison.svelte +138 -21
  101. package/src/effects/image-comparison/compare.test.ts +17 -0
  102. package/src/effects/image-comparison/compare.ts +5 -0
  103. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +338 -0
  104. package/src/effects/magic-card/MagicCard.css +47 -0
  105. package/src/effects/magic-card/MagicCard.svelte +57 -0
  106. package/src/effects/marquee/Marquee.css +27 -1
  107. package/src/effects/marquee/Marquee.svelte +67 -3
  108. package/src/effects/reveal/index.ts +2 -1
  109. package/src/effects/reveal/observe.test.ts +60 -0
  110. package/src/effects/reveal/observe.ts +33 -1
  111. package/src/effects/reveal/types.ts +7 -0
  112. package/src/effects/theme-toggle/ThemeToggle.css +199 -6
  113. package/src/effects/theme-toggle/ThemeToggle.svelte +51 -26
  114. package/src/gallery.test.ts +52 -0
  115. package/src/index.ts +5 -1
  116. package/src/lib/panel.ts +7 -1
  117. package/src/lib/sliding-pill.ts +68 -0
  118. package/src/lib/theme-transition.ts +41 -13
  119. package/src/lib/tone.ts +48 -0
  120. package/src/shadcn/accordion/accordion-trigger.svelte +15 -6
  121. package/src/shadcn/alert/alert.svelte +3 -1
  122. package/src/shadcn/badge/badge.svelte +8 -7
  123. package/src/shadcn/breadcrumb/breadcrumb-ellipsis.svelte +2 -2
  124. package/src/shadcn/breadcrumb/breadcrumb-separator.svelte +2 -2
  125. package/src/shadcn/calendar/calendar-month-select.svelte +2 -2
  126. package/src/shadcn/calendar/calendar-next-button.svelte +2 -2
  127. package/src/shadcn/calendar/calendar-prev-button.svelte +2 -2
  128. package/src/shadcn/calendar/calendar-year-select.svelte +2 -2
  129. package/src/shadcn/calendar/calendar.svelte +6 -0
  130. package/src/shadcn/code-block/code-block-actions.svelte +23 -0
  131. package/src/shadcn/code-block/code-block-content.svelte +55 -0
  132. package/src/shadcn/code-block/code-block-copy-button.svelte +10 -0
  133. package/src/shadcn/code-block/code-block-footer.svelte +20 -0
  134. package/src/shadcn/code-block/code-block-header.svelte +23 -0
  135. package/src/shadcn/code-block/code-block-line.svelte +71 -0
  136. package/src/shadcn/code-block/code-block-title.svelte +23 -0
  137. package/src/shadcn/code-block/code-block.svelte +47 -0
  138. package/src/shadcn/code-block/index.ts +42 -0
  139. package/src/shadcn/command/index.ts +3 -0
  140. package/src/shadcn/field/index.ts +3 -0
  141. package/src/shadcn/field/simple-field.svelte +71 -0
  142. package/src/shadcn/field/simple-field.test.ts +47 -0
  143. package/src/shadcn/hooks/highlighter.ts +132 -0
  144. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +98 -0
  145. package/src/shadcn/kbd/index.ts +10 -0
  146. package/src/shadcn/kbd/kbd-group.svelte +20 -0
  147. package/src/shadcn/kbd/kbd.svelte +23 -0
  148. package/src/shadcn/message-scroller/index.ts +28 -0
  149. package/src/shadcn/message-scroller/message-scroller-button.svelte +52 -0
  150. package/src/shadcn/message-scroller/message-scroller-content.svelte +17 -0
  151. package/src/shadcn/message-scroller/message-scroller-item.svelte +22 -0
  152. package/src/shadcn/message-scroller/message-scroller-provider.svelte +7 -0
  153. package/src/shadcn/message-scroller/message-scroller-viewport.svelte +20 -0
  154. package/src/shadcn/message-scroller/message-scroller.svelte +20 -0
  155. package/src/shadcn/radio-group/radio-group-item.svelte +3 -2
  156. package/src/shadcn/sheet/sheet-content.svelte +1 -0
  157. package/src/shadcn/sheet/sheet.css +48 -0
  158. package/src/shadcn/sonner/sonner.svelte +7 -10
  159. package/src/shadcn/tabs/tabs-list.svelte +13 -2
  160. package/src/shadcn/tabs/tabs-trigger.svelte +1 -1
  161. package/src/shadcn/toggle-group/toggle-group.svelte +10 -3
  162. package/src/shadcn/trace/index.ts +29 -0
  163. package/src/shadcn/trace/trace-collapsible-content.svelte +29 -0
  164. package/src/shadcn/trace/trace-collapsible-trigger.svelte +29 -0
  165. package/src/shadcn/trace/trace-collapsible.svelte +20 -0
  166. package/src/shadcn/trace/trace-description.svelte +20 -0
  167. package/src/shadcn/trace/trace-icon.svelte +24 -0
  168. package/src/shadcn/trace/trace-line.svelte +31 -0
  169. package/src/shadcn/trace/trace-title.svelte +20 -0
  170. package/src/shadcn/trace/trace.svelte +23 -0
  171. package/src/ai/composer/Composer.css +0 -108
  172. package/src/ai/composer/Composer.svelte +0 -111
  173. package/src/ai/composer/index.ts +0 -10
  174. package/src/ai/conversation/Conversation.svelte +0 -28
  175. package/src/ai/conversation/ConversationContent.svelte +0 -35
  176. package/src/ai/conversation/ConversationEmptyState.svelte +0 -46
  177. package/src/ai/conversation/ConversationScrollButton.svelte +0 -51
  178. package/src/ai/conversation/index.ts +0 -33
  179. package/src/ai/conversation/stick-to-bottom-context.svelte.ts +0 -131
  180. package/src/animations/accordion.css +0 -58
  181. package/src/animations/icon-swap.css +0 -43
  182. package/src/animations/notification-badge.css +0 -81
  183. package/src/animations/panel.css +0 -86
  184. package/src/animations/reasoning-stream.css +0 -49
  185. package/src/animations/skeleton-reveal.css +0 -97
  186. package/src/animations/streaming-text.css +0 -31
  187. package/src/animations/tabs-sliding.css +0 -83
  188. package/src/animations/texts-reveal.css +0 -70
  189. package/src/animations/tooltip.css +0 -72
  190. package/src/animations/view-transition-reveal.ts +0 -74
  191. package/src/charts/animated-value.css +0 -7
  192. package/src/charts/animated-value.svelte +0 -110
  193. package/src/charts/area-chart.css +0 -29
  194. package/src/charts/area-chart.svelte +0 -195
  195. package/src/charts/bar-chart.css +0 -119
  196. package/src/charts/bar-chart.svelte +0 -269
  197. package/src/charts/charts.css +0 -201
  198. package/src/charts/crosshair.svelte +0 -35
  199. package/src/charts/donut-chart.css +0 -56
  200. package/src/charts/donut-chart.svelte +0 -158
  201. package/src/charts/edge-fade.svelte +0 -43
  202. package/src/charts/edge-fade.ts +0 -16
  203. package/src/charts/figure.css +0 -100
  204. package/src/charts/figure.svelte +0 -122
  205. package/src/charts/gauge-chart.css +0 -94
  206. package/src/charts/gauge-chart.svelte +0 -303
  207. package/src/charts/hover-band.test.ts +0 -30
  208. package/src/charts/hover-band.ts +0 -21
  209. package/src/charts/hover-focus.svelte +0 -85
  210. package/src/charts/index.ts +0 -30
  211. package/src/charts/legend.css +0 -131
  212. package/src/charts/legend.svelte +0 -112
  213. package/src/charts/line-chart.css +0 -26
  214. package/src/charts/line-chart.svelte +0 -256
  215. package/src/charts/motion.ts +0 -19
  216. package/src/charts/number-chart.css +0 -168
  217. package/src/charts/number-chart.svelte +0 -177
  218. package/src/charts/primitives/chart-container.svelte +0 -57
  219. package/src/charts/primitives/chart-style.svelte +0 -36
  220. package/src/charts/primitives/chart-tooltip.svelte +0 -161
  221. package/src/charts/primitives/chart-utils.ts +0 -54
  222. package/src/charts/primitives/index.ts +0 -11
  223. package/src/charts/reveal.svelte +0 -61
  224. package/src/charts/ring-chart.css +0 -96
  225. package/src/charts/ring-chart.svelte +0 -158
  226. package/src/charts/sparkline.css +0 -45
  227. package/src/charts/sparkline.svelte +0 -140
  228. package/src/charts/stat-card.css +0 -103
  229. package/src/charts/stat-card.svelte +0 -267
  230. package/src/charts/time-series-chart.css +0 -28
  231. package/src/charts/time-series-chart.svelte +0 -179
  232. package/src/charts/trend-pill.css +0 -38
  233. package/src/charts/trend-pill.svelte +0 -48
  234. package/src/lib/hooks/is-mobile.svelte.ts +0 -11
  235. package/src/lib/utils.ts +0 -45
  236. package/src/showcase/AiSection.svelte +0 -157
  237. package/src/showcase/AnimationsSection.svelte +0 -1658
  238. package/src/showcase/ChartsSection.svelte +0 -458
  239. package/src/showcase/EffectsSection.svelte +0 -152
  240. package/src/showcase/ShadcnSection.svelte +0 -27
  241. package/src/showcase/ShowcaseImport.svelte +0 -126
  242. package/src/showcase/ShowcaseList.svelte +0 -132
  243. package/src/showcase/ShowcaseNav.svelte +0 -247
  244. package/src/showcase/ShowcaseSection.svelte +0 -112
  245. package/src/showcase/ShowcaseShell.svelte +0 -55
  246. package/src/showcase/ShowcaseStage.svelte +0 -342
  247. package/src/showcase/ShowcaseVariant.svelte +0 -109
  248. package/src/showcase/assets/image-reveal/art-1.jpg +0 -0
  249. package/src/showcase/assets/image-reveal/art-2.jpg +0 -0
  250. package/src/showcase/assets/image-reveal/art-3.jpg +0 -0
  251. package/src/showcase/check.svg +0 -1
  252. package/src/showcase/chevron-arms.svg +0 -2
  253. package/src/showcase/chevron-down.svg +0 -1
  254. package/src/showcase/copy.svg +0 -2
  255. package/src/showcase/demos/ai/confirmation.svelte +0 -59
  256. package/src/showcase/demos/ai/conversation.svelte +0 -59
  257. package/src/showcase/demos/ai/loading-state.svelte +0 -19
  258. package/src/showcase/demos/ai/prompt-input.svelte +0 -57
  259. package/src/showcase/demos/ai/reasoning.svelte +0 -19
  260. package/src/showcase/demos/ai/thinking-dots.svelte +0 -15
  261. package/src/showcase/demos/ai/voice-waveform.svelte +0 -24
  262. package/src/showcase/demos/effects/animated-beam.svelte +0 -50
  263. package/src/showcase/demos/effects/animated-group.svelte +0 -47
  264. package/src/showcase/demos/effects/border-beam-default.svelte +0 -20
  265. package/src/showcase/demos/effects/border-beam-line.svelte +0 -20
  266. package/src/showcase/demos/effects/border-beam-mono.svelte +0 -20
  267. package/src/showcase/demos/effects/border-beam-sunset.svelte +0 -20
  268. package/src/showcase/demos/effects/image-comparison.svelte +0 -25
  269. package/src/showcase/demos/effects/image-reveal.svelte +0 -49
  270. package/src/showcase/demos/effects/in-view.svelte +0 -41
  271. package/src/showcase/demos/effects/integrations-hub.svelte +0 -82
  272. package/src/showcase/demos/effects/magnetic.svelte +0 -31
  273. package/src/showcase/demos/effects/marquee.svelte +0 -33
  274. package/src/showcase/demos/effects/progressive-blur.svelte +0 -47
  275. package/src/showcase/demos/effects/spotlight.svelte +0 -40
  276. package/src/showcase/demos/effects/text-effect.svelte +0 -55
  277. package/src/showcase/demos/shadcn/accordion.svelte +0 -45
  278. package/src/showcase/demos/shadcn/alert-dialog.svelte +0 -23
  279. package/src/showcase/demos/shadcn/alert.svelte +0 -30
  280. package/src/showcase/demos/shadcn/attachment.svelte +0 -71
  281. package/src/showcase/demos/shadcn/avatar.svelte +0 -30
  282. package/src/showcase/demos/shadcn/badge.svelte +0 -26
  283. package/src/showcase/demos/shadcn/breadcrumb.svelte +0 -34
  284. package/src/showcase/demos/shadcn/bubble.svelte +0 -36
  285. package/src/showcase/demos/shadcn/button.svelte +0 -11
  286. package/src/showcase/demos/shadcn/calendar.svelte +0 -8
  287. package/src/showcase/demos/shadcn/card.svelte +0 -39
  288. package/src/showcase/demos/shadcn/checkbox.svelte +0 -39
  289. package/src/showcase/demos/shadcn/collapsible.svelte +0 -20
  290. package/src/showcase/demos/shadcn/command.svelte +0 -48
  291. package/src/showcase/demos/shadcn/context-menu.svelte +0 -54
  292. package/src/showcase/demos/shadcn/demo-harness.svelte +0 -12
  293. package/src/showcase/demos/shadcn/demos.test.ts +0 -20
  294. package/src/showcase/demos/shadcn/dialog.svelte +0 -38
  295. package/src/showcase/demos/shadcn/dropdown-menu.svelte +0 -59
  296. package/src/showcase/demos/shadcn/empty.svelte +0 -29
  297. package/src/showcase/demos/shadcn/field.svelte +0 -115
  298. package/src/showcase/demos/shadcn/hover-card.svelte +0 -32
  299. package/src/showcase/demos/shadcn/index.ts +0 -358
  300. package/src/showcase/demos/shadcn/input-otp.svelte +0 -19
  301. package/src/showcase/demos/shadcn/input.svelte +0 -5
  302. package/src/showcase/demos/shadcn/label.svelte +0 -11
  303. package/src/showcase/demos/shadcn/marker.svelte +0 -30
  304. package/src/showcase/demos/shadcn/message.svelte +0 -75
  305. package/src/showcase/demos/shadcn/popover.svelte +0 -36
  306. package/src/showcase/demos/shadcn/progress.svelte +0 -13
  307. package/src/showcase/demos/shadcn/radio-group.svelte +0 -19
  308. package/src/showcase/demos/shadcn/scroll-area.svelte +0 -18
  309. package/src/showcase/demos/shadcn/select.svelte +0 -29
  310. package/src/showcase/demos/shadcn/separator.svelte +0 -12
  311. package/src/showcase/demos/shadcn/sheet.svelte +0 -48
  312. package/src/showcase/demos/shadcn/skeleton.svelte +0 -11
  313. package/src/showcase/demos/shadcn/slider.svelte +0 -7
  314. package/src/showcase/demos/shadcn/sonner.svelte +0 -18
  315. package/src/showcase/demos/shadcn/spinner.svelte +0 -11
  316. package/src/showcase/demos/shadcn/switch.svelte +0 -9
  317. package/src/showcase/demos/shadcn/table.svelte +0 -76
  318. package/src/showcase/demos/shadcn/tabs.svelte +0 -62
  319. package/src/showcase/demos/shadcn/textarea.svelte +0 -5
  320. package/src/showcase/demos/shadcn/toggle-group.svelte +0 -18
  321. package/src/showcase/demos/shadcn/toggle.svelte +0 -14
  322. package/src/showcase/demos/shadcn/tooltip.svelte +0 -13
  323. package/src/showcase/heart.svg +0 -1
  324. package/src/showcase/index.ts +0 -38
  325. package/src/showcase/kit.test.ts +0 -72
  326. package/src/showcase/kit.ts +0 -17
  327. package/src/showcase/plus.svg +0 -1
  328. package/src/showcase/showcase-shell.css +0 -91
  329. package/src/showcase/stacked-section.css +0 -15
  330. package/src/showcase/success-check.svg +0 -1
  331. package/src/showcase/types.ts +0 -29
  332. package/src/svg-raw.d.ts +0 -7
@@ -1,112 +0,0 @@
1
- <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import "./legend.css";
6
- import { cn } from "@heyramzi/utils";
7
-
8
- // =========================
9
- // TYPES
10
- // =========================
11
- type LegendItem = {
12
- label: string;
13
- value: number;
14
- /** Required when `showProgress` or `showPercentage` is set. */
15
- maxValue?: number;
16
- color: string;
17
- };
18
-
19
- type Props = {
20
- items: LegendItem[];
21
- title?: string;
22
- /** Stacked rows vs inline wrap. */
23
- layout?: "vertical" | "horizontal";
24
- /** Render a progress bar under each row (needs maxValue). */
25
- showProgress?: boolean;
26
- /** Show the value beside each label. */
27
- showValue?: boolean;
28
- /** Append a percentage (needs maxValue). */
29
- showPercentage?: boolean;
30
- formatValue?: (v: number) => string;
31
- formatPercentage?: (p: number) => string;
32
- /** Hovered index, bindable for chart hover-sync. */
33
- hoveredIndex?: number | null;
34
- onHoverChange?: (index: number | null) => void;
35
- class?: string;
36
- };
37
-
38
- // =========================
39
- // PROPS
40
- // =========================
41
- let {
42
- items,
43
- title,
44
- layout = "vertical",
45
- showProgress = false,
46
- showValue = true,
47
- showPercentage = false,
48
- formatValue = (v) => v.toLocaleString(),
49
- formatPercentage = (p) => `${p.toFixed(0)}%`,
50
- hoveredIndex = $bindable(null),
51
- onHoverChange,
52
- class: className,
53
- }: Props = $props();
54
-
55
- // =========================
56
- // FUNCTIONS
57
- // =========================
58
- function pct(item: LegendItem): number {
59
- if (!item.maxValue || item.maxValue <= 0) return 0;
60
- return Math.max(0, Math.min(100, (item.value / item.maxValue) * 100));
61
- }
62
-
63
- function setHover(index: number | null) {
64
- hoveredIndex = index;
65
- onHoverChange?.(index);
66
- }
67
- </script>
68
-
69
- <!-- =========================== -->
70
- <!-- MARKUP -->
71
- <!-- =========================== -->
72
- <div class={cn("hr-legend", className)} class:horizontal={layout === "horizontal"}>
73
- {#if title}
74
- <p class="hr-legend-title">{title}</p>
75
- {/if}
76
- <ul class="hr-legend-items">
77
- {#each items as item, i (item.label + i)}
78
- {@const dimmed = hoveredIndex !== null && hoveredIndex !== i}
79
- <li>
80
- <button
81
- type="button"
82
- class="hr-legend-item"
83
- class:dimmed
84
- onmouseenter={() => setHover(i)}
85
- onmouseleave={() => setHover(null)}
86
- onfocus={() => setHover(i)}
87
- onblur={() => setHover(null)}
88
- >
89
- <span class="hr-legend-marker" style:background={item.color}></span>
90
- <span class="hr-legend-label">{item.label}</span>
91
- {#if showValue}
92
- <span class="hr-legend-value">
93
- {formatValue(item.value)}{#if showPercentage && item.maxValue}<span
94
- class="hr-legend-pct">{formatPercentage(pct(item))}</span
95
- >{/if}
96
- </span>
97
- {/if}
98
- {#if showProgress && item.maxValue}
99
- <span class="hr-legend-track">
100
- <span
101
- class="hr-legend-bar"
102
- style:--hr-bar-scale={pct(item) / 100}
103
- style:background={item.color}
104
- style:animation-delay="{160 + i * 120}ms"
105
- ></span>
106
- </span>
107
- {/if}
108
- </button>
109
- </li>
110
- {/each}
111
- </ul>
112
- </div>
@@ -1,26 +0,0 @@
1
- /* Styles for LineChart. Plain stylesheet (see area-chart.css for why). Selectors
2
- are namespaced under `.hr-line-chart`. */
3
-
4
- .hr-line-chart {
5
- width: 100%;
6
- display: flex;
7
- flex-direction: column;
8
- gap: 0.5rem;
9
- }
10
- .hr-line-chart .hr-chart-body {
11
- width: 100%;
12
- }
13
-
14
- .hr-line-chart .empty {
15
- display: flex;
16
- align-items: center;
17
- justify-content: center;
18
- height: 100%;
19
- border: 1px dashed var(--chart-grid, oklch(0.5 0.01 286 / 0.2));
20
- border-radius: 0.5rem;
21
- }
22
- .hr-line-chart .empty p {
23
- font-size: 0.8125rem;
24
- color: var(--chart-label, oklch(0.5 0.01 286));
25
- margin: 0;
26
- }
@@ -1,256 +0,0 @@
1
- <script lang="ts" generics="TData extends Record<string, unknown>">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import "./line-chart.css";
6
- import {
7
- Area,
8
- Axis,
9
- Chart,
10
- Highlight,
11
- LinearGradient,
12
- Points,
13
- Spline,
14
- Svg,
15
- Tooltip,
16
- } from "layerchart";
17
- import { curveMonotoneX } from "d3-shape";
18
- import Crosshair from "./crosshair.svelte";
19
- import EdgeFade from "./edge-fade.svelte";
20
- import { resolveEdgeFade } from "./edge-fade.js";
21
- import Figure from "./figure.svelte";
22
- import HoverFocus from "./hover-focus.svelte";
23
- import { DOMAIN_MOTION, TOOLTIP_GAP, TOOLTIP_TOP } from "./motion.js";
24
- import Reveal from "./reveal.svelte";
25
-
26
- // =========================
27
- // TYPES
28
- // =========================
29
- type Series = {
30
- key: keyof TData & string;
31
- label?: string;
32
- color?: string;
33
- };
34
-
35
- type Props = {
36
- data: TData[];
37
- xKey: keyof TData & string;
38
- yKey?: keyof TData & string;
39
- series?: Series[];
40
- color?: string;
41
- height?: number;
42
- smooth?: boolean;
43
- /**
44
- * Softens the left and right ends of the gradient fill instead of cutting them off at the plot edge.
45
- */
46
-
47
- /*
48
- * `true` fades 8% of the width per side, a number sets the fraction (0-0.45), `false` keeps the hard edges.
49
- */
50
- edgeFade?: boolean | number;
51
- /** Draw a ring marker at every data point. */
52
- showMarkers?: boolean;
53
- /** Inclusive index where a dashed "in progress" tail begins (e.g. the latest, still-open period). */
54
- dashFromIndex?: number;
55
- /** Dash pattern for the tail segment. */
56
- dashArray?: string;
57
- formatValue?: (v: number) => string;
58
- formatX?: (v: unknown) => string;
59
- class?: string;
60
- };
61
-
62
- // =========================
63
- // PROPS
64
- // =========================
65
- let {
66
- data,
67
- xKey,
68
- yKey,
69
- series: seriesInput,
70
- color = "var(--chart-1)",
71
- height = 240,
72
- smooth = true,
73
- edgeFade = true,
74
- showMarkers = false,
75
- dashFromIndex,
76
- dashArray = "5 4",
77
- formatValue = (v) => v.toLocaleString(),
78
- formatX = (v) => String(v),
79
- class: className,
80
- }: Props = $props();
81
-
82
- // =========================
83
- // STATE
84
- // =========================
85
- let active = $state<TData | null>(null);
86
-
87
- // ========================= 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.
88
-
89
- // The microtask defers the state write out of template evaluation.
90
- function trackActive(d: TData): string {
91
- queueMicrotask(() => {
92
- active = d;
93
- });
94
- return formatX(d[xKey]);
95
- }
96
-
97
- // =========================
98
- // DERIVED
99
- // =========================
100
- const uid = `hr-line-${Math.random().toString(36).slice(2, 8)}`;
101
- const series = $derived(
102
- seriesInput && seriesInput.length > 0
103
- ? seriesInput.map((s, i) => ({
104
- key: s.key,
105
- label: s.label ?? s.key,
106
- color: s.color ?? `var(--chart-${(i % 5) + 1})`,
107
- }))
108
- : yKey
109
- ? [{ key: yKey, label: yKey, color }]
110
- : [],
111
- );
112
- const yMax = $derived(
113
- Math.max(0, ...data.flatMap((d) => series.map((s) => Number(d[s.key] ?? 0)))),
114
- );
115
- const curve = $derived(smooth ? curveMonotoneX : undefined);
116
- const fade = $derived(resolveEdgeFade(edgeFade));
117
- const fadeMask = $derived(fade > 0 ? `url(#${uid}-fade)` : undefined);
118
- const fallback = $derived(data.length > 0 ? data[data.length - 1] : null);
119
- const display = $derived(active ?? fallback);
120
- // WHY: a dashed tail is two splines sharing the boundary point so the solid
121
- // and dashed segments meet without a gap.
122
- const hasTail = $derived(
123
- dashFromIndex != null && dashFromIndex >= 0 && dashFromIndex < data.length - 1,
124
- );
125
- const solidData = $derived(hasTail ? data.slice(0, (dashFromIndex as number) + 1) : data);
126
- const tailData = $derived(hasTail ? data.slice(dashFromIndex as number) : []);
127
- const figureRows = $derived(
128
- display
129
- ? series.map((s) => ({ value: Number(display[s.key]), label: s.label, color: s.color }))
130
- : null,
131
- );
132
- </script>
133
-
134
- <!-- =========================== -->
135
- <!-- LINE CHART -->
136
- <!-- =========================== -->
137
- <div
138
- class="hr-line-chart {className ?? ''}"
139
- role="img"
140
- aria-label="Line chart"
141
- onpointerleave={() => (active = null)}
142
- >
143
- {#if figureRows && display}
144
- <Figure series={figureRows} eyebrow={formatX(display[xKey])} format={formatValue} size="md" />
145
- {:else}
146
- <Figure value="—" eyebrow="Latest" size="md" />
147
- {/if}
148
-
149
- <div class="hr-chart-body" style:height="{height}px">
150
- {#snippet strokes(s: (typeof series)[number])}
151
- {#if hasTail}
152
- <Spline
153
- data={solidData}
154
- y={s.key}
155
- {curve}
156
- style="stroke: {s.color}; fill: none"
157
- stroke-width="2"
158
- stroke-linecap="round"
159
- />
160
- <Spline
161
- data={tailData}
162
- y={s.key}
163
- {curve}
164
- style="stroke: {s.color}; fill: none"
165
- stroke-width="2"
166
- stroke-dasharray={dashArray}
167
- />
168
- {:else}
169
- <Spline
170
- y={s.key}
171
- {curve}
172
- style="stroke: {s.color}; fill: none"
173
- stroke-width="2"
174
- stroke-linecap="round"
175
- />
176
- {/if}
177
- {/snippet}
178
- {#if data.length === 0}
179
- <div class="empty"><p>No data</p></div>
180
- {:else}
181
- <Chart
182
- {data}
183
- x={xKey}
184
- y={series[0]?.key}
185
- yDomain={[0, yMax || 1]}
186
- yNice
187
- padding={{ left: 48, bottom: 28, top: 8, right: 12 }}
188
- tooltipContext={{ mode: "bisect-x" }}
189
- motion={DOMAIN_MOTION}
190
- >
191
- <Svg>
192
- <Axis placement="left" format={(v) => formatValue(Number(v))} ticks={4} />
193
- <Axis placement="bottom" format={(v) => formatX(v)} />
194
- <g class="hr-line-grid">
195
- <Axis placement="left" grid rule={false} ticks={4} format={() => ""} />
196
- </g>
197
- {#if fade > 0}
198
- <EdgeFade id="{uid}-fade" amount={fade} />
199
- {/if}
200
- <Crosshair id="{uid}-cross" />
201
- <HoverFocus>
202
- <Reveal>
203
- {#each series as s, i (s.key)}
204
- <LinearGradient id="{uid}-{i}" vertical>
205
- {#snippet stopsContent()}
206
- <stop offset="0%" stop-color={s.color} stop-opacity="0.22" />
207
- <stop offset="100%" stop-color={s.color} stop-opacity="0.01" />
208
- {/snippet}
209
- </LinearGradient>
210
- <Area
211
- y1={s.key}
212
- {curve}
213
- fill="url(#{uid}-{i})"
214
- mask={fadeMask}
215
- line={false}
216
- class="hr-line-area"
217
- />
218
- {@render strokes(s)}
219
- {#if showMarkers}
220
- <Points
221
- y={s.key}
222
- r={3}
223
- fill="var(--background, white)"
224
- stroke={s.color}
225
- stroke-width={2}
226
- class="hr-line-marker"
227
- />
228
- {/if}
229
- {/each}
230
- </Reveal>
231
- {#snippet segment()}
232
- {#each series as s (s.key)}
233
- {@render strokes(s)}
234
- {/each}
235
- {/snippet}
236
- </HoverFocus>
237
- <Highlight points lines={{ style: `stroke: url(#${uid}-cross)` }} />
238
- </Svg>
239
- <Tooltip.Root x="data" y={TOOLTIP_TOP} xOffset={TOOLTIP_GAP}>
240
- {#snippet children({ data: d })}
241
- <Tooltip.Header value={trackActive(d as TData)} />
242
- <Tooltip.List>
243
- {#each series as s (s.key)}
244
- <Tooltip.Item
245
- label={s.label}
246
- value={formatValue(Number(d[s.key]))}
247
- color={s.color}
248
- />
249
- {/each}
250
- </Tooltip.List>
251
- {/snippet}
252
- </Tooltip.Root>
253
- </Chart>
254
- {/if}
255
- </div>
256
- </div>
@@ -1,19 +0,0 @@
1
- import { cubicInOut } from "svelte/easing";
2
-
3
- /** Mirrors `--chart-motion-reveal` in charts.css, for when no element is mounted to read it from. */
4
- export const REVEAL_MS = 1100;
5
-
6
- /** Rescales the axes when the data changes; the first render never animates. */
7
- export const DOMAIN_MOTION = { type: "tween", duration: 500, easing: cubicInOut } as const;
8
-
9
- /** The tooltip sits beside the hovered point at the top of the plot, so it never chases the cursor. */
10
- export const TOOLTIP_TOP = 8;
11
- export const TOOLTIP_GAP = 16;
12
-
13
- /** A chart duration token in ms, read off a mounted element; both `ms` and `s` parse. */
14
- export function tokenMs(el: Element, name: string, fallback: number): number {
15
- const raw = getComputedStyle(el).getPropertyValue(name).trim();
16
- const n = Number.parseFloat(raw);
17
- if (Number.isNaN(n)) return fallback;
18
- return raw.endsWith("ms") ? n : n * 1000;
19
- }
@@ -1,168 +0,0 @@
1
- /* Styles for NumberChart. Plain stylesheet (see area-chart.css for why). All
2
- selectors are already namespaced under `.hr-number-chart`. */
3
-
4
- .hr-number-chart {
5
- display: flex;
6
- flex-direction: column;
7
- gap: 0.375rem;
8
- min-width: 0;
9
- padding: 1rem 1.25rem;
10
- border: 1px solid var(--border, oklch(0.92 0.004 286));
11
- border-radius: 0.75rem;
12
- background: var(--card, white);
13
- box-shadow: var(--chart-card-shadow);
14
- transition: border-color var(--chart-motion, 250ms) var(--chart-ease-out, ease);
15
- }
16
- .hr-number-chart:hover {
17
- border-color: color-mix(in srgb, var(--ring, var(--chart-1)) 28%, var(--border, transparent));
18
- }
19
- /* WHY a bare mode and not a second component: a band is one panel with
20
- hairlines through it, so a figure inside it must bring no edge of its own.
21
- Same object, one less border. */
22
- .hr-number-chart[data-bare] {
23
- padding: 1rem;
24
- border: 0;
25
- border-radius: 0;
26
- box-shadow: none;
27
- }
28
- .hr-number-chart[data-bare]:hover {
29
- border-color: transparent;
30
- }
31
-
32
- .hr-number-label {
33
- display: flex;
34
- align-items: center;
35
- gap: 0.375rem;
36
- margin: 0;
37
- font-size: 0.75rem;
38
- font-weight: 500;
39
- line-height: 1.4;
40
- color: var(--muted-foreground, oklch(0.5 0.01 286));
41
- }
42
- .hr-number-label-text {
43
- min-width: 0;
44
- overflow: hidden;
45
- text-overflow: ellipsis;
46
- white-space: nowrap;
47
- }
48
- .hr-number-icon {
49
- display: inline-flex;
50
- align-items: center;
51
- flex: none;
52
- }
53
- .hr-number-trailing {
54
- margin-left: auto;
55
- flex: none;
56
- font-size: 0.625rem;
57
- }
58
-
59
- /* WHY the row is baseline-aligned and top-anchored: a band mixes figures that
60
- carry a caption with figures that do not, and every value in the row has to
61
- sit on one line. The slack falls to the bottom, where nothing reads it. */
62
- .hr-number-row {
63
- display: flex;
64
- align-items: baseline;
65
- justify-content: space-between;
66
- gap: 0.5rem;
67
- }
68
-
69
- /* WHY the heading family and not the body one: a figure is structure, not
70
- prose, and tabular digits are what makes a column of them scannable. */
71
- .hr-number-value {
72
- font-family: var(--font-heading, inherit);
73
- font-weight: 600;
74
- font-variant-numeric: tabular-nums;
75
- font-feature-settings: "tnum";
76
- line-height: 1.15;
77
- letter-spacing: -0.015em;
78
- color: var(--foreground, oklch(0.2 0.01 286));
79
- min-width: 0;
80
- }
81
- .hr-number-chart[data-size="md"] .hr-number-value {
82
- font-size: 1.25rem;
83
- }
84
- /* WHY the lead figure is size and not colour: the accent is spent on actions,
85
- active states, marks and links. A number is none of those. */
86
- .hr-number-chart[data-size="lg"] .hr-number-value {
87
- font-size: 1.5rem;
88
- }
89
-
90
- .hr-number-delta {
91
- display: inline-flex;
92
- align-items: center;
93
- gap: 0.125rem;
94
- flex: none;
95
- font-size: 0.75rem;
96
- font-weight: 500;
97
- line-height: 1.2;
98
- color: var(--muted-foreground, oklch(0.5 0.01 286));
99
- }
100
- .hr-number-delta-text {
101
- font-variant-numeric: tabular-nums;
102
- }
103
- /* The readable form of a trend colour: a hue picked to read as a mark measures
104
- about 3.5:1 as 12px text, so it is mixed toward the page foreground. One rule
105
- serves both themes and the package needs to know nothing about which is on. */
106
- .hr-number-delta[data-tone="up"] {
107
- color: color-mix(
108
- in oklab,
109
- var(--stat-up, var(--success, oklch(0.6 0.13 163))) 72%,
110
- var(--foreground, oklch(0.2 0.01 286))
111
- );
112
- }
113
- .hr-number-delta[data-tone="down"] {
114
- color: color-mix(
115
- in oklab,
116
- var(--stat-down, var(--destructive, oklch(0.58 0.22 25))) 72%,
117
- var(--foreground, oklch(0.2 0.01 286))
118
- );
119
- }
120
- .hr-number-delta[data-tone="none"] {
121
- color: color-mix(in srgb, var(--muted-foreground, oklch(0.5 0.01 286)) 40%, transparent);
122
- }
123
- .hr-number-chart .hr-number-arrow {
124
- width: 0.875rem;
125
- height: 0.875rem;
126
- flex: none;
127
- }
128
-
129
- .hr-number-track {
130
- height: 0.25rem;
131
- border-radius: 999px;
132
- overflow: hidden;
133
- background: color-mix(in srgb, var(--border, oklch(0.92 0.004 286)) 80%, transparent);
134
- }
135
- .hr-number-fill {
136
- display: block;
137
- height: 100%;
138
- border-radius: inherit;
139
- transform-origin: left center;
140
- animation: hr-number-fill-in var(--chart-motion-slow, 800ms) var(--chart-ease-out, ease) both;
141
- transition: width var(--chart-motion, 250ms) var(--chart-ease-out, ease);
142
- }
143
- @keyframes hr-number-fill-in {
144
- from {
145
- transform: scaleX(0);
146
- }
147
- }
148
-
149
- .hr-number-caption {
150
- margin: 0;
151
- font-size: 0.75rem;
152
- line-height: 1.4;
153
- color: var(--muted-foreground, oklch(0.5 0.01 286));
154
- overflow: hidden;
155
- text-overflow: ellipsis;
156
- white-space: nowrap;
157
- }
158
- .hr-number-share {
159
- font-variant-numeric: tabular-nums;
160
- font-weight: 600;
161
- color: var(--foreground, oklch(0.2 0.01 286));
162
- }
163
-
164
- @media (prefers-reduced-motion: reduce) {
165
- .hr-number-fill {
166
- animation: none;
167
- }
168
- }