@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
@@ -0,0 +1,91 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import { Button } from "../../shadcn/button/index.js";
6
+ import * as Empty from "../../shadcn/empty/index.js";
7
+ import type { PhosphorIcon, IconWeight } from "../../lib/icon-types.js";
8
+
9
+ // =========================
10
+ // TYPES
11
+ // =========================
12
+ interface Props {
13
+ icon?: PhosphorIcon;
14
+ iconWeight?: IconWeight;
15
+ title: string;
16
+ description?: string;
17
+ actionLabel?: string;
18
+ actionHref?: string;
19
+ onAction?: () => void;
20
+ }
21
+
22
+ // =========================
23
+ // PROPS
24
+ // =========================
25
+ let {
26
+ icon: Icon,
27
+ iconWeight,
28
+ title,
29
+ description,
30
+ actionLabel,
31
+ actionHref,
32
+ onAction,
33
+ }: Props = $props();
34
+ </script>
35
+
36
+ <!-- =========================== -->
37
+ <!-- MARKUP -->
38
+ <!-- =========================== -->
39
+ <!-- The four parts arrive in reading order. The stagger is what says the page
40
+ finished loading and is simply empty. -->
41
+ <Empty.Root class="t-empty py-16">
42
+ <Empty.Header class="max-w-md">
43
+ {#if Icon}
44
+ <Empty.Media class="mb-4 size-16 rounded-2xl bg-brand-indigo/10 text-brand-indigo">
45
+ <Icon weight={iconWeight} class="size-8" />
46
+ </Empty.Media>
47
+ {/if}
48
+ <Empty.Title class="font-heading text-lg font-semibold">{title}</Empty.Title>
49
+ {#if description}
50
+ <Empty.Description>{description}</Empty.Description>
51
+ {/if}
52
+ </Empty.Header>
53
+ {#if actionLabel && (actionHref || onAction)}
54
+ <Empty.Content>
55
+ {#if actionHref}
56
+ <Button href={actionHref}>{actionLabel}</Button>
57
+ {:else}
58
+ <Button onclick={onAction}>{actionLabel}</Button>
59
+ {/if}
60
+ </Empty.Content>
61
+ {/if}
62
+ </Empty.Root>
63
+
64
+ <!-- =========================== -->
65
+ <!-- STYLE -->
66
+ <!-- =========================== -->
67
+ <style>
68
+ /* Never `:nth-child` here: one such rule makes every DOM insert on the page restyle its later siblings. */
69
+ :global(.t-empty) > :global(*) {
70
+ animation: t-empty-in var(--duration-very-slow) var(--ease-smooth-out) both;
71
+ animation-delay: calc((sibling-index() - 1) * var(--duration-stagger));
72
+ }
73
+
74
+ @keyframes -global-t-empty-in {
75
+ from {
76
+ opacity: 0;
77
+ transform: translateY(var(--distance-small));
78
+ }
79
+ to {
80
+ opacity: 1;
81
+ transform: none;
82
+ }
83
+ }
84
+
85
+ @media (prefers-reduced-motion: reduce) {
86
+ :global(.t-empty) > :global(*) {
87
+ animation-duration: 1ms;
88
+ animation-delay: 0ms;
89
+ }
90
+ }
91
+ </style>
@@ -0,0 +1,49 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import type { Snippet } from "svelte";
6
+ import { Checkbox } from "../../shadcn/checkbox/index.js";
7
+ import { Check } from "phosphor-svelte";
8
+ import { cn } from "@heyramzi/utils";
9
+
10
+ // =========================
11
+ // TYPES
12
+ // =========================
13
+ type Props = {
14
+ checked: boolean;
15
+ onclick: () => void;
16
+ variant?: "checkbox" | "radio";
17
+ children: Snippet;
18
+ };
19
+
20
+ // =========================
21
+ // PROPS
22
+ // =========================
23
+ let { checked, onclick, variant = "checkbox", children }: Props = $props();
24
+ </script>
25
+
26
+ <!-- =========================== -->
27
+ <!-- MARKUP -->
28
+ <!-- =========================== -->
29
+ <button
30
+ type="button"
31
+ class="flex items-center gap-2 rounded-lg px-2 py-1.5 text-sm transition-colors hover:bg-muted"
32
+ {onclick}
33
+ >
34
+ {#if variant === "radio"}
35
+ <span
36
+ class={cn(
37
+ "grid size-3.5 shrink-0 place-items-center rounded-full border transition-colors",
38
+ checked ? "border-primary bg-primary text-primary-foreground" : "border-border",
39
+ )}
40
+ >
41
+ {#if checked}
42
+ <Check class="size-2.5" weight="bold" />
43
+ {/if}
44
+ </span>
45
+ {:else}
46
+ <Checkbox {checked} />
47
+ {/if}
48
+ {@render children()}
49
+ </button>
@@ -0,0 +1,88 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import type { Snippet } from "svelte";
6
+ import * as Popover from "../../shadcn/popover/index.js";
7
+ import { Funnel as FunnelIcon } from "phosphor-svelte";
8
+ import { cn } from "@heyramzi/utils";
9
+ import { Button } from "../../shadcn/button/index.js";
10
+ import { toolbarTriggerClass } from "./toolbar-trigger.js";
11
+ import type { PhosphorIcon } from "../../lib/icon-types.js";
12
+
13
+ // phosphor-svelte v2 (resolved here) and v3 (what consumers run) differ only in
14
+ // Component generics. The cast bridges that type-only gap.
15
+ const DefaultFunnelIcon = FunnelIcon as unknown as PhosphorIcon;
16
+
17
+ // =========================
18
+ // TYPES
19
+ // =========================
20
+ type Props = {
21
+ label: string;
22
+ icon?: PhosphorIcon;
23
+ count?: number;
24
+ active?: boolean;
25
+ onClear?: () => void;
26
+ align?: "start" | "center" | "end";
27
+ width?: `w-${string}`;
28
+ open?: boolean;
29
+ clearLabel?: string;
30
+ triggerClass?: string;
31
+ contentClass?: string;
32
+ children: Snippet;
33
+ trailing?: Snippet;
34
+ };
35
+
36
+ // =========================
37
+ // PROPS
38
+ // =========================
39
+ let {
40
+ label,
41
+ icon: IconComponent = DefaultFunnelIcon,
42
+ count = 0,
43
+ active,
44
+ onClear,
45
+ align = "start",
46
+ width = "w-52",
47
+ open = $bindable(false),
48
+ clearLabel = "Clear",
49
+ triggerClass,
50
+ contentClass,
51
+ children,
52
+ trailing,
53
+ }: Props = $props();
54
+
55
+ // =========================
56
+ // DERIVED
57
+ // =========================
58
+ let hasCount = $derived(count > 0);
59
+ let isActive = $derived(active ?? hasCount);
60
+ </script>
61
+
62
+ <!-- =========================== -->
63
+ <!-- MARKUP -->
64
+ <!-- =========================== -->
65
+ <Popover.Root bind:open>
66
+ <Popover.Trigger class={toolbarTriggerClass({ active: isActive, className: triggerClass })}>
67
+ <IconComponent class="size-3.5" />
68
+ <span>{label}</span>
69
+ {#if trailing}
70
+ {@render trailing()}
71
+ {/if}
72
+ {#if hasCount}
73
+ <span
74
+ class="ml-0.5 inline-flex size-4 items-center justify-center rounded-full bg-brand-indigo/15 text-[10px] font-semibold text-brand-indigo dark:text-brand-indigo-light"
75
+ >
76
+ {count}
77
+ </span>
78
+ {/if}
79
+ </Popover.Trigger>
80
+ <Popover.Content class={cn("p-2", width, contentClass)} {align}>
81
+ {@render children()}
82
+ {#if onClear && hasCount}
83
+ <Button variant="ghost" size="sm" class="mt-2 w-full text-xs text-muted-foreground" onclick={onClear}>
84
+ {clearLabel}
85
+ </Button>
86
+ {/if}
87
+ </Popover.Content>
88
+ </Popover.Root>
@@ -0,0 +1,12 @@
1
+ import { cn } from "@heyramzi/utils";
2
+
3
+ // The header for each control group in a toolbar popover, in one place so
4
+ // sections can't drift in size or weight.
5
+
6
+ // `first:` drops the leading header's top padding, since the popover pads itself.
7
+ export function filterSectionClass(className?: string): string {
8
+ return cn(
9
+ "px-2 pt-2 pb-1 text-[11px] font-medium uppercase tracking-wide text-muted-foreground first:pt-1",
10
+ className,
11
+ );
12
+ }
@@ -0,0 +1,116 @@
1
+ <script lang="ts" generics="TGroup">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import { untrack, type Snippet } from "svelte";
6
+ import { SvelteSet } from "svelte/reactivity";
7
+ import { CaretDown } from "phosphor-svelte";
8
+ import { cn } from "@heyramzi/utils";
9
+
10
+ // =========================
11
+ // TYPES
12
+ // =========================
13
+ type Props = {
14
+ groups: TGroup[];
15
+ getKey: (group: TGroup) => string;
16
+ defaultCollapsed?: string[];
17
+ /** WHY: a page that drives its own keyboard cursor has to know which rows
18
+ are on screen, and the collapsed set is the only thing that says so. */
19
+ onCollapsedChange?: (keys: string[]) => void;
20
+ header: Snippet<[TGroup, { collapsed: boolean }]>;
21
+ /** Controls beside the header, outside its toggle button so a click on
22
+ one doesn't collapse the group. Shown on hover of the header row. */
23
+ actions?: Snippet<[TGroup]>;
24
+ rows: Snippet<[TGroup]>;
25
+ class?: string;
26
+ onGroupDragOver?: (e: DragEvent, group: TGroup) => void;
27
+ onGroupDragLeave?: (e: DragEvent) => void;
28
+ onGroupDrop?: (e: DragEvent, group: TGroup) => void;
29
+ };
30
+
31
+ // =========================
32
+ // PROPS
33
+ // =========================
34
+ let {
35
+ groups,
36
+ getKey,
37
+ defaultCollapsed = [],
38
+ onCollapsedChange,
39
+ header,
40
+ actions,
41
+ rows,
42
+ class: className,
43
+ onGroupDragOver,
44
+ onGroupDragLeave,
45
+ onGroupDrop,
46
+ }: Props = $props();
47
+
48
+ // =========================
49
+ // STATE
50
+ // =========================
51
+ const collapsed = new SvelteSet<string>(untrack(() => defaultCollapsed));
52
+
53
+ // =========================
54
+ // FUNCTIONS
55
+ // =========================
56
+ function toggle(key: string) {
57
+ if (collapsed.has(key)) {
58
+ collapsed.delete(key);
59
+ } else {
60
+ collapsed.add(key);
61
+ }
62
+ onCollapsedChange?.([...collapsed]);
63
+ }
64
+ </script>
65
+
66
+ <!-- =========================== -->
67
+ <!-- MARKUP -->
68
+ <!-- =========================== -->
69
+ <div class={cn("flex w-full flex-col", className)}>
70
+ {#each groups as group, index (getKey(group))}
71
+ {@const key = getKey(group)}
72
+ {@const isCollapsed = collapsed.has(key)}
73
+ <div
74
+ class={cn(index > 0 && "mt-3 border-t border-border/50 pt-3")}
75
+ role={onGroupDragOver || onGroupDrop ? "group" : undefined}
76
+ ondragover={onGroupDragOver ? (e) => onGroupDragOver(e, group) : undefined}
77
+ ondragleave={onGroupDragLeave}
78
+ ondrop={onGroupDrop ? (e) => onGroupDrop(e, group) : undefined}
79
+ >
80
+ <div class="group/header flex items-center gap-1 rounded-lg transition-colors hover:bg-muted/60">
81
+ <button
82
+ type="button"
83
+ class="flex min-w-0 flex-1 cursor-pointer items-center gap-2.5 rounded-lg px-3 py-2 text-left"
84
+ onclick={() => toggle(key)}
85
+ aria-expanded={!isCollapsed}
86
+ >
87
+ <CaretDown
88
+ class={cn(
89
+ "size-3 shrink-0 text-muted-foreground/60 transition-transform duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)]",
90
+ isCollapsed && "-rotate-90",
91
+ )}
92
+ weight="bold"
93
+ />
94
+ {@render header(group, { collapsed: isCollapsed })}
95
+ </button>
96
+ {#if actions}
97
+ <div
98
+ class="flex shrink-0 items-center gap-1 pe-2 opacity-0 transition-opacity group-hover/header:opacity-100 focus-within:opacity-100 any-pointer-coarse:opacity-100"
99
+ >
100
+ {@render actions(group)}
101
+ </div>
102
+ {/if}
103
+ </div>
104
+
105
+ {#if !isCollapsed}
106
+ <div>
107
+ {@render rows(group)}
108
+ </div>
109
+ {/if}
110
+ </div>
111
+ {/each}
112
+ </div>
113
+
114
+ <!-- No entrance motion. Four stacked animations made the last group of a
115
+ 40-row board arrive two seconds late. Working surfaces don't animate in. -->
116
+ <!-- No `overflow: hidden` either: it clipped anything a row opened. -->
@@ -0,0 +1,28 @@
1
+ export { default as DetailEmpty } from "./detail-empty.svelte";
2
+ export { default as DetailHeader } from "./detail-header.svelte";
3
+ export { default as DetailPanel } from "./detail-panel.svelte";
4
+ export { default as DetailRow } from "./detail-row.svelte";
5
+ export { default as DetailStack } from "./detail-stack.svelte";
6
+ export { default as EmptyState } from "./empty-state.svelte";
7
+ export { default as FilterItem } from "./filter-item.svelte";
8
+ export { default as PrevNext } from "./prev-next.svelte";
9
+ export { stepTo } from "./step.js";
10
+ export { default as NoticeBar } from "./notice-bar.svelte";
11
+ export { default as RailBody } from "./rail-body.svelte";
12
+ export { default as RailEmpty } from "./rail-empty.svelte";
13
+ export { default as RailHead } from "./rail-head.svelte";
14
+ export { default as RailRow } from "./rail-row.svelte";
15
+ export { default as SectionLabel } from "./section-label.svelte";
16
+ export { queueRailClass, queueRailWidth } from "./queue-rail.js";
17
+ export { default as FilterPopover } from "./filter-popover.svelte";
18
+ export { filterSectionClass } from "./filter-section.js";
19
+ export { default as GroupedList } from "./grouped-list.svelte";
20
+ export { default as ListPageShell } from "./list-page-shell.svelte";
21
+ export { default as MasterDetail } from "./master-detail.svelte";
22
+ // The segmented control isn't re-exported here. Two import paths drifted, so
23
+ // its one home is `@heyramzi/ui/components/segmented`.
24
+ export { default as ToolbarSearch } from "./toolbar-search.svelte";
25
+ export { toolbarTriggerClass } from "./toolbar-trigger.js";
26
+ export { listTable } from "./list-table.js";
27
+ export type { ViewMode } from "./types.js";
28
+ export { getSectionHost, setSectionHost, type SectionHost } from "./section-host.js";
@@ -0,0 +1,195 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import type { Snippet } from "svelte";
6
+ import EmptyState from "./empty-state.svelte";
7
+ import { cn } from "@heyramzi/utils";
8
+ import { pageGutterClass } from "../../lib/layout.js";
9
+ import { getSectionHost } from "./section-host.js";
10
+ import SectionRuns from "../segmented/section-runs.svelte";
11
+ import type { PhosphorIcon, IconWeight } from "../../lib/icon-types.js";
12
+
13
+ // =========================
14
+ // TYPES
15
+ // =========================
16
+ type EmptyConfig = {
17
+ icon?: PhosphorIcon;
18
+ iconWeight?: IconWeight;
19
+ title: string;
20
+ description?: string;
21
+ actionLabel?: string;
22
+ onAction?: () => void;
23
+ actionHref?: string;
24
+ };
25
+
26
+ type Props = {
27
+ isEmpty?: boolean;
28
+ emptyState?: EmptyConfig;
29
+ hideToolbarWhenEmpty?: boolean;
30
+ maxWidth?: string | false;
31
+ fillHeight?: boolean;
32
+ containerClass?: string;
33
+ toolbarClass?: string;
34
+ tabs?: Snippet;
35
+ search?: Snippet;
36
+ filters?: Snippet;
37
+ rightActions?: Snippet;
38
+ summary?: Snippet;
39
+ content?: Snippet;
40
+ empty?: Snippet;
41
+ extra?: Snippet;
42
+ };
43
+
44
+ // =========================
45
+ // PROPS
46
+ // =========================
47
+ let {
48
+ isEmpty = false,
49
+ emptyState,
50
+ hideToolbarWhenEmpty = false,
51
+ maxWidth = "1600px",
52
+ fillHeight = false,
53
+ containerClass,
54
+ toolbarClass,
55
+ tabs,
56
+ search,
57
+ filters,
58
+ rightActions,
59
+ summary,
60
+ content,
61
+ empty,
62
+ extra,
63
+ }: Props = $props();
64
+
65
+ // =========================
66
+ // STATE
67
+ // =========================
68
+ // Null off an app shell, where the toolbar just has no section strip to draw.
69
+ const section = getSectionHost();
70
+
71
+ // =========================
72
+ // DERIVED
73
+ // =========================
74
+ let hideChrome = $derived(isEmpty && hideToolbarWhenEmpty);
75
+ // WHY the section strip counts: on a page whose only chrome is the section
76
+ // nav, this row is what draws it, and skipping the row would drop the tabs.
77
+ let hasToolbar = $derived(
78
+ Boolean(search || filters || tabs || rightActions || section?.strip) && !hideChrome,
79
+ );
80
+ // Whether anything holds the left of the row. With nothing there, the tabs
81
+ // take that edge instead of floating mid-row beside the actions.
82
+ let hasLeading = $derived(Boolean(search || filters || section?.strip));
83
+
84
+ // =========================
85
+ // EFFECTS
86
+ // =========================
87
+ // Claiming stops the app shell drawing its own strip above this page.
88
+ // Unmount releases it, so shell-less pages get the standalone row back.
89
+ $effect(() => {
90
+ if (!hasToolbar) return;
91
+ return section?.claim();
92
+ });
93
+ // The shared gutter from `lib/layout.ts`. A typed one put the search field
94
+ // 8px left of the section tabs above it.
95
+ let outerClass = $derived(
96
+ cn(
97
+ `flex flex-col gap-4 py-4 ${pageGutterClass}`,
98
+ fillHeight && "min-h-0 flex-1",
99
+ !fillHeight && "flex-1",
100
+ containerClass,
101
+ ),
102
+ );
103
+ // flex-1 lets a snippet pin a footer with mt-auto. min-w-0 keeps wide
104
+ // tables and chip rows from causing horizontal scroll.
105
+ let innerClass = $derived(
106
+ maxWidth === false
107
+ ? cn("flex min-w-0 flex-1 flex-col gap-4", fillHeight && "min-h-0")
108
+ : cn(
109
+ "mx-auto flex w-full min-w-0 flex-1 flex-col gap-4",
110
+ fillHeight && "min-h-0",
111
+ ),
112
+ );
113
+ let innerStyle = $derived(maxWidth ? `max-width: ${maxWidth}` : undefined);
114
+ </script>
115
+
116
+ <!-- =========================== -->
117
+ <!-- MARKUP -->
118
+ <!-- =========================== -->
119
+ <div class={outerClass}>
120
+ <div class={innerClass} style={innerStyle}>
121
+ {#if hasToolbar}
122
+ <!-- One row on every list page: search and filters left, then tabs,
123
+ view toggle and primary action right. -->
124
+ <!-- Without room, `order` keeps the actions beside search and drops
125
+ filters to a scrolling line below. flex-wrap stays on at every width. -->
126
+ <!-- A container query, not a breakpoint: an 834px iPad read as desktop
127
+ and clipped a 733px toolbar into a 538px column. -->
128
+ <!-- Sticky, because the section tabs live here. `-mx-4 px-4` covers the
129
+ gutter, `-mt-4 py-4` pulls the top padding inside the sticky box. -->
130
+ <div
131
+ class="@container/toolbar sticky top-0 z-20 -mx-4 -mt-4 bg-background/95 px-4 py-4 backdrop-blur supports-[backdrop-filter]:bg-background/70"
132
+ >
133
+ <div class={cn("flex flex-wrap items-center gap-2", toolbarClass)}>
134
+ <!-- The section tabs lead: which page comes before which slice of it.
135
+ One row replaces two rows that held four controls. -->
136
+ {#if section?.strip}
137
+ <SectionRuns
138
+ strip={section.strip}
139
+ class="order-first w-full max-w-full @3xl/toolbar:w-auto"
140
+ />
141
+ {/if}
142
+ {#if search}
143
+ <div class="order-1 flex min-w-0 flex-1 items-center @3xl/toolbar:flex-none">
144
+ {@render search()}
145
+ </div>
146
+ {/if}
147
+ {#if tabs || rightActions}
148
+ <!-- Shrinks and wraps, not shrink-0: /tasks needs 610px here, and
149
+ shrink-0 clipped its last control off an iPad screen. -->
150
+ <div
151
+ class={cn(
152
+ "order-2 flex min-w-0 flex-wrap items-center gap-2 @3xl/toolbar:order-3",
153
+ hasLeading ? "@3xl/toolbar:ms-auto" : "flex-1",
154
+ )}
155
+ >
156
+ {#if tabs}
157
+ <div class={cn("flex min-w-0 items-center", !hasLeading && "me-auto")}>
158
+ {@render tabs()}
159
+ </div>
160
+ {/if}
161
+ {#if rightActions}
162
+ {@render rightActions()}
163
+ {/if}
164
+ </div>
165
+ {/if}
166
+ {#if filters}
167
+ <div
168
+ class="no-scrollbar order-3 flex w-full max-w-full min-w-0 items-center gap-2 overflow-x-auto @3xl/toolbar:order-2 @3xl/toolbar:w-auto"
169
+ >
170
+ {@render filters()}
171
+ </div>
172
+ {/if}
173
+ </div>
174
+ </div>
175
+ {/if}
176
+
177
+ {#if summary && !hideChrome}
178
+ {@render summary()}
179
+ {/if}
180
+
181
+ {#if extra && !hideChrome}
182
+ {@render extra()}
183
+ {/if}
184
+
185
+ {#if isEmpty}
186
+ {#if empty}
187
+ {@render empty()}
188
+ {:else if emptyState}
189
+ <EmptyState {...emptyState} />
190
+ {/if}
191
+ {:else if content}
192
+ {@render content()}
193
+ {/if}
194
+ </div>
195
+ </div>
@@ -0,0 +1,19 @@
1
+ import { cn } from "@heyramzi/utils";
2
+
3
+ /**
4
+ * The list-page table rhythm, Attio's: one 40px row, a hairline under each, a flat hover.
5
+ */
6
+ export const listTable = {
7
+ /** The header row: 36px, a hairline under it. On the cells: `thead` zeroes its row's border. */
8
+ headRow: (className?: string) =>
9
+ cn(
10
+ "hover:translate-y-0 hover:bg-transparent hover:shadow-none [&>th]:h-9 [&>th]:border-b [&>th]:border-border",
11
+ className,
12
+ ),
13
+ /** A body row. The shared row lifts on hover; a flush list row does not. */
14
+ row: (className?: string) =>
15
+ cn(
16
+ "border-b border-border/50 last:border-b-0 hover:translate-y-0 hover:bg-muted/40 hover:shadow-none [&>td]:h-10 [&>td]:py-0",
17
+ className,
18
+ ),
19
+ };