@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,303 +0,0 @@
1
- <script module lang="ts">
2
- // WHY: each gradient needs a document-unique <defs> id; a module counter keeps
3
- // SSR and hydration in lockstep (def and ref share the same value per render).
4
- let gradientSeq = 0;
5
- </script>
6
-
7
- <script lang="ts">
8
- // =========================
9
- // IMPORTS
10
- // =========================
11
- import "./gauge-chart.css";
12
- import { cn } from "@heyramzi/utils";
13
- import AnimatedValue from "./animated-value.svelte";
14
-
15
- // =========================
16
- // TYPES
17
- // =========================
18
- type Props = {
19
- /** Arc fill, 0-100 (how many notches light up). */
20
- value: number;
21
- /** "gauge" = notched arc, "ring" = continuous swept arc. */
22
- variant?: "gauge" | "ring";
23
- /** Show the center value/label. Off when the number sits beside the gauge. */
24
- showValue?: boolean;
25
- /** Active arc color (CSS color or var). Alias for `activeFill`. */
26
- color?: string;
27
- /** Single-hue gradient along the active arc. Alias for `useGradient`. */
28
- gradient?: boolean;
29
- /** Center label under the value. Alias for `defaultLabel`. */
30
- label?: string;
31
- /** Center statistic. Defaults to `value`. Formatted via `formatOptions`. */
32
- centerValue?: number;
33
- /** Notch count around the arc. */
34
- totalNotches?: number;
35
- /** Gap between notches as a percentage of the arc. */
36
- spacing?: number;
37
- /** Radial depth of the notch band as a percentage of the default. */
38
- notchLengthPercent?: number;
39
- /** Corner fillet in viewBox units (0 = sharp). Defaults to a soft pill. */
40
- notchCornerRadius?: number;
41
- /** Rectangular notches instead of angular/tapered. */
42
- uniformWidth?: boolean;
43
- /** Arc start in degrees (default 135, sweeping to 405 = 270deg with a bottom gap). */
44
- startAngle?: number;
45
- endAngle?: number;
46
- /** Single-hue gradient along the active arc instead of a flat fill. */
47
- useGradient?: boolean;
48
- /** Hex stops for the active gradient. Defaults to a tonal ramp of `activeFill`. */
49
- activeGradient?: [string, string];
50
- /** Active notch color (CSS color or var). */
51
- activeFill?: string;
52
- /** Track notch color. Defaults to `activeFill`. */
53
- inactiveFill?: string;
54
- activeFillOpacity?: number;
55
- inactiveFillOpacity?: number;
56
- /** Center label under the value. */
57
- defaultLabel?: string;
58
- /** Intl formatting for the center value. */
59
- formatOptions?: Intl.NumberFormatOptions;
60
- locale?: string;
61
- prefix?: string;
62
- suffix?: string;
63
- /** Fixed square size in px. Omit to fill the parent (responsive). */
64
- size?: number;
65
- /** Minimum width when responsive. */
66
- minWidth?: number;
67
- /** Accessible description, e.g. "ARR run rate". */
68
- ariaLabel?: string;
69
- class?: string;
70
- };
71
-
72
- // =========================
73
- // PROPS
74
- // =========================
75
- let {
76
- value,
77
- variant = "gauge",
78
- showValue = true,
79
- color,
80
- gradient,
81
- label,
82
- centerValue,
83
- totalNotches = 40,
84
- spacing = 25,
85
- notchLengthPercent = 100,
86
- notchCornerRadius,
87
- uniformWidth = false,
88
- startAngle = 135,
89
- endAngle = 405,
90
- useGradient = false,
91
- activeGradient,
92
- activeFill = "var(--chart-1)",
93
- inactiveFill,
94
- activeFillOpacity = 1,
95
- inactiveFillOpacity = 0.12,
96
- defaultLabel = "Total",
97
- formatOptions,
98
- locale,
99
- prefix,
100
- suffix,
101
- size,
102
- minWidth = 160,
103
- ariaLabel,
104
- class: className,
105
- }: Props = $props();
106
-
107
- // =========================
108
- // CONST
109
- // =========================
110
- // Normalized 100x100 coordinate space so the SVG scales to any container.
111
- const CX = 50;
112
- const CY = 50;
113
- const OUTER_R = 42;
114
- const gradientId = `hr-gauge-grad-${(gradientSeq += 1)}`;
115
-
116
- // =========================
117
- // DERIVED
118
- // =========================
119
- const clamped = $derived(Math.max(0, Math.min(100, value)));
120
- const center = $derived(centerValue ?? value);
121
- const effFill = $derived(color ?? activeFill);
122
- const effGradient = $derived(gradient ?? useGradient);
123
- const effLabel = $derived(label ?? defaultLabel);
124
- const band = $derived(14 * (Math.max(5, Math.min(100, notchLengthPercent)) / 100));
125
- const innerR = $derived(OUTER_R - band);
126
- const midR = $derived((OUTER_R + innerR) / 2);
127
- const corner = $derived(notchCornerRadius ?? band * 0.22);
128
- const totalAngle = $derived(endAngle - startAngle);
129
- const activeCount = $derived(Math.round((clamped / 100) * totalNotches));
130
- const trackFill = $derived(inactiveFill ?? effFill);
131
- const activePaint = $derived(effGradient ? `url(#${gradientId})` : effFill);
132
- const gradStops = $derived(
133
- activeGradient ?? [`color-mix(in oklab, ${effFill}, white 45%)`, effFill],
134
- );
135
- // WHY: ring variant draws a single rounded arc that sweeps from empty; the
136
- // stroke-dashoffset (with pathLength 100) maps 1:1 to the percent value.
137
- const ringTrack = $derived(arcPath(startAngle, endAngle));
138
- const ringOffset = $derived(100 - clamped);
139
-
140
- const notches = $derived.by(() => {
141
- const notchAngle = (totalAngle * (1 - spacing / 100)) / totalNotches;
142
- const gapAngle = (totalAngle * (spacing / 100)) / Math.max(1, totalNotches - 1);
143
- const half = (notchAngle * 0.8 * Math.PI) / 180 / 2;
144
- const items: { d: string; active: boolean; index: number }[] = [];
145
- for (let i = 0; i < totalNotches; i++) {
146
- const angle = startAngle + i * (notchAngle + gapAngle) + notchAngle / 2;
147
- items.push({
148
- index: i,
149
- active: i < activeCount,
150
- d: notchPath(buildQuad(angle, half), corner),
151
- });
152
- }
153
- return items;
154
- });
155
-
156
- // =========================
157
- // FUNCTIONS
158
- // =========================
159
- function buildQuad(
160
- angleDeg: number,
161
- half: number,
162
- ): [number, number, number, number, number, number, number, number] {
163
- const rad = (angleDeg * Math.PI) / 180;
164
- if (uniformWidth) {
165
- // WHY: constant perpendicular half-width gives rectangular notches; the
166
- // chord at the mid radius keeps them visually consistent with tapered ones.
167
- const w = midR * Math.sin(half);
168
- const ux = Math.cos(rad);
169
- const uy = Math.sin(rad);
170
- const px = -uy;
171
- const py = ux;
172
- const ox = CX + ux * OUTER_R;
173
- const oy = CY + uy * OUTER_R;
174
- const ix = CX + ux * innerR;
175
- const iy = CY + uy * innerR;
176
- return [
177
- ox + px * w,
178
- oy + py * w,
179
- ox - px * w,
180
- oy - py * w,
181
- ix - px * w,
182
- iy - py * w,
183
- ix + px * w,
184
- iy + py * w,
185
- ];
186
- }
187
- return [
188
- CX + Math.cos(rad - half) * OUTER_R,
189
- CY + Math.sin(rad - half) * OUTER_R,
190
- CX + Math.cos(rad + half) * OUTER_R,
191
- CY + Math.sin(rad + half) * OUTER_R,
192
- CX + Math.cos(rad + half) * innerR,
193
- CY + Math.sin(rad + half) * innerR,
194
- CX + Math.cos(rad - half) * innerR,
195
- CY + Math.sin(rad - half) * innerR,
196
- ];
197
- }
198
-
199
- function arcPath(startDeg: number, endDeg: number): string {
200
- const a0 = (startDeg * Math.PI) / 180;
201
- const a1 = (endDeg * Math.PI) / 180;
202
- const sx = CX + Math.cos(a0) * midR;
203
- const sy = CY + Math.sin(a0) * midR;
204
- const ex = CX + Math.cos(a1) * midR;
205
- const ey = CY + Math.sin(a1) * midR;
206
- const large = endDeg - startDeg > 180 ? 1 : 0;
207
- return `M ${sx} ${sy} A ${midR} ${midR} 0 ${large} 1 ${ex} ${ey}`;
208
- }
209
-
210
- // WHY: rounded corners (lerp toward each vertex, then a quadratic through it)
211
- // read as soft pills, matching the bklit gauge look.
212
- function notchPath([x1, y1, x2, y2, x3, y3, x4, y4]: number[], cornerR: number): string {
213
- if (cornerR <= 0) {
214
- return `M ${x1} ${y1} L ${x2} ${y2} L ${x3} ${y3} L ${x4} ${y4} Z`;
215
- }
216
- const lerp = (a: number, b: number, t: number) => a + (b - a) * t;
217
- const dist = (ax: number, ay: number, bx: number, by: number) => Math.hypot(bx - ax, by - ay);
218
- const d12 = dist(x1, y1, x2, y2);
219
- const d23 = dist(x2, y2, x3, y3);
220
- const d34 = dist(x3, y3, x4, y4);
221
- const d41 = dist(x4, y4, x1, y1);
222
- const r1 = Math.min(cornerR / d12, 0.49);
223
- const r2 = Math.min(cornerR / d23, 0.49);
224
- const r3 = Math.min(cornerR / d34, 0.49);
225
- const r4 = Math.min(cornerR / d41, 0.49);
226
- const p = (ax: number, ay: number, bx: number, by: number, t: number) =>
227
- `${lerp(ax, bx, t)} ${lerp(ay, by, t)}`;
228
- return (
229
- `M ${p(x1, y1, x4, y4, r4)} Q ${x1} ${y1} ${p(x1, y1, x2, y2, r1)} ` +
230
- `L ${p(x2, y2, x1, y1, r1)} Q ${x2} ${y2} ${p(x2, y2, x3, y3, r2)} ` +
231
- `L ${p(x3, y3, x2, y2, r2)} Q ${x3} ${y3} ${p(x3, y3, x4, y4, r3)} ` +
232
- `L ${p(x4, y4, x3, y3, r3)} Q ${x4} ${y4} ${p(x4, y4, x1, y1, r4)} Z`
233
- );
234
- }
235
- </script>
236
-
237
- <!-- =========================== -->
238
- <!-- MARKUP -->
239
- <!-- =========================== -->
240
- <div
241
- class={cn("hr-gauge", className)}
242
- style:width={size ? `${size}px` : "100%"}
243
- style:min-width="{minWidth}px"
244
- role="img"
245
- aria-label={ariaLabel
246
- ? `${ariaLabel}: ${Math.round(clamped)}%`
247
- : `Gauge at ${Math.round(clamped)}%`}
248
- >
249
- <!-- no-inline-svg-ignore -->
250
- <svg viewBox="0 0 100 100" aria-hidden="true">
251
- {#if effGradient}
252
- <defs>
253
- <linearGradient id={gradientId} gradientUnits="userSpaceOnUse" x1="8" y1="0" x2="92" y2="0">
254
- <stop offset="0%" stop-color={gradStops[0]} />
255
- <stop offset="100%" stop-color={gradStops[1]} />
256
- </linearGradient>
257
- </defs>
258
- {/if}
259
- {#if variant === "ring"}
260
- <path
261
- class="ring-track"
262
- d={ringTrack}
263
- stroke={trackFill}
264
- stroke-opacity={inactiveFillOpacity}
265
- style:stroke-width="{band}px"
266
- />
267
- <path
268
- class="ring-fill"
269
- d={ringTrack}
270
- pathLength="100"
271
- stroke={activePaint}
272
- style:stroke-width="{band}px"
273
- style:stroke-dashoffset={ringOffset}
274
- />
275
- {:else}
276
- {#each notches as notch (notch.index)}
277
- <path class="track" d={notch.d} fill={trackFill} fill-opacity={inactiveFillOpacity} />
278
- {/each}
279
- {#each notches as notch (notch.index)}
280
- {#if notch.active}
281
- <path
282
- class="fill"
283
- d={notch.d}
284
- fill={activePaint}
285
- fill-opacity={activeFillOpacity}
286
- style:animation-delay="{100 + notch.index * 18}ms"
287
- />
288
- {/if}
289
- {/each}
290
- {/if}
291
- </svg>
292
-
293
- {#if showValue}
294
- <div class="center">
295
- <span class="value">
296
- <AnimatedValue value={center} {formatOptions} {locale} {prefix} {suffix} />
297
- </span>
298
- {#if effLabel}
299
- <span class="label">{effLabel}</span>
300
- {/if}
301
- </div>
302
- {/if}
303
- </div>
@@ -1,30 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { hoverBand } from "./hover-band.js";
3
-
4
- const center = (b: { x: number; width: number } | null) => (b ? b.x + b.width / 2 : NaN);
5
-
6
- describe("hoverBand", () => {
7
- it("centers on the point when the gaps are even", () => {
8
- expect(hoverBand([0, 100, 200], 1, 300)).toEqual({ x: 20, width: 160 });
9
- });
10
-
11
- it("stays centered when the gaps differ", () => {
12
- const b = hoverBand([0, 40, 200], 1, 300);
13
- expect(center(b)).toBe(40);
14
- expect(b?.width).toBe(64);
15
- });
16
-
17
- it("stays centered at either end of the line", () => {
18
- expect(center(hoverBand([0, 100, 200], 0, 300))).toBe(0);
19
- expect(center(hoverBand([0, 100, 200], 2, 300))).toBe(200);
20
- });
21
-
22
- it("falls back to the plot width for a single point", () => {
23
- expect(hoverBand([50], 0, 100)).toEqual({ x: -30, width: 160 });
24
- });
25
-
26
- it("returns null for an index off the data", () => {
27
- expect(hoverBand([0, 100], -1, 300)).toBeNull();
28
- expect(hoverBand([0, 100], 2, 300)).toBeNull();
29
- });
30
- });
@@ -1,21 +0,0 @@
1
- /** How far the hover band reaches each side of the point, as a share of the gap to its neighbour. */
2
- export const HOVER_BAND_REACH = 0.8;
3
-
4
- /**
5
- * The hovered segment's clip, centered on the point at `xs[i]`.
6
- * Both sides take the shorter neighbour gap, so the dot always sits in the middle.
7
- */
8
- export function hoverBand(
9
- xs: number[],
10
- i: number,
11
- width: number,
12
- reach = HOVER_BAND_REACH,
13
- ): { x: number; width: number } | null {
14
- if (i < 0 || i >= xs.length) return null;
15
- const at = xs[i];
16
- const before = i > 0 ? at - xs[i - 1] : Infinity;
17
- const after = i < xs.length - 1 ? xs[i + 1] - at : Infinity;
18
- const gap = Math.min(before, after);
19
- const half = (Number.isFinite(gap) ? gap : width) * reach;
20
- return { x: at - half, width: half * 2 };
21
- }
@@ -1,85 +0,0 @@
1
- <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import { getChartContext } from "layerchart";
6
- import type { Snippet } from "svelte";
7
- import { prefersReducedMotion, Spring } from "svelte/motion";
8
- import { fade } from "svelte/transition";
9
- import { HOVER_BAND_REACH, hoverBand } from "./hover-band.js";
10
-
11
- // =========================
12
- // TYPES
13
- // =========================
14
- type Props = {
15
- /** The series: dims while a point is hovered. */
16
- children: Snippet;
17
- /** The strokes alone, redrawn at full strength around the hovered point. */
18
- segment: Snippet;
19
- /** Headroom above and below the plot so a thick stroke is not cut. */
20
- bleed?: number;
21
- /** How far the band reaches each side of the point, as a share of the gap to its neighbour. */
22
- reach?: number;
23
- };
24
-
25
- // =========================
26
- // PROPS
27
- // =========================
28
- let { children, segment, bleed = 6, reach = HOVER_BAND_REACH }: Props = $props();
29
-
30
- // =========================
31
- // STATE
32
- // =========================
33
- const ctx = getChartContext();
34
- const id = `hr-focus-${Math.random().toString(36).slice(2, 8)}`;
35
- const left = new Spring(0, { stiffness: 0.2, damping: 0.85 });
36
- const width = new Spring(0, { stiffness: 0.2, damping: 0.85 });
37
-
38
- // =========================
39
- // DERIVED
40
- // =========================
41
- const band = $derived.by(() => {
42
- const d = ctx.tooltip.data;
43
- const xs = (ctx.data as unknown[]).map((r) => Number(ctx.xGet(r)));
44
- // WHY by x and not indexOf: ctx.data hands back proxies, so identity never matches.
45
- const at = d ? Number(ctx.xGet(d)) : NaN;
46
- return hoverBand(xs, xs.indexOf(at), ctx.width, reach);
47
- });
48
- const duration = $derived(prefersReducedMotion.current ? 0 : 400);
49
-
50
- // =========================
51
- // EFFECTS
52
- // =========================
53
- // WHY instant on first hover: the clip should open where the pointer is, not
54
- // slide in from wherever it last closed.
55
- let wasOn = false;
56
- $effect(() => {
57
- if (!band) {
58
- wasOn = false;
59
- return;
60
- }
61
- const instant = !wasOn || prefersReducedMotion.current;
62
- left.set(band.x, { instant });
63
- width.set(band.width, { instant });
64
- wasOn = true;
65
- });
66
- </script>
67
-
68
- <!-- =========================== -->
69
- <!-- HOVER FOCUS -->
70
- <!-- =========================== -->
71
- <g class="hr-hover-dim" class:hr-hover-dimmed={band !== null}>
72
- {@render children()}
73
- </g>
74
- <!-- WHY outside the if: the segment fades out after hover ends, and without its
75
- clip it would flash as the whole line. -->
76
- <defs>
77
- <clipPath {id}>
78
- <rect x={left.current} y={-bleed} width={width.current} height={ctx.height + bleed * 2} />
79
- </clipPath>
80
- </defs>
81
- {#if band}
82
- <g clip-path="url(#{id})" transition:fade={{ duration }}>
83
- {@render segment()}
84
- </g>
85
- {/if}
@@ -1,30 +0,0 @@
1
- import "./charts.css";
2
-
3
- export { default as BarChart } from "./bar-chart.svelte";
4
- export { default as AreaChart } from "./area-chart.svelte";
5
- export { default as LineChart } from "./line-chart.svelte";
6
- export { default as DonutChart } from "./donut-chart.svelte";
7
- export { default as Sparkline } from "./sparkline.svelte";
8
- export { default as TimeSeriesChart } from "./time-series-chart.svelte";
9
- export { default as GaugeChart } from "./gauge-chart.svelte";
10
- export { default as RingChart } from "./ring-chart.svelte";
11
- export { default as Legend } from "./legend.svelte";
12
- export { default as StatCard } from "./stat-card.svelte";
13
- export { default as NumberChart, type NumberDirection } from "./number-chart.svelte";
14
- export { default as Figure } from "./figure.svelte";
15
- export { default as TrendPill } from "./trend-pill.svelte";
16
- export { default as AnimatedValue } from "./animated-value.svelte";
17
- export { default as EdgeFade } from "./edge-fade.svelte";
18
- export { resolveEdgeFade, EDGE_FADE_DEFAULT } from "./edge-fade.js";
19
-
20
- export {
21
- ChartContainer,
22
- ChartTooltip,
23
- ChartStyle,
24
- type ChartConfig,
25
- type TooltipSeriesItem,
26
- getPayloadConfigFromPayload,
27
- useChart,
28
- setChartContext,
29
- THEMES,
30
- } from "./primitives/index.js";
@@ -1,131 +0,0 @@
1
- /* Styles for Legend. Plain stylesheet (see area-chart.css for why). Selectors
2
- are namespaced under `.hr-legend`; the `.horizontal` layout modifier sits on
3
- the same root element. */
4
-
5
- .hr-legend {
6
- width: 100%;
7
- font-size: 0.8125rem;
8
- }
9
-
10
- .hr-legend-title {
11
- margin: 0 0 0.625rem;
12
- font-size: 0.8125rem;
13
- font-weight: 600;
14
- color: var(--foreground);
15
- }
16
-
17
- .hr-legend-items {
18
- list-style: none;
19
- margin: 0;
20
- padding: 0;
21
- display: flex;
22
- flex-direction: column;
23
- gap: 0.125rem;
24
- }
25
-
26
- .hr-legend.horizontal .hr-legend-items {
27
- flex-direction: row;
28
- flex-wrap: wrap;
29
- gap: 0.5rem 1rem;
30
- }
31
-
32
- .hr-legend-item {
33
- width: 100%;
34
- display: grid;
35
- grid-template-columns: auto 1fr auto;
36
- align-items: center;
37
- gap: 0.5rem 0.625rem;
38
- padding: 0.375rem 0.5rem;
39
- border: 0;
40
- border-radius: 0.5rem;
41
- background: transparent;
42
- font: inherit;
43
- color: var(--foreground);
44
- text-align: left;
45
- cursor: pointer;
46
- transition:
47
- background 160ms ease,
48
- opacity 160ms ease;
49
- }
50
-
51
- .hr-legend.horizontal .hr-legend-item {
52
- width: auto;
53
- grid-template-columns: auto auto;
54
- padding: 0.25rem 0.375rem;
55
- }
56
-
57
- .hr-legend-item:hover,
58
- .hr-legend-item:focus-visible {
59
- background: var(--muted);
60
- outline: none;
61
- }
62
-
63
- .hr-legend-item.dimmed {
64
- opacity: 0.4;
65
- }
66
-
67
- .hr-legend-marker {
68
- width: 0.625rem;
69
- height: 0.625rem;
70
- border-radius: 3px;
71
- flex-shrink: 0;
72
- }
73
-
74
- .hr-legend-label {
75
- font-weight: 500;
76
- white-space: nowrap;
77
- overflow: hidden;
78
- text-overflow: ellipsis;
79
- }
80
-
81
- .hr-legend-value {
82
- display: inline-flex;
83
- align-items: baseline;
84
- gap: 0.3125rem;
85
- font-variant-numeric: tabular-nums;
86
- color: var(--foreground);
87
- white-space: nowrap;
88
- }
89
-
90
- .hr-legend-pct {
91
- font-size: 0.6875rem;
92
- color: var(--muted-foreground);
93
- }
94
-
95
- .hr-legend-track {
96
- grid-column: 1 / -1;
97
- height: 0.375rem;
98
- border-radius: 999px;
99
- background: var(--muted);
100
- overflow: hidden;
101
- }
102
-
103
- .hr-legend-bar {
104
- display: block;
105
- width: 100%;
106
- height: 100%;
107
- border-radius: 999px;
108
- /* WHY: scaleX from the left avoids the layout thrash of animating width;
109
- the bar's fill fraction is set inline as --hr-bar-scale. The entrance
110
- sweeps from 0 once (delays match the ring sweep); the transition takes
111
- over for later data changes. */
112
- transform-origin: left center;
113
- transform: scaleX(var(--hr-bar-scale, 0));
114
- animation: hr-legend-fill var(--chart-motion-reveal, 1100ms)
115
- var(--chart-ease-reveal, cubic-bezier(0.85, 0, 0.15, 1)) both;
116
- transition: transform var(--chart-motion-slow, 1.1s)
117
- var(--chart-ease-out, cubic-bezier(0.22, 1, 0.36, 1));
118
- }
119
-
120
- @keyframes hr-legend-fill {
121
- from {
122
- transform: scaleX(0);
123
- }
124
- }
125
-
126
- @media (prefers-reduced-motion: reduce) {
127
- .hr-legend-bar {
128
- animation: none;
129
- transition: none;
130
- }
131
- }