@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,158 +0,0 @@
1
- <script lang="ts" generics="TData extends Record<string, unknown>">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import "./donut-chart.css";
6
- import { Arc, Chart, Group, Pie, Svg, Text } from "layerchart";
7
- import { scaleOrdinal } from "d3-scale";
8
- import { expoInOut } from "svelte/easing";
9
- import { prefersReducedMotion, Tween } from "svelte/motion";
10
- import { REVEAL_MS } from "./motion.js";
11
-
12
- // =========================
13
- // TYPES
14
- // =========================
15
- type Props = {
16
- data: TData[];
17
- valueKey: keyof TData & string;
18
- labelKey: keyof TData & string;
19
- colorKey?: keyof TData & string;
20
- height?: number;
21
- innerRadius?: number;
22
- outerRadius?: number;
23
- /** Center label shown when nothing is hovered */
24
- centerLabel?: string;
25
- /** Format the center value */
26
- formatValue?: (v: number) => string;
27
- class?: string;
28
- };
29
-
30
- // =========================
31
- // PROPS
32
- // =========================
33
- let {
34
- data,
35
- valueKey,
36
- labelKey,
37
- colorKey,
38
- height = 240,
39
- innerRadius = 60,
40
- outerRadius = 100,
41
- centerLabel,
42
- formatValue = (v) => v.toLocaleString(),
43
- class: className,
44
- }: Props = $props();
45
-
46
- // =========================
47
- // STATE
48
- // =========================
49
- let hovered = $state<string | null>(null);
50
- // Milliseconds into the entrance; each slice sweeps on its own offset.
51
- const clock = new Tween(0);
52
- let swept = $state(false);
53
- let started = false;
54
-
55
- // =========================
56
- // FUNCTIONS
57
- // =========================
58
- const SLICE_DELAY = 100;
59
- const SLICE_STEP = 80;
60
- const HOVER_OFFSET = 6;
61
-
62
- function sweep(i: number): number {
63
- if (swept) return 1;
64
- const t = (clock.current - SLICE_DELAY - i * SLICE_STEP) / REVEAL_MS;
65
- return expoInOut(Math.min(1, Math.max(0, t)));
66
- }
67
-
68
- // =========================
69
- // EFFECTS
70
- // =========================
71
- $effect(() => {
72
- if (started || filtered.length === 0) return;
73
- started = true;
74
- const end = SLICE_DELAY + (filtered.length - 1) * SLICE_STEP + REVEAL_MS;
75
- const duration = prefersReducedMotion.current ? 0 : end;
76
- clock.set(end, { duration }).then(() => (swept = true));
77
- });
78
-
79
- // =========================
80
- // DERIVED
81
- // =========================
82
- const filtered = $derived(data.filter((d) => Number(d[valueKey] ?? 0) > 0));
83
- const total = $derived(filtered.reduce((sum, d) => sum + Number(d[valueKey] ?? 0), 0));
84
- const idAccessor = $derived((d: TData) => String(d[labelKey] ?? ""));
85
- const colorRange = $derived(colorKey ? filtered.map((d) => String(d[colorKey] ?? "")) : []);
86
- const colorDomain = $derived(filtered.map(idAccessor));
87
- const hoveredItem = $derived(filtered.find((d) => idAccessor(d) === hovered) ?? null);
88
- </script>
89
-
90
- <!-- =========================== -->
91
- <!-- DONUT CHART -->
92
- <!-- =========================== -->
93
- <div
94
- class="hr-donut-chart {className ?? ''}"
95
- style:height="{height}px"
96
- role="img"
97
- aria-label="{centerLabel ?? 'Donut chart'}: {formatValue(total)} total"
98
- >
99
- {#if filtered.length === 0}
100
- <div class="empty">
101
- <p>No data</p>
102
- </div>
103
- {:else}
104
- <Chart
105
- data={filtered}
106
- x={valueKey}
107
- c={colorKey ? idAccessor : undefined}
108
- cScale={scaleOrdinal()}
109
- cDomain={colorDomain}
110
- cRange={colorRange}
111
- >
112
- <Svg>
113
- <Group center>
114
- <Pie {innerRadius} {outerRadius} cornerRadius={3} padAngle={0.02} sort={null}>
115
- {#snippet children({ arcs })}
116
- {#each arcs as arc, i (i)}
117
- {@const id = idAccessor(arc.data as TData)}
118
- {@const isHovered = hovered === id}
119
- {@const dimmed = hovered !== null && !isHovered}
120
- {@const mid = (arc.startAngle + arc.endAngle) / 2}
121
- {@const dx = isHovered ? Math.sin(mid) * HOVER_OFFSET : 0}
122
- {@const dy = isHovered ? -Math.cos(mid) * HOVER_OFFSET : 0}
123
- <Arc
124
- startAngle={arc.startAngle}
125
- endAngle={arc.startAngle + (arc.endAngle - arc.startAngle) * sweep(i)}
126
- padAngle={arc.padAngle}
127
- {innerRadius}
128
- {outerRadius}
129
- cornerRadius={3}
130
- fill={colorKey
131
- ? String((arc.data as TData)[colorKey])
132
- : `var(--chart-${(i % 5) + 1})`}
133
- class="hr-donut-arc"
134
- data-hovered={isHovered ? "" : undefined}
135
- style="opacity: {dimmed ? 0.4 : 1}; translate: {dx}px {dy}px"
136
- onmouseenter={() => (hovered = id)}
137
- onmouseleave={() => (hovered = null)}
138
- />
139
- {/each}
140
- {/snippet}
141
- </Pie>
142
- <Text
143
- value={hoveredItem ? formatValue(Number(hoveredItem[valueKey])) : formatValue(total)}
144
- textAnchor="middle"
145
- dy={-4}
146
- class="hr-donut-center-value"
147
- />
148
- <Text
149
- value={hoveredItem ? String(hoveredItem[labelKey]) : (centerLabel ?? "Total")}
150
- textAnchor="middle"
151
- dy={14}
152
- class="hr-donut-center-label"
153
- />
154
- </Group>
155
- </Svg>
156
- </Chart>
157
- {/if}
158
- </div>
@@ -1,43 +0,0 @@
1
- <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import { EDGE_FADE_DEFAULT } from "./edge-fade.js";
6
-
7
- // =========================
8
- // TYPES
9
- // =========================
10
- type Props = {
11
- /** Mask id. Apply it to the shape with `mask="url(#id)"`. */
12
- id: string;
13
- /** Fraction of the shape's width faded at each side, 0-0.45. */
14
- amount?: number;
15
- };
16
-
17
- // =========================
18
- // PROPS
19
- // =========================
20
- let { id, amount = EDGE_FADE_DEFAULT }: Props = $props();
21
-
22
- // =========================
23
- // DERIVED
24
- // =========================
25
- const stop = $derived(Math.min(0.45, Math.max(0, amount)));
26
- </script>
27
-
28
- <!-- =========================== -->
29
- <!-- EDGE FADE -->
30
- <!-- =========================== -->
31
- <!-- WHY: the fill gradient of an area is vertical, so the horizontal fade cannot ride on it. A luminance mask in objectBoundingBox units needs no pixel -->
32
- <!-- dimensions: the ramp is expressed as a fraction of the masked path's own box, so it follows the plot width through every resize. -->
33
- <defs>
34
- <linearGradient id="{id}-ramp" x1="0" y1="0" x2="1" y2="0">
35
- <stop offset="0" stop-color="#fff" stop-opacity="0" />
36
- <stop offset={stop} stop-color="#fff" stop-opacity="1" />
37
- <stop offset={1 - stop} stop-color="#fff" stop-opacity="1" />
38
- <stop offset="1" stop-color="#fff" stop-opacity="0" />
39
- </linearGradient>
40
- <mask {id} maskContentUnits="objectBoundingBox">
41
- <rect x="0" y="0" width="1" height="1" fill="url(#{id}-ramp)" />
42
- </mask>
43
- </defs>
@@ -1,16 +0,0 @@
1
- /** `true` fades this much of the width at each side. */
2
- export const EDGE_FADE_DEFAULT = 0.08;
3
-
4
- /**
5
- * Turns an `edgeFade` prop into the fraction of the width to fade at each side.
6
- * `false` (or 0) means no mask is rendered at all.
7
- */
8
- export function resolveEdgeFade(
9
- value: boolean | number | undefined,
10
- fallback = EDGE_FADE_DEFAULT,
11
- ): number {
12
- if (value === false) return 0;
13
- if (value === true || value == null) return fallback;
14
- if (!Number.isFinite(value)) return 0;
15
- return Math.min(0.45, Math.max(0, value));
16
- }
@@ -1,100 +0,0 @@
1
- /* Styles for Figure. Plain stylesheet (see area-chart.css for why). All
2
- selectors are already namespaced under `.hr-figure`. */
3
-
4
- .hr-figure {
5
- display: flex;
6
- flex-direction: column;
7
- gap: 0.1875rem;
8
- min-width: 0;
9
- }
10
- .hr-figure[data-align="center"] {
11
- align-items: center;
12
- text-align: center;
13
- }
14
-
15
- /* WHY the title truncates: title and pill share one narrow row (the gauge card
16
- leaves it ~160px), and a shrunken title box let the word paint over the pill. */
17
- .hr-figure-head {
18
- display: flex;
19
- align-items: center;
20
- justify-content: space-between;
21
- gap: 0.75rem;
22
- }
23
- .hr-figure-title {
24
- display: flex;
25
- align-items: center;
26
- gap: 0.4375rem;
27
- min-width: 0;
28
- font-size: 0.9375rem;
29
- font-weight: 600;
30
- color: var(--foreground, oklch(0.2 0.01 286));
31
- letter-spacing: -0.005em;
32
- }
33
- .hr-figure-title-text {
34
- min-width: 0;
35
- overflow: hidden;
36
- white-space: nowrap;
37
- text-overflow: ellipsis;
38
- }
39
- .hr-figure-icon {
40
- display: inline-flex;
41
- align-items: center;
42
- justify-content: center;
43
- }
44
-
45
- .hr-figure-values {
46
- display: flex;
47
- flex-wrap: wrap;
48
- gap: 0.75rem;
49
- align-items: baseline;
50
- }
51
- .hr-figure-value-row {
52
- display: inline-flex;
53
- align-items: baseline;
54
- gap: 0.375rem;
55
- }
56
-
57
- .hr-figure-value {
58
- font-feature-settings: "tnum";
59
- line-height: 1.1;
60
- transition: color 180ms ease;
61
- }
62
- /* md: chart header (monospace, compact). */
63
- .hr-figure[data-size="md"] .hr-figure-value {
64
- font-size: 1.25rem;
65
- font-weight: 600;
66
- font-family: ui-monospace, SFMono-Regular, monospace;
67
- }
68
- /* lg: KPI card headline. */
69
- .hr-figure[data-size="lg"] .hr-figure-value {
70
- font-size: 1.875rem;
71
- font-weight: 700;
72
- line-height: 1;
73
- letter-spacing: -0.025em;
74
- color: var(--foreground, oklch(0.2 0.01 286));
75
- }
76
-
77
- .hr-figure-series-label {
78
- font-size: 0.6875rem;
79
- }
80
-
81
- /* The readable form of a series colour: see the note in figure.svelte. The wash
82
- and the line keep the raw hue; only text is pulled toward the foreground. */
83
- .hr-figure-value.is-series,
84
- .hr-figure-series-label.is-series {
85
- color: color-mix(
86
- in oklab,
87
- var(--hr-series, currentColor) 70%,
88
- var(--foreground, oklch(0.2 0.01 286))
89
- );
90
- }
91
- .hr-figure-caption {
92
- font-size: 0.75rem;
93
- color: var(--muted-foreground, oklch(0.5 0.01 286));
94
- }
95
- .hr-figure-eyebrow {
96
- font-size: 0.6875rem;
97
- color: var(--chart-label, var(--muted-foreground, oklch(0.5 0.01 286)));
98
- text-transform: uppercase;
99
- letter-spacing: 0.04em;
100
- }
@@ -1,122 +0,0 @@
1
- <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import "./figure.css";
6
- import { cn } from "@heyramzi/utils";
7
- import type { Snippet } from "svelte";
8
- import AnimatedValue from "./animated-value.svelte";
9
- import TrendPill from "./trend-pill.svelte";
10
-
11
- // =========================
12
- // TYPES
13
- // =========================
14
- type Trend = "up" | "down" | "neutral";
15
- type SeriesValue = { value: number; label?: string; color?: string };
16
-
17
- type Props = {
18
- /** Single metric value. Numbers count up via AnimatedValue. Ignored when `series` is set. */
19
- value?: number | string;
20
- /** Multiple value rows (chart headers with more than one series). */
21
- series?: SeriesValue[];
22
- /** Single-value color. */
23
- color?: string;
24
- /** Title above the value (KPI card). Pairs with `delta` for a trend pill. */
25
- title?: string;
26
- /** Percentage change; renders a TrendPill beside the title. */
27
- delta?: number;
28
- trend?: Trend;
29
- /** Sub-caption under the value. */
30
- caption?: string;
31
- /** Uppercase context label under the value (chart headers, e.g. "JUN"). */
32
- eyebrow?: string;
33
- /** Left header (md, monospace) vs centered overlay reuse later. */
34
- align?: "start" | "center";
35
- /** Value scale: md = chart header, lg = KPI card. */
36
- size?: "md" | "lg";
37
- format?: (v: number) => string;
38
- formatOptions?: Intl.NumberFormatOptions;
39
- locale?: string;
40
- animate?: boolean;
41
- /** Leading icon before the title. */
42
- icon?: Snippet;
43
- class?: string;
44
- };
45
-
46
- // =========================
47
- // PROPS
48
- // =========================
49
- let {
50
- value,
51
- series,
52
- color,
53
- title,
54
- delta,
55
- trend,
56
- caption,
57
- eyebrow,
58
- align = "start",
59
- size = "md",
60
- format,
61
- formatOptions,
62
- locale,
63
- animate = true,
64
- icon,
65
- class: className,
66
- }: Props = $props();
67
-
68
- // =========================
69
- // DERIVED
70
- // =========================
71
- const rows = $derived(series && series.length > 0 ? series : null);
72
- </script>
73
-
74
- <!-- =========================== -->
75
- <!-- FIGURE -->
76
- <!-- =========================== -->
77
- <div class={cn("hr-figure", className)} data-align={align} data-size={size}>
78
- {#if title}
79
- <div class="hr-figure-head">
80
- <span class="hr-figure-title">
81
- {#if icon}<span class="hr-figure-icon" style:color>{@render icon()}</span>{/if}
82
- <span class="hr-figure-title-text">{title}</span>
83
- </span>
84
- {#if delta != null}
85
- <TrendPill {delta} {trend} />
86
- {/if}
87
- </div>
88
- {/if}
89
-
90
- {#if rows}
91
- <div class="hr-figure-values">
92
- {#each rows as row, i (i)}
93
- <!-- WHY the series colour goes in as a custom property rather than as `color`: a palette entry is picked to read as a line on a chart, which -->
94
- <!-- is a 3:1 job, and the same value as 11px text measured 2.7:1. The stylesheet mixes it toward the page foreground, so the number still -->
95
- <!-- names its series and the text still clears AA in either theme. -->
96
- <span class="hr-figure-value-row" style:--hr-series={row.color}>
97
- <span class="hr-figure-value is-series">
98
- <AnimatedValue value={row.value} {format} {formatOptions} {locale} duration={450} />
99
- </span>
100
- {#if rows.length > 1 && row.label}
101
- <span class="hr-figure-series-label is-series">{row.label}</span>
102
- {/if}
103
- </span>
104
- {/each}
105
- </div>
106
- {:else if value != null}
107
- <span class="hr-figure-value" style:color>
108
- {#if typeof value === "number" && animate}
109
- <AnimatedValue {value} {format} {formatOptions} {locale} duration={600} />
110
- {:else}
111
- {value}
112
- {/if}
113
- </span>
114
- {/if}
115
-
116
- {#if caption}
117
- <span class="hr-figure-caption">{caption}</span>
118
- {/if}
119
- {#if eyebrow}
120
- <span class="hr-figure-eyebrow">{eyebrow}</span>
121
- {/if}
122
- </div>
@@ -1,94 +0,0 @@
1
- /* Styles for GaugeChart. Plain stylesheet (see area-chart.css for why).
2
- Selectors are namespaced under `.hr-gauge` (the generic ones below would
3
- otherwise collide with RingChart's identical `.value`/`.label`/`.center`/
4
- `.fill`/`svg` names once they are no longer Svelte-scoped). */
5
-
6
- .hr-gauge {
7
- position: relative;
8
- display: inline-grid;
9
- place-items: center;
10
- aspect-ratio: 1;
11
- container-type: size;
12
- }
13
-
14
- .hr-gauge svg {
15
- grid-area: 1 / 1;
16
- width: 100%;
17
- height: 100%;
18
- overflow: visible;
19
- }
20
-
21
- .hr-gauge .center {
22
- grid-area: 1 / 1;
23
- display: flex;
24
- flex-direction: column;
25
- align-items: center;
26
- justify-content: center;
27
- gap: 0.15cqmin;
28
- /* WHY: keep the readout inside the inner ring so long values never collide
29
- with the notches. */
30
- max-width: 62cqmin;
31
- pointer-events: none;
32
- }
33
-
34
- .hr-gauge .value {
35
- font-size: max(0.78rem, 11cqmin);
36
- font-weight: 650;
37
- line-height: 1;
38
- letter-spacing: -0.02em;
39
- font-variant-numeric: tabular-nums;
40
- color: var(--foreground);
41
- }
42
-
43
- .hr-gauge .label {
44
- margin-top: 1.5cqmin;
45
- font-size: max(0.5rem, 5cqmin);
46
- font-weight: 500;
47
- white-space: nowrap;
48
- color: var(--muted-foreground);
49
- }
50
-
51
- /* WHY: active paint comes from the fill attribute (flat or gradient url), so
52
- no CSS fill here, which would override it. */
53
- .hr-gauge .fill {
54
- /* WHY: each active notch fades up in arc order; the per-notch stagger
55
- sweeps the colour around the arc so the gauge reads as filling, instead
56
- of every notch popping in from the centre at once. */
57
- animation: hr-gauge-notch-in 0.3s var(--chart-ease-out, cubic-bezier(0.22, 1, 0.36, 1)) both;
58
- }
59
-
60
- @keyframes hr-gauge-notch-in {
61
- from {
62
- opacity: 0;
63
- }
64
- }
65
-
66
- .hr-gauge .ring-track,
67
- .hr-gauge .ring-fill {
68
- fill: none;
69
- stroke-linecap: round;
70
- }
71
-
72
- .hr-gauge .ring-fill {
73
- stroke-dasharray: 100;
74
- transition: stroke-dashoffset var(--chart-motion-slow, 1.1s)
75
- var(--chart-ease-out, cubic-bezier(0.22, 1, 0.36, 1));
76
- animation: hr-gauge-ring-sweep var(--chart-motion-reveal, 1100ms)
77
- var(--chart-ease-reveal, cubic-bezier(0.85, 0, 0.15, 1)) both;
78
- }
79
-
80
- @keyframes hr-gauge-ring-sweep {
81
- from {
82
- stroke-dashoffset: 100;
83
- }
84
- }
85
-
86
- @media (prefers-reduced-motion: reduce) {
87
- .hr-gauge .fill {
88
- animation: none;
89
- }
90
- .hr-gauge .ring-fill {
91
- transition: none;
92
- animation: none;
93
- }
94
- }