@heyramzi/ui 2.0.1 → 2.2.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 (341) hide show
  1. package/package.json +48 -24
  2. package/scripts/sync-shadcn.mjs +3 -4
  3. package/src/agents/agent-avatar.svelte +604 -575
  4. package/src/agents/agent-cast.svelte +179 -183
  5. package/src/agents/demo.svelte +125 -113
  6. package/src/agents/helpers.ts +23 -17
  7. package/src/agents/index.ts +4 -4
  8. package/src/agents/page-state.ts +6 -6
  9. package/src/agents/types.ts +6 -6
  10. package/src/ai/ai-button/AiButton.svelte +4 -12
  11. package/src/ai/generation-progress/GenerationProgress.svelte +10 -3
  12. package/src/ai/prompt-input/PromptInputTextarea.svelte +1 -2
  13. package/src/ai/response/Response.css +2 -3
  14. package/src/animations/avatar-group-hover.css +2 -16
  15. package/src/animations/card-tilt.css +3 -6
  16. package/src/animations/digit-pop-in.css +2 -15
  17. package/src/animations/dropdown.css +4 -17
  18. package/src/animations/input-clear-dissolve.css +5 -18
  19. package/src/animations/input-shake.css +9 -41
  20. package/src/animations/learn-more-hover.css +17 -9
  21. package/src/animations/like-button.css +2 -4
  22. package/src/animations/page-side-by-side.css +2 -10
  23. package/src/animations/plus-menu-morph.css +2 -5
  24. package/src/animations/shimmer-text.css +5 -18
  25. package/src/animations/spinning-counter.css +2 -6
  26. package/src/animations/success-check.css +2 -15
  27. package/src/animations/thinking-states.css +2 -7
  28. package/src/brand/brand.test.ts +21 -5
  29. package/src/brand/index.ts +4 -10
  30. package/src/canvas/README.md +9 -12
  31. package/src/canvas/card/card-frame.svelte +43 -39
  32. package/src/canvas/card/resize-handles.svelte +89 -80
  33. package/src/canvas/chrome/chrome-zoom.svelte +101 -100
  34. package/src/canvas/chrome/lock-badge.svelte +8 -8
  35. package/src/canvas/chrome/side-handles.svelte +24 -19
  36. package/src/canvas/chrome/size-chip.svelte +30 -30
  37. package/src/canvas/chrome/snap-guides.svelte +20 -20
  38. package/src/canvas/components/icons/ToolIcon.svelte +8 -8
  39. package/src/canvas/components/shared/svg/PreviewSvg.svelte +19 -19
  40. package/src/canvas/components/shared/svg/ShapeOutline.svelte +71 -48
  41. package/src/canvas/components/shared/svg/StrokeNode.svelte +21 -27
  42. package/src/canvas/components/shared/svg/TextRule.svelte +25 -17
  43. package/src/canvas/edges/bend-handle.svelte +46 -46
  44. package/src/canvas/edges/edge-gradient.svelte +10 -10
  45. package/src/canvas/edges/gradient-edge.svelte +45 -29
  46. package/src/canvas/editor/lock.test.ts +6 -1
  47. package/src/canvas/editor/style.ts +7 -1
  48. package/src/canvas/headless/svg-figure.test.ts +5 -1
  49. package/src/canvas/lod/lod-switch.svelte +111 -106
  50. package/src/canvas/pen/PenField.svelte +87 -81
  51. package/src/canvas/pen/PenOverlay.svelte +127 -127
  52. package/src/canvas/pen/ShapeNode.svelte +31 -32
  53. package/src/canvas/pen/TextNode.svelte +54 -53
  54. package/src/canvas/pen/host-field.test.svelte +7 -7
  55. package/src/canvas/pen/shapes.ts +7 -1
  56. package/src/canvas/screen/GlyphIcon.svelte +22 -17
  57. package/src/canvas/screen/PartView.svelte +138 -72
  58. package/src/canvas/screen/ScreenBody.svelte +81 -60
  59. package/src/canvas/screen/ScreenCard.svelte +20 -20
  60. package/src/canvas/ui/CanvasToolbar.svelte +173 -85
  61. package/src/canvas/ui/FrameLayer.svelte +59 -59
  62. package/src/canvas/ui/PenLayer.svelte +39 -30
  63. package/src/canvas/ui/StylePanel.svelte +119 -102
  64. package/src/canvas/ui/canvas-grid.svelte +137 -129
  65. package/src/charts/animated-value.svelte +1 -1
  66. package/src/charts/area-chart.css +2 -5
  67. package/src/charts/area-chart.svelte +1 -1
  68. package/src/charts/bar-chart.css +2 -3
  69. package/src/charts/bar-chart.svelte +1 -1
  70. package/src/charts/charts.css +5 -17
  71. package/src/charts/crosshair.svelte +1 -1
  72. package/src/charts/dashboard/dashboard-empty.svelte +25 -25
  73. package/src/charts/dashboard/dashboard-section.svelte +24 -24
  74. package/src/charts/dashboard/data-panel.svelte +50 -50
  75. package/src/charts/dashboard/data-sort-head.svelte +56 -56
  76. package/src/charts/dashboard/data-table.svelte +138 -138
  77. package/src/charts/dashboard/panel-grid.svelte +30 -30
  78. package/src/charts/dashboard/period-filter.svelte +25 -25
  79. package/src/charts/dashboard/rating-chip.svelte +19 -19
  80. package/src/charts/dashboard/stat-band.svelte +32 -32
  81. package/src/charts/dashboard/stat-tile.svelte +106 -99
  82. package/src/charts/donut-chart.svelte +1 -1
  83. package/src/charts/edge-fade.svelte +1 -1
  84. package/src/charts/figure.svelte +1 -1
  85. package/src/charts/gauge-chart.css +4 -7
  86. package/src/charts/gauge-chart.svelte +1 -1
  87. package/src/charts/hover-focus.svelte +1 -1
  88. package/src/charts/legend.css +1 -3
  89. package/src/charts/legend.svelte +1 -1
  90. package/src/charts/line-chart.svelte +1 -1
  91. package/src/charts/number-chart.css +6 -9
  92. package/src/charts/number-chart.svelte +1 -1
  93. package/src/charts/reference-line.svelte +19 -19
  94. package/src/charts/reveal.svelte +1 -1
  95. package/src/charts/ring-chart.css +2 -4
  96. package/src/charts/ring-chart.svelte +1 -1
  97. package/src/charts/sparkline.svelte +5 -2
  98. package/src/charts/stat-card.svelte +1 -1
  99. package/src/charts/time-series-chart.css +2 -5
  100. package/src/charts/trend-pill.css +2 -4
  101. package/src/charts/trend-pill.svelte +1 -1
  102. package/src/components/copy/copy-button.svelte +93 -98
  103. package/src/components/copy/copy-button.test.ts +10 -2
  104. package/src/components/data-table/infinite-scroll.svelte +42 -42
  105. package/src/components/data-table/pagination.svelte +34 -22
  106. package/src/components/data-table/row-arrow.svelte +19 -16
  107. package/src/components/data-table/row-context-menu.svelte +52 -52
  108. package/src/components/data-table/sortable-head.svelte +25 -25
  109. package/src/components/data-table/table-skeleton.svelte +41 -36
  110. package/src/components/docs/doc-page.svelte +18 -9
  111. package/src/components/docs/on-this-page.svelte +16 -3
  112. package/src/components/docs/scroll-spy.svelte.ts +2 -3
  113. package/src/components/field-row/field-row.svelte +10 -8
  114. package/src/components/hold-to-confirm/hold-to-confirm.svelte +5 -1
  115. package/src/components/icon/icon.svelte +14 -14
  116. package/src/components/language-flag/language-flag.svelte +3 -1
  117. package/src/components/list-page/detail-empty.svelte +34 -34
  118. package/src/components/list-page/detail-header.svelte +55 -47
  119. package/src/components/list-page/detail-panel.svelte +51 -59
  120. package/src/components/list-page/detail-row.svelte +19 -19
  121. package/src/components/list-page/detail-stack.svelte +21 -21
  122. package/src/components/list-page/empty-state.svelte +30 -30
  123. package/src/components/list-page/filter-item.svelte +20 -20
  124. package/src/components/list-page/filter-popover.svelte +60 -55
  125. package/src/components/list-page/grouped-list.svelte +4 -2
  126. package/src/components/list-page/index.ts +8 -4
  127. package/src/components/list-page/list-page-shell.svelte +2 -5
  128. package/src/components/list-page/master-detail.svelte +87 -88
  129. package/src/components/list-page/notice-bar.svelte +38 -38
  130. package/src/components/list-page/prev-next.svelte +63 -63
  131. package/src/components/list-page/rail-body.svelte +18 -18
  132. package/src/components/list-page/rail-empty.svelte +23 -30
  133. package/src/components/list-page/rail-head.svelte +24 -24
  134. package/src/components/list-page/rail-row.svelte +77 -73
  135. package/src/components/list-page/section-host.ts +2 -3
  136. package/src/components/locale-switch/locale-switch.svelte +41 -41
  137. package/src/components/mark/mark.svelte +34 -38
  138. package/src/components/open-in/index.ts +1 -5
  139. package/src/components/open-in/open-in.svelte +40 -40
  140. package/src/components/picker/mark-picker.svelte +49 -49
  141. package/src/components/picker/picker.svelte +5 -3
  142. package/src/components/section-header/index.ts +1 -0
  143. package/src/components/section-header/section-header.svelte +1 -1
  144. package/src/components/section-header/section-header.test.ts +7 -1
  145. package/src/components/section-header/section-label.svelte +39 -0
  146. package/src/components/segmented/animated-tabs.svelte +31 -2
  147. package/src/components/segmented/collapse.test.ts +2 -1
  148. package/src/components/segmented/index.ts +2 -0
  149. package/src/components/segmented/segment.svelte +52 -49
  150. package/src/components/segmented/segmented-filter.svelte +79 -80
  151. package/src/components/segmented/segmented-nav.svelte +34 -40
  152. package/src/components/segmented/segmented-overflow.svelte +53 -50
  153. package/src/components/segmented/segmented-track.svelte +62 -46
  154. package/src/components/segmented/segmented.ts +2 -3
  155. package/src/components/star-rating-input/star-rating-input.svelte +103 -103
  156. package/src/components/svg/svg.svelte +4 -4
  157. package/src/components/table-view/table-view.svelte +17 -2
  158. package/src/components/table-view/table-view.test.ts +7 -1
  159. package/src/components/table-view/types.ts +11 -11
  160. package/src/components/tags-input/index.ts +13 -0
  161. package/src/components/tags-input/tags-input.svelte +305 -0
  162. package/src/components/tags-input/tags.test.ts +64 -0
  163. package/src/components/tags-input/tags.ts +142 -0
  164. package/src/components/theme/index.ts +2 -4
  165. package/src/components/theme/theme-provider.svelte +1 -3
  166. package/src/components/toolbar/index.ts +2 -0
  167. package/src/components/{list-page → toolbar}/toolbar-search.svelte +25 -25
  168. package/src/components/toolbar/toolbar.svelte +13 -9
  169. package/src/components/toolbar/toolbar.test.ts +10 -1
  170. package/src/design/index.css +21 -78
  171. package/src/design/polish.css +26 -35
  172. package/src/editor/editor-toolbar.svelte +104 -99
  173. package/src/editor/editor.svelte +296 -266
  174. package/src/editor/image-toolbar.svelte +20 -22
  175. package/src/editor/index.ts +1 -4
  176. package/src/editor/inline-editor.svelte +280 -238
  177. package/src/editor/markdown.ts +15 -15
  178. package/src/editor/renderer.svelte +71 -71
  179. package/src/editor/slash-menu.svelte +24 -27
  180. package/src/effects/animated-beam/diagrams/BeamSvg.svelte +1 -1
  181. package/src/effects/image-comparison/ImageComparison.svelte +11 -1
  182. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +59 -46
  183. package/src/effects/magic-card/MagicCard.css +14 -7
  184. package/src/effects/marquee/Marquee.svelte +3 -5
  185. package/src/effects/reveal/observe.ts +0 -3
  186. package/src/effects/spotlight/Spotlight.svelte +1 -1
  187. package/src/effects/theme-toggle/ThemeToggle.css +25 -22
  188. package/src/effects/theme-toggle/ThemeToggle.svelte +6 -7
  189. package/src/gallery.test.ts +14 -4
  190. package/src/inbox/README.md +27 -0
  191. package/src/inbox/inbox.test.ts +214 -0
  192. package/src/inbox/index.ts +31 -0
  193. package/src/inbox/logic.ts +169 -0
  194. package/src/inbox/thread-inbox.svelte +439 -0
  195. package/src/inbox/types.ts +161 -0
  196. package/src/lib/theme-transition.ts +2 -4
  197. package/src/lib/tone.ts +2 -4
  198. package/src/lib/utils.ts +2 -2
  199. package/src/modules.test.ts +105 -0
  200. package/src/onboarding/define-tours.test.ts +55 -40
  201. package/src/onboarding/define-tours.ts +23 -23
  202. package/src/onboarding/demo.svelte +24 -5
  203. package/src/onboarding/index.ts +1 -8
  204. package/src/onboarding/onboarding-provider.svelte +19 -7
  205. package/src/onboarding/onboarding.svelte.ts +16 -16
  206. package/src/onboarding/pick.test.ts +61 -52
  207. package/src/onboarding/pick.ts +16 -13
  208. package/src/onboarding/stores.test.ts +80 -80
  209. package/src/onboarding/stores.ts +41 -39
  210. package/src/onboarding/tour-card.svelte +30 -5
  211. package/src/onboarding/tour-runner.svelte +12 -3
  212. package/src/onboarding/types.ts +20 -12
  213. package/src/onboarding/wait.ts +22 -22
  214. package/src/panel/README.md +5 -0
  215. package/src/panel/index.ts +2 -0
  216. package/src/panel/side-panel.svelte +194 -0
  217. package/src/record/README.md +3 -0
  218. package/src/record/demo-data.ts +352 -0
  219. package/src/record/field-section.svelte +57 -0
  220. package/src/record/fields.ts +194 -0
  221. package/src/record/format.ts +94 -0
  222. package/src/{tasks/record → record}/index.ts +49 -35
  223. package/src/{tasks/record → record}/info-list.svelte +145 -114
  224. package/src/record/kinds.ts +42 -0
  225. package/src/record/logic.ts +326 -0
  226. package/src/record/next-up.svelte +81 -0
  227. package/src/{tasks/record → record}/project-list.svelte +56 -50
  228. package/src/{tasks/record → record}/record-demo.svelte +130 -110
  229. package/src/{tasks/record → record}/record-header.svelte +37 -28
  230. package/src/{tasks/record → record}/record-shell.svelte +54 -54
  231. package/src/record/record-stats.svelte +48 -0
  232. package/src/record/record-timeline.svelte +251 -0
  233. package/src/record/record.test.ts +471 -0
  234. package/src/{tasks/record → record}/steps-rail.svelte +225 -216
  235. package/src/record/types.ts +299 -0
  236. package/src/record/where-we-are.svelte +125 -0
  237. package/src/shadcn/button/button.svelte +1 -1
  238. package/src/shadcn/card/card-title.svelte +1 -5
  239. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +2 -13
  240. package/src/shadcn/sheet/sheet-content.svelte +2 -4
  241. package/src/shadcn/table/table-cell.svelte +2 -3
  242. package/src/shadcn/utilities.css +3 -8
  243. package/src/slides/Controls.svelte +150 -124
  244. package/src/slides/Deck.svelte +243 -238
  245. package/src/slides/Fragment.svelte +64 -57
  246. package/src/slides/Overview.svelte +106 -101
  247. package/src/slides/Presenter.svelte +300 -266
  248. package/src/slides/Slide.svelte +185 -170
  249. package/src/slides/SlideList.svelte +16 -12
  250. package/src/slides/ambient.d.ts +1 -1
  251. package/src/slides/bridges.ts +42 -23
  252. package/src/slides/deck-controller.svelte.ts +47 -35
  253. package/src/slides/demo-slides.svelte +330 -306
  254. package/src/slides/demo.svelte +164 -160
  255. package/src/slides/export/capture.ts +20 -16
  256. package/src/slides/export/ffmpeg.ts +43 -38
  257. package/src/slides/export/index.ts +5 -5
  258. package/src/slides/export/launch.ts +3 -1
  259. package/src/slides/export/pdf.ts +30 -27
  260. package/src/slides/export/types.ts +10 -8
  261. package/src/slides/index.ts +11 -11
  262. package/src/slides/labels.ts +22 -19
  263. package/src/slides/layout.ts +9 -2
  264. package/src/slides/model.ts +4 -4
  265. package/src/slides/navigation.ts +43 -31
  266. package/src/slides/theme.css +5 -7
  267. package/src/slides/types.ts +9 -10
  268. package/src/status/README.md +18 -0
  269. package/src/status/define-statuses.ts +25 -0
  270. package/src/status/index.ts +11 -0
  271. package/src/{tasks → status}/status-badge.svelte +3 -1
  272. package/src/{tasks → status}/status-dot.svelte +1 -3
  273. package/src/{tasks → status}/status-icon.svelte +1 -1
  274. package/src/status/status-types.ts +40 -0
  275. package/src/tasks/README.md +1 -1
  276. package/src/tasks/board-order.ts +2 -4
  277. package/src/tasks/demo.svelte +192 -34
  278. package/src/tasks/detail/detail-demo.svelte +75 -15
  279. package/src/tasks/detail/detail-types.ts +45 -45
  280. package/src/tasks/detail/task-detail.svelte +40 -10
  281. package/src/tasks/detail/task-property.svelte +8 -2
  282. package/src/tasks/detail/task-thread.svelte +53 -12
  283. package/src/tasks/gantt/gantt-bar.svelte +17 -6
  284. package/src/tasks/gantt/gantt-grid.svelte +40 -9
  285. package/src/tasks/gantt/gantt-group-row.svelte +8 -2
  286. package/src/tasks/gantt/gantt-header.svelte +9 -3
  287. package/src/tasks/gantt/gantt-task-row.svelte +26 -6
  288. package/src/tasks/gantt/gantt.css +2 -1
  289. package/src/tasks/gantt/gantt.test.ts +4 -2
  290. package/src/tasks/gantt/logic.test.ts +110 -20
  291. package/src/tasks/gantt/logic.ts +42 -6
  292. package/src/tasks/inbox/inbox-card.svelte +15 -7
  293. package/src/tasks/inbox/inbox-draft.svelte +1 -1
  294. package/src/tasks/inbox/inbox-logic.ts +6 -40
  295. package/src/tasks/inbox/inbox-statuses.ts +24 -24
  296. package/src/tasks/inbox/inbox-trace.svelte +9 -3
  297. package/src/tasks/inbox/inbox-types.ts +54 -58
  298. package/src/tasks/inbox/index.ts +22 -21
  299. package/src/tasks/index.ts +52 -53
  300. package/src/tasks/kanban-board.svelte +16 -12
  301. package/src/tasks/kanban-types.ts +2 -3
  302. package/src/tasks/presets/sales.ts +7 -7
  303. package/src/tasks/task-gantt.svelte +28 -8
  304. package/src/tasks/task-list.svelte +41 -17
  305. package/src/tasks/time/duration.ts +40 -40
  306. package/src/tasks/time/time-chip.svelte +9 -2
  307. package/src/tasks/time/time-dot.svelte +1 -1
  308. package/src/tasks/time/time-ring.svelte +11 -3
  309. package/src/tasks/time/time-tracker.svelte +57 -15
  310. package/src/tasks/time/time-types.ts +57 -57
  311. package/src/tasks/use-kanban-dnd.svelte.ts +2 -3
  312. package/src/thread/README.md +31 -0
  313. package/src/thread/index.ts +12 -0
  314. package/src/thread/logic.ts +61 -0
  315. package/src/thread/thread.svelte +432 -0
  316. package/src/thread/thread.test.ts +73 -0
  317. package/src/thread/types.ts +93 -0
  318. package/src/tokens/craft.css +17 -53
  319. package/src/tokens/index.css +3 -13
  320. package/src/tokens/palette.css +13 -11
  321. package/src/tokens/primitives.css +10 -23
  322. package/src/components/list-page/section-label.svelte +0 -39
  323. package/src/tasks/define-statuses.ts +0 -32
  324. package/src/tasks/inbox/inbox.svelte +0 -203
  325. package/src/tasks/record/demo-data.ts +0 -286
  326. package/src/tasks/record/field-section.svelte +0 -53
  327. package/src/tasks/record/fields.ts +0 -142
  328. package/src/tasks/record/format.ts +0 -87
  329. package/src/tasks/record/kinds.ts +0 -42
  330. package/src/tasks/record/logic.ts +0 -324
  331. package/src/tasks/record/next-up.svelte +0 -70
  332. package/src/tasks/record/record-stats.svelte +0 -48
  333. package/src/tasks/record/record-timeline.svelte +0 -231
  334. package/src/tasks/record/record.test.ts +0 -350
  335. package/src/tasks/record/types.ts +0 -280
  336. package/src/tasks/record/where-we-are.svelte +0 -121
  337. package/src/tasks/side-panel.svelte +0 -193
  338. package/src/tasks/status-types.ts +0 -40
  339. /package/src/components/{list-page → toolbar}/toolbar-trigger.ts +0 -0
  340. /package/src/{tasks → panel}/side-panel-types.ts +0 -0
  341. /package/src/{tasks/record → record}/gutter.ts +0 -0
@@ -1,87 +1,87 @@
1
1
  <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import "./content.css";
6
- import { renderTiptapContent } from "./serializer.js";
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import "./content.css";
6
+ import { renderTiptapContent } from "./serializer.js";
7
7
 
8
- // =========================
9
- // TYPES
10
- // =========================
11
- interface Props {
12
- content: Record<string, unknown>;
13
- /** Optional URL prefix to rewrite in rendered HTML (e.g. storage CDN -> local proxy). */
14
- storagePrefix?: string;
15
- /** Replacement path for storagePrefix rewrites. */
16
- storageReplacement?: string;
17
- };
8
+ // =========================
9
+ // TYPES
10
+ // =========================
11
+ interface Props {
12
+ content: Record<string, unknown>;
13
+ /** Optional URL prefix to rewrite in rendered HTML (e.g. storage CDN -> local proxy). */
14
+ storagePrefix?: string;
15
+ /** Replacement path for storagePrefix rewrites. */
16
+ storageReplacement?: string;
17
+ }
18
18
 
19
- // =========================
20
- // PROPS
21
- // =========================
22
- let { content, storagePrefix, storageReplacement = "" }: Props = $props();
19
+ // =========================
20
+ // PROPS
21
+ // =========================
22
+ let { content, storagePrefix, storageReplacement = "" }: Props = $props();
23
23
 
24
- // =========================
25
- // STATE
26
- // =========================
27
- let root = $state<HTMLDivElement | null>(null);
24
+ // =========================
25
+ // STATE
26
+ // =========================
27
+ let root = $state<HTMLDivElement | null>(null);
28
28
 
29
- // =========================
30
- // DERIVED
31
- // =========================
29
+ // =========================
30
+ // DERIVED
31
+ // =========================
32
32
 
33
- // WHY {@html}: the serializer only reads trusted admin CMS JSON, and unlike the
34
- // old client-only action it renders in SSR, so crawlers see the body. Issue #48.
35
- let html = $derived.by(() => {
36
- if (!content) return "";
37
- let rendered = renderTiptapContent(content);
38
- if (storagePrefix && storageReplacement !== undefined) {
39
- rendered = rendered.replaceAll(storagePrefix, storageReplacement);
40
- }
41
- return rendered;
42
- });
33
+ // WHY {@html}: the serializer only reads trusted admin CMS JSON, and unlike the
34
+ // old client-only action it renders in SSR, so crawlers see the body. Issue #48.
35
+ let html = $derived.by(() => {
36
+ if (!content) return "";
37
+ let rendered = renderTiptapContent(content);
38
+ if (storagePrefix && storageReplacement !== undefined) {
39
+ rendered = rendered.replaceAll(storagePrefix, storageReplacement);
40
+ }
41
+ return rendered;
42
+ });
43
43
 
44
- // =========================
45
- // EFFECTS
46
- // =========================
44
+ // =========================
45
+ // EFFECTS
46
+ // =========================
47
47
 
48
- // WHY bound here, not onclick on the div: the facade's real <button> already
49
- // handles Enter and Space. onclick on the div makes the compiler demand a role.
50
- $effect(() => {
51
- const el = root;
52
- if (!el) return;
53
- el.addEventListener("click", handleClick);
54
- return () => el.removeEventListener("click", handleClick);
55
- });
48
+ // WHY bound here, not onclick on the div: the facade's real <button> already
49
+ // handles Enter and Space. onclick on the div makes the compiler demand a role.
50
+ $effect(() => {
51
+ const el = root;
52
+ if (!el) return;
53
+ el.addEventListener("click", handleClick);
54
+ return () => el.removeEventListener("click", handleClick);
55
+ });
56
56
 
57
- // =========================
58
- // FUNCTIONS
59
- // =========================
57
+ // =========================
58
+ // FUNCTIONS
59
+ // =========================
60
60
 
61
- // WHY: the {@html} facade has no Svelte handlers, so this delegated listener swaps
62
- // a clicked facade for a youtube-nocookie.com iframe: no cookies, no cost until play.
63
- function handleClick(event: MouseEvent) {
64
- const target = event.target as HTMLElement | null;
65
- const facade = target?.closest<HTMLElement>(".tiptap-youtube");
66
- if (!facade) return;
61
+ // WHY: the {@html} facade has no Svelte handlers, so this delegated listener swaps
62
+ // a clicked facade for a youtube-nocookie.com iframe: no cookies, no cost until play.
63
+ function handleClick(event: MouseEvent) {
64
+ const target = event.target as HTMLElement | null;
65
+ const facade = target?.closest<HTMLElement>(".tiptap-youtube");
66
+ if (!facade) return;
67
67
 
68
- const videoId = facade.dataset.videoId;
69
- if (!videoId) return;
70
- const start = facade.dataset.start || "0";
71
- const title = facade.querySelector(".tiptap-youtube-title")?.textContent ?? "";
68
+ const videoId = facade.dataset.videoId;
69
+ if (!videoId) return;
70
+ const start = facade.dataset.start || "0";
71
+ const title = facade.querySelector(".tiptap-youtube-title")?.textContent ?? "";
72
72
 
73
- const iframe = document.createElement("iframe");
74
- iframe.src = `https://www.youtube-nocookie.com/embed/${videoId}?autoplay=1&start=${start}&rel=0`;
75
- iframe.title = title;
76
- iframe.setAttribute("frameborder", "0");
77
- iframe.setAttribute(
78
- "allow",
79
- "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",
80
- );
81
- iframe.allowFullscreen = true;
73
+ const iframe = document.createElement("iframe");
74
+ iframe.src = `https://www.youtube-nocookie.com/embed/${videoId}?autoplay=1&start=${start}&rel=0`;
75
+ iframe.title = title;
76
+ iframe.setAttribute("frameborder", "0");
77
+ iframe.setAttribute(
78
+ "allow",
79
+ "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",
80
+ );
81
+ iframe.allowFullscreen = true;
82
82
 
83
- facade.replaceChildren(iframe);
84
- }
83
+ facade.replaceChildren(iframe);
84
+ }
85
85
  </script>
86
86
 
87
87
  <!-- =========================== -->
@@ -1,43 +1,38 @@
1
1
  <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import type { SlashItem } from "./slash-items.js";
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import type { SlashItem } from "./slash-items.js";
6
6
 
7
- // =========================
8
- // TYPES
9
- // =========================
10
- interface Props {
11
- items: SlashItem[];
12
- pos: { top: number; left: number };
13
- selectedIndex: number;
14
- /** The menu element, so the parent can close it on an outside click. */
15
- el?: HTMLDivElement;
16
- onselect: (item: SlashItem) => void;
17
- }
7
+ // =========================
8
+ // TYPES
9
+ // =========================
10
+ interface Props {
11
+ items: SlashItem[];
12
+ pos: { top: number; left: number };
13
+ selectedIndex: number;
14
+ /** The menu element, so the parent can close it on an outside click. */
15
+ el?: HTMLDivElement;
16
+ onselect: (item: SlashItem) => void;
17
+ }
18
18
 
19
- // =========================
20
- // PROPS
21
- // =========================
22
- let { items, pos, selectedIndex = $bindable(), el = $bindable(), onselect }: Props = $props();
19
+ // =========================
20
+ // PROPS
21
+ // =========================
22
+ let { items, pos, selectedIndex = $bindable(), el = $bindable(), onselect }: Props = $props();
23
23
  </script>
24
24
 
25
25
  <!-- =========================== -->
26
26
  <!-- MARKUP -->
27
27
  <!-- =========================== -->
28
- <div
29
- bind:this={el}
30
- class="slash-menu"
31
- style="top: {pos.top}px; left: {pos.left}px;"
32
- role="listbox"
33
- >
28
+ <div bind:this={el} class="slash-menu" style="top: {pos.top}px; left: {pos.left}px;" role="listbox">
34
29
  {#each items as item, i}
35
30
  <button
36
31
  type="button"
37
32
  class="slash-item"
38
33
  class:selected={i === selectedIndex}
39
34
  onclick={() => onselect(item)}
40
- onpointerenter={() => selectedIndex = i}
35
+ onpointerenter={() => (selectedIndex = i)}
41
36
  role="option"
42
37
  aria-selected={i === selectedIndex}
43
38
  >
@@ -117,7 +112,9 @@ let { items, pos, selectedIndex = $bindable(), el = $bindable(), onselect }: Pro
117
112
  background: var(--color-muted);
118
113
  color: var(--color-muted-foreground);
119
114
  flex-shrink: 0;
120
- transition: background-color var(--duration-micro) var(--ease-smooth-out), color var(--duration-micro) var(--ease-smooth-out);
115
+ transition:
116
+ background-color var(--duration-micro) var(--ease-smooth-out),
117
+ color var(--duration-micro) var(--ease-smooth-out);
121
118
  }
122
119
 
123
120
  .slash-item.selected .slash-icon {
@@ -17,7 +17,7 @@
17
17
  delay: number;
18
18
  gradientStartColor: string;
19
19
  gradientStopColor: string;
20
- };
20
+ }
21
21
  let {
22
22
  id,
23
23
  class: className,
@@ -200,7 +200,17 @@
200
200
  />
201
201
  <div class="hr-compare__handle">
202
202
  <span class="hr-compare__grip">
203
- <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
203
+ <svg
204
+ viewBox="0 0 24 24"
205
+ width="16"
206
+ height="16"
207
+ fill="none"
208
+ stroke="currentColor"
209
+ stroke-width="2.5"
210
+ stroke-linecap="round"
211
+ stroke-linejoin="round"
212
+ aria-hidden="true"
213
+ >
204
214
  <polyline points="10 6 4 12 10 18"></polyline>
205
215
  <polyline points="14 6 20 12 14 18"></polyline>
206
216
  </svg>
@@ -1,47 +1,58 @@
1
1
  <script lang="ts">
2
- // =========================
3
- // IMPORTS
4
- // =========================
5
- import { ArrowRight } from "phosphor-svelte";
6
-
7
- // =========================
8
- // TYPES
9
- // =========================
10
- interface Props {
11
- href?: string;
12
- /** `submit` posts the form the button sits in. */
13
- type?: "button" | "submit";
14
- disabled?: boolean;
15
- size?: "default" | "md" | "sm";
16
- tone?: "ink" | "coral";
17
- beam?: boolean;
18
- dot?: boolean;
19
- class?: string;
20
- onclick?: (event: MouseEvent) => void;
21
- children: import("svelte").Snippet;
22
- };
23
-
24
- // =========================
25
- // PROPS
26
- // =========================
27
- let { href, type = "button", disabled = false, size = "default", tone = "ink", beam = true, dot = true, class: className, onclick, children }: Props = $props();
28
-
29
- // =========================
30
- // DERIVED
31
- // =========================
32
- let classes = $derived(
33
- [
34
- "cta-hover",
35
- size === "md" && "cta-hover--md",
36
- size === "sm" && "cta-hover--sm",
37
- tone === "coral" && "cta-hover--coral",
38
- !beam && "cta-hover--no-beam",
39
- !dot && "cta-hover--no-dot",
40
- className,
41
- ]
42
- .filter(Boolean)
43
- .join(" "),
44
- );
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import { ArrowRight } from "phosphor-svelte";
6
+
7
+ // =========================
8
+ // TYPES
9
+ // =========================
10
+ interface Props {
11
+ href?: string;
12
+ /** `submit` posts the form the button sits in. */
13
+ type?: "button" | "submit";
14
+ disabled?: boolean;
15
+ size?: "default" | "md" | "sm";
16
+ tone?: "ink" | "coral";
17
+ beam?: boolean;
18
+ dot?: boolean;
19
+ class?: string;
20
+ onclick?: (event: MouseEvent) => void;
21
+ children: import("svelte").Snippet;
22
+ }
23
+
24
+ // =========================
25
+ // PROPS
26
+ // =========================
27
+ let {
28
+ href,
29
+ type = "button",
30
+ disabled = false,
31
+ size = "default",
32
+ tone = "ink",
33
+ beam = true,
34
+ dot = true,
35
+ class: className,
36
+ onclick,
37
+ children,
38
+ }: Props = $props();
39
+
40
+ // =========================
41
+ // DERIVED
42
+ // =========================
43
+ let classes = $derived(
44
+ [
45
+ "cta-hover",
46
+ size === "md" && "cta-hover--md",
47
+ size === "sm" && "cta-hover--sm",
48
+ tone === "coral" && "cta-hover--coral",
49
+ !beam && "cta-hover--no-beam",
50
+ !dot && "cta-hover--no-dot",
51
+ className,
52
+ ]
53
+ .filter(Boolean)
54
+ .join(" "),
55
+ );
45
56
  </script>
46
57
 
47
58
  <!-- =========================== -->
@@ -123,8 +134,8 @@ let classes = $derived(
123
134
  background: conic-gradient(
124
135
  transparent 0deg,
125
136
  transparent 220deg,
126
- oklch(0.65 0.20 30) 280deg,
127
- oklch(0.82 0.10 30) 320deg,
137
+ oklch(0.65 0.2 30) 280deg,
138
+ oklch(0.82 0.1 30) 320deg,
128
139
  transparent 360deg
129
140
  );
130
141
  transform: translate(-50%, -50%);
@@ -230,7 +241,9 @@ let classes = $derived(
230
241
  justify-content: center;
231
242
  gap: 8px;
232
243
  width: 100%;
233
- transition: opacity var(--duration-fast) var(--ease-smooth-out), transform var(--duration-fast) var(--ease-smooth-out);
244
+ transition:
245
+ opacity var(--duration-fast) var(--ease-smooth-out),
246
+ transform var(--duration-fast) var(--ease-smooth-out);
234
247
  }
235
248
 
236
249
  /* signal-600, not signal-400: the dot becomes the hover fill under a paper
@@ -6,9 +6,20 @@
6
6
  box-shadow 300ms ease;
7
7
  }
8
8
 
9
- .magic-card:hover {
10
- border-color: color-mix(in oklch, var(--magic-card-glow, var(--color-primary)) 30%, var(--color-border));
11
- box-shadow: 0 0 0 1px color-mix(in oklch, var(--magic-card-glow, var(--color-primary)) 8%, transparent);
9
+ @media (hover: hover) and (pointer: fine) {
10
+ .magic-card:hover {
11
+ border-color: color-mix(
12
+ in oklch,
13
+ var(--magic-card-glow, var(--color-primary)) 30%,
14
+ var(--color-border)
15
+ );
16
+ box-shadow: 0 0 0 1px
17
+ color-mix(in oklch, var(--magic-card-glow, var(--color-primary)) 8%, transparent);
18
+ }
19
+
20
+ .magic-card:hover .magic-card__glow {
21
+ opacity: 1;
22
+ }
12
23
  }
13
24
 
14
25
  .magic-card:focus-within {
@@ -35,10 +46,6 @@
35
46
  );
36
47
  }
37
48
 
38
- .magic-card:hover .magic-card__glow {
39
- opacity: 1;
40
- }
41
-
42
49
  @media (prefers-reduced-motion: reduce) {
43
50
  .magic-card,
44
51
  .magic-card__glow {
@@ -25,8 +25,8 @@
25
25
  /** Waits this many ms before the first scroll starts. */
26
26
  delay?: number;
27
27
  /**
28
- * Scrolls only while the content is longer than the box, for a line of text with a max width.
29
- * The box shrinks to its content, drops its padding, and shows one still copy when it fits.
28
+ * Scrolls only while the content overflows the box; when it fits, the box shrinks to it,
29
+ * drops its padding and shows one still copy.
30
30
  */
31
31
  overflowOnly?: boolean;
32
32
  style?: string;
@@ -73,9 +73,7 @@
73
73
  let still = $derived(overflowOnly && !overflowing);
74
74
  // WHY: two copies loop seamlessly once one copy is wider than the box.
75
75
  let copies = $derived(overflowOnly ? (overflowing ? 2 : 1) : repeat);
76
- let loopSeconds = $derived(
77
- speed && trackSize > 0 ? (trackSize + gapSize) / speed : undefined,
78
- );
76
+ let loopSeconds = $derived(speed && trackSize > 0 ? (trackSize + gapSize) / speed : undefined);
79
77
 
80
78
  // WHY: only emit the CSS vars that were passed as props, so a consumer can
81
79
  // still override timing or spacing with a `[--duration:24s]` utility class.
@@ -36,9 +36,6 @@ export function observeInView(
36
36
  /**
37
37
  * Svelte action that calls back when the node enters or leaves the viewport.
38
38
  * Options are read at call time, so a `use:onInView={...}` update swaps the callbacks.
39
- *
40
- * @example
41
- * <section use:onInView={{ amount: 0.2, onEnter: () => (visible = true) }}>
42
39
  */
43
40
  export const onInView: Action<HTMLElement, InViewActionOptions | undefined> = (node, initial) => {
44
41
  let options = initial ?? {};
@@ -11,7 +11,7 @@
11
11
  interface Props {
12
12
  class?: string;
13
13
  fill?: string;
14
- };
14
+ }
15
15
 
16
16
  // =========================
17
17
  // PROPS
@@ -8,17 +8,16 @@
8
8
  color: color-mix(in oklch, var(--color-background) 70%, transparent);
9
9
  }
10
10
 
11
- .theme-toggle--inverted:hover {
12
- border-color: color-mix(in oklch, var(--color-background) 40%, transparent);
13
- background: color-mix(in oklch, var(--color-background) 12%, transparent);
14
- color: var(--color-background);
11
+ @media (hover: hover) and (pointer: fine) {
12
+ .theme-toggle--inverted:hover {
13
+ border-color: color-mix(in oklch, var(--color-background) 40%, transparent);
14
+ background: color-mix(in oklch, var(--color-background) 12%, transparent);
15
+ color: var(--color-background);
16
+ }
15
17
  }
16
18
 
17
- /* The slide variant: a pill track with a thumb that moves to the active side.
18
- A caller sets brand colours with custom properties on the class:
19
- --theme-toggle-thumb-light, --theme-toggle-thumb-dark, --theme-toggle-track,
20
- --theme-toggle-track-hover, --theme-toggle-sun, --theme-toggle-moon,
21
- --theme-toggle-thumb-icon and --theme-toggle-glow. */
19
+ /* The slide variant, a pill track with a moving thumb; a caller sets brand
20
+ colours through the `--theme-toggle-*` custom properties on this class. */
22
21
  .theme-toggle-slide {
23
22
  --tt-thumb: 1.5rem;
24
23
  --tt-pad: 0.25rem;
@@ -46,12 +45,18 @@
46
45
  --tt-thumb: 2rem;
47
46
  }
48
47
 
49
- .theme-toggle-slide:hover {
50
- background: var(
51
- --theme-toggle-track-hover,
52
- color-mix(in oklch, var(--color-foreground) 20%, transparent)
53
- );
54
- box-shadow: 0 0 15px color-mix(in oklch, var(--theme-toggle-glow, transparent) 30%, transparent);
48
+ @media (hover: hover) and (pointer: fine) {
49
+ .theme-toggle-slide:hover {
50
+ background: var(
51
+ --theme-toggle-track-hover,
52
+ color-mix(in oklch, var(--color-foreground) 20%, transparent)
53
+ );
54
+ box-shadow: 0 0 15px color-mix(in oklch, var(--theme-toggle-glow, transparent) 30%, transparent);
55
+ }
56
+
57
+ .theme-toggle-slide:hover .theme-toggle-slide__glow {
58
+ opacity: 1;
59
+ }
55
60
  }
56
61
 
57
62
  .theme-toggle-slide:active {
@@ -77,10 +82,6 @@
77
82
  transition: opacity 500ms;
78
83
  }
79
84
 
80
- .theme-toggle-slide:hover .theme-toggle-slide__glow {
81
- opacity: 1;
82
- }
83
-
84
85
  .theme-toggle-slide[data-state="dark"] {
85
86
  --tt-glow-x: 75%;
86
87
  }
@@ -130,7 +131,9 @@
130
131
  border-radius: 9999px;
131
132
  background: var(--tt-thumb-color);
132
133
  color: var(--theme-toggle-thumb-icon, var(--color-primary-foreground));
133
- box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
134
+ box-shadow:
135
+ 0 10px 15px -3px rgb(0 0 0 / 0.1),
136
+ 0 4px 6px -4px rgb(0 0 0 / 0.1);
134
137
  transition:
135
138
  transform 500ms cubic-bezier(0.165, 0.84, 0.44, 1),
136
139
  background-color 500ms;
@@ -164,12 +167,12 @@
164
167
 
165
168
  .theme-toggle-slide__mark--moon {
166
169
  opacity: 0;
167
- transform: scale(0) rotate(-90deg);
170
+ transform: scale(0.9) rotate(-90deg);
168
171
  }
169
172
 
170
173
  .theme-toggle-slide[data-state="dark"] .theme-toggle-slide__mark--sun {
171
174
  opacity: 0;
172
- transform: scale(0) rotate(90deg);
175
+ transform: scale(0.9) rotate(90deg);
173
176
  }
174
177
 
175
178
  .theme-toggle-slide[data-state="dark"] .theme-toggle-slide__mark--moon {
@@ -53,12 +53,7 @@
53
53
  let active = $derived(mode ?? (isDark ? "dark" : "light"));
54
54
  let style = $derived(inverted ? "inverted" : variant);
55
55
  let text = $derived(
56
- label ??
57
- (mode
58
- ? `Theme: ${mode}`
59
- : isDark
60
- ? "Switch to light mode"
61
- : "Switch to dark mode"),
56
+ label ?? (mode ? `Theme: ${mode}` : isDark ? "Switch to light mode" : "Switch to dark mode"),
62
57
  );
63
58
 
64
59
  // =========================
@@ -97,7 +92,11 @@
97
92
  <span class="theme-toggle-slide__thumb" aria-hidden="true">
98
93
  <span class="theme-toggle-slide__ring"></span>
99
94
  <Sun size={14} weight="fill" class="theme-toggle-slide__mark theme-toggle-slide__mark--sun" />
100
- <Moon size={14} weight="fill" class="theme-toggle-slide__mark theme-toggle-slide__mark--moon" />
95
+ <Moon
96
+ size={14}
97
+ weight="fill"
98
+ class="theme-toggle-slide__mark theme-toggle-slide__mark--moon"
99
+ />
101
100
  </span>
102
101
  <span class="sr-only">{text}</span>
103
102
  </button>
@@ -51,21 +51,31 @@ describe("site gallery coverage", () => {
51
51
  it("gives every component, AI part and effect a place on the site", () => {
52
52
  const walk = (dir: string): string[] =>
53
53
  readdirSync(dir, { withFileTypes: true }).flatMap((d) =>
54
- d.isDirectory() ? walk(join(dir, d.name)) : /\.(svelte|ts)$/.test(d.name) ? [join(dir, d.name)] : [],
54
+ d.isDirectory()
55
+ ? walk(join(dir, d.name))
56
+ : /\.(svelte|ts)$/.test(d.name)
57
+ ? [join(dir, d.name)]
58
+ : [],
55
59
  );
56
- const site = walk(join(SITE, "..")).map((file) => readFileSync(file, "utf8")).join("\n");
60
+ const site = walk(join(SITE, ".."))
61
+ .map((file) => readFileSync(file, "utf8"))
62
+ .join("\n");
57
63
  const parts = ["components", "ai", "effects"].flatMap((top) =>
58
64
  readdirSync(join(PKG, "src", top), { withFileTypes: true })
59
65
  .filter((d) => d.isDirectory())
60
66
  .map((d) => `${top}/${d.name}`),
61
67
  );
62
- expect(parts.filter((part) => !site.includes(`${part}/`) && !site.includes(`${part}"`))).toEqual([]);
68
+ expect(
69
+ parts.filter((part) => !site.includes(`${part}/`) && !site.includes(`${part}"`)),
70
+ ).toEqual([]);
63
71
  });
64
72
 
65
73
  it("keeps a demo file behind every listed primitive", () => {
66
74
  const manifest = readFileSync(join(SITE, "shadcn-demos", "index.ts"), "utf8");
67
75
  const listed = [...manifest.matchAll(/name: "([a-z-]+)"/g)].map((m) => m[1]);
68
76
  expect(listed.length).toBeGreaterThan(0);
69
- expect(listed.filter((name) => !existsSync(join(SITE, "shadcn-demos", `${name}.svelte`)))).toEqual([]);
77
+ expect(
78
+ listed.filter((name) => !existsSync(join(SITE, "shadcn-demos", `${name}.svelte`))),
79
+ ).toEqual([]);
70
80
  });
71
81
  });
@@ -0,0 +1,27 @@
1
+ # @heyramzi/ui/inbox
2
+
3
+ One threaded inbox. Entries from any source (a comment, a status change, an assignment, a mention, an agent reply, an agent's ask) group into one thread per subject, and the screen shows the thread list on the left and the selected thread on the right. The inbox never fetches. Everything leaves through callbacks.
4
+
5
+ ```svelte
6
+ <script lang="ts">
7
+ import { ThreadInbox, type InboxEntry } from "@heyramzi/ui/inbox";
8
+
9
+ let entries = $state<InboxEntry[]>([]);
10
+ </script>
11
+
12
+ <ThreadInbox
13
+ {entries}
14
+ unreadFirst
15
+ onRead={(thread, read) => mark(thread.key, read)}
16
+ onSnooze={({ thread, until }) => snooze(thread.key, until)}
17
+ onDecide={(verdict, thread) => decide(verdict)}
18
+ />
19
+ ```
20
+
21
+ - **Grouping.** `threadsOf(entries, { unreadFirst })` keys on `subject.type` plus `subject.id`, sorts threads by their newest entry and entries oldest first. An entry with no subject id is dropped. A malformed timestamp sorts last. Each thread carries `unread`, `latestAt` and `pending`.
22
+ - **Needs you.** A `proposal` entry whose `proposal` (an `InboxItem`) is still open pins to the top of its thread, drawn with `InboxCard`. The "Needs you" filter keeps threads that hold one. Verdicts come out of `onDecide`.
23
+ - **Filters.** By subject type, by actor name and by Needs you. `filterThreads` runs them without the component.
24
+ - **Keys.** `j` and `k` move, `u` marks the thread read (or unread when nothing is unread), `h` opens the snooze row (`1`, `2`, `3` pick a time), and `a`, `d`, `s`, `e` act on the pinned proposal. A key does nothing while focus is in an input, a textarea, a select or an editable element, or while a modifier is held. `inboxKeyOf(event)` is the same check.
25
+ - **Snooze.** `onSnooze({ thread, until })` gets a `Date`: 3 hours on, tomorrow 9:00 or next Monday 9:00 (`snoozeChoices(now)`). The host decides what snoozed means and stops passing the entries.
26
+ - **Read.** `onRead(thread, read)` is true when the thread had unread entries, so the host marks them all read.
27
+ - **Words.** `labels` is a partial of `InboxLabels`. Status and assignment lines have `{from}`, `{to}` and `{assignee}` slots. `typeLabels` names the subject types. The `avatar` and `status` snippets let the app draw its own portrait and status badge.