@heyramzi/ui 2.1.0 → 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 (312) hide show
  1. package/package.json +7 -8
  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/card/card-frame.svelte +43 -39
  31. package/src/canvas/card/resize-handles.svelte +89 -80
  32. package/src/canvas/chrome/chrome-zoom.svelte +101 -100
  33. package/src/canvas/chrome/lock-badge.svelte +8 -8
  34. package/src/canvas/chrome/side-handles.svelte +24 -19
  35. package/src/canvas/chrome/size-chip.svelte +30 -30
  36. package/src/canvas/chrome/snap-guides.svelte +20 -20
  37. package/src/canvas/components/icons/ToolIcon.svelte +8 -8
  38. package/src/canvas/components/shared/svg/PreviewSvg.svelte +19 -19
  39. package/src/canvas/components/shared/svg/ShapeOutline.svelte +71 -48
  40. package/src/canvas/components/shared/svg/StrokeNode.svelte +21 -27
  41. package/src/canvas/components/shared/svg/TextRule.svelte +25 -17
  42. package/src/canvas/edges/bend-handle.svelte +46 -46
  43. package/src/canvas/edges/edge-gradient.svelte +10 -10
  44. package/src/canvas/edges/gradient-edge.svelte +45 -29
  45. package/src/canvas/editor/lock.test.ts +6 -1
  46. package/src/canvas/editor/style.ts +7 -1
  47. package/src/canvas/headless/svg-figure.test.ts +5 -1
  48. package/src/canvas/lod/lod-switch.svelte +111 -106
  49. package/src/canvas/pen/PenField.svelte +87 -81
  50. package/src/canvas/pen/PenOverlay.svelte +127 -127
  51. package/src/canvas/pen/ShapeNode.svelte +31 -32
  52. package/src/canvas/pen/TextNode.svelte +54 -53
  53. package/src/canvas/pen/host-field.test.svelte +7 -7
  54. package/src/canvas/pen/shapes.ts +7 -1
  55. package/src/canvas/screen/GlyphIcon.svelte +22 -17
  56. package/src/canvas/screen/PartView.svelte +138 -72
  57. package/src/canvas/screen/ScreenBody.svelte +81 -60
  58. package/src/canvas/screen/ScreenCard.svelte +20 -20
  59. package/src/canvas/ui/CanvasToolbar.svelte +173 -85
  60. package/src/canvas/ui/FrameLayer.svelte +59 -59
  61. package/src/canvas/ui/PenLayer.svelte +39 -30
  62. package/src/canvas/ui/StylePanel.svelte +119 -102
  63. package/src/canvas/ui/canvas-grid.svelte +137 -129
  64. package/src/charts/animated-value.svelte +1 -1
  65. package/src/charts/area-chart.css +2 -5
  66. package/src/charts/area-chart.svelte +1 -1
  67. package/src/charts/bar-chart.css +2 -3
  68. package/src/charts/bar-chart.svelte +1 -1
  69. package/src/charts/charts.css +5 -17
  70. package/src/charts/crosshair.svelte +1 -1
  71. package/src/charts/dashboard/dashboard-empty.svelte +25 -25
  72. package/src/charts/dashboard/dashboard-section.svelte +24 -24
  73. package/src/charts/dashboard/data-panel.svelte +50 -50
  74. package/src/charts/dashboard/data-sort-head.svelte +56 -56
  75. package/src/charts/dashboard/data-table.svelte +138 -138
  76. package/src/charts/dashboard/panel-grid.svelte +30 -30
  77. package/src/charts/dashboard/period-filter.svelte +25 -25
  78. package/src/charts/dashboard/rating-chip.svelte +19 -19
  79. package/src/charts/dashboard/stat-band.svelte +32 -32
  80. package/src/charts/dashboard/stat-tile.svelte +106 -99
  81. package/src/charts/donut-chart.svelte +1 -1
  82. package/src/charts/edge-fade.svelte +1 -1
  83. package/src/charts/figure.svelte +1 -1
  84. package/src/charts/gauge-chart.css +4 -7
  85. package/src/charts/gauge-chart.svelte +1 -1
  86. package/src/charts/hover-focus.svelte +1 -1
  87. package/src/charts/legend.css +1 -3
  88. package/src/charts/legend.svelte +1 -1
  89. package/src/charts/line-chart.svelte +1 -1
  90. package/src/charts/number-chart.css +6 -9
  91. package/src/charts/number-chart.svelte +1 -1
  92. package/src/charts/reference-line.svelte +19 -19
  93. package/src/charts/reveal.svelte +1 -1
  94. package/src/charts/ring-chart.css +2 -4
  95. package/src/charts/ring-chart.svelte +1 -1
  96. package/src/charts/sparkline.svelte +5 -2
  97. package/src/charts/stat-card.svelte +1 -1
  98. package/src/charts/time-series-chart.css +2 -5
  99. package/src/charts/trend-pill.css +2 -4
  100. package/src/charts/trend-pill.svelte +1 -1
  101. package/src/components/copy/copy-button.svelte +93 -98
  102. package/src/components/copy/copy-button.test.ts +10 -2
  103. package/src/components/data-table/infinite-scroll.svelte +42 -42
  104. package/src/components/data-table/pagination.svelte +34 -22
  105. package/src/components/data-table/row-arrow.svelte +19 -16
  106. package/src/components/data-table/row-context-menu.svelte +52 -52
  107. package/src/components/data-table/sortable-head.svelte +25 -25
  108. package/src/components/data-table/table-skeleton.svelte +41 -36
  109. package/src/components/docs/doc-page.svelte +18 -9
  110. package/src/components/docs/on-this-page.svelte +16 -3
  111. package/src/components/docs/scroll-spy.svelte.ts +2 -3
  112. package/src/components/field-row/field-row.svelte +10 -8
  113. package/src/components/hold-to-confirm/hold-to-confirm.svelte +5 -1
  114. package/src/components/icon/icon.svelte +14 -14
  115. package/src/components/language-flag/language-flag.svelte +3 -1
  116. package/src/components/list-page/detail-empty.svelte +34 -34
  117. package/src/components/list-page/detail-header.svelte +55 -47
  118. package/src/components/list-page/detail-panel.svelte +51 -59
  119. package/src/components/list-page/detail-row.svelte +19 -19
  120. package/src/components/list-page/detail-stack.svelte +21 -21
  121. package/src/components/list-page/empty-state.svelte +30 -30
  122. package/src/components/list-page/filter-item.svelte +20 -20
  123. package/src/components/list-page/filter-popover.svelte +60 -55
  124. package/src/components/list-page/grouped-list.svelte +4 -2
  125. package/src/components/list-page/index.ts +5 -1
  126. package/src/components/list-page/list-page-shell.svelte +2 -5
  127. package/src/components/list-page/master-detail.svelte +87 -88
  128. package/src/components/list-page/notice-bar.svelte +38 -38
  129. package/src/components/list-page/prev-next.svelte +63 -63
  130. package/src/components/list-page/rail-body.svelte +18 -18
  131. package/src/components/list-page/rail-empty.svelte +23 -30
  132. package/src/components/list-page/rail-head.svelte +24 -24
  133. package/src/components/list-page/rail-row.svelte +77 -73
  134. package/src/components/list-page/section-host.ts +2 -3
  135. package/src/components/locale-switch/locale-switch.svelte +41 -41
  136. package/src/components/mark/mark.svelte +34 -38
  137. package/src/components/open-in/index.ts +1 -5
  138. package/src/components/open-in/open-in.svelte +40 -40
  139. package/src/components/picker/mark-picker.svelte +49 -49
  140. package/src/components/picker/picker.svelte +5 -3
  141. package/src/components/section-header/section-header.test.ts +7 -1
  142. package/src/components/section-header/section-label.svelte +20 -20
  143. package/src/components/segmented/animated-tabs.svelte +31 -2
  144. package/src/components/segmented/collapse.test.ts +2 -1
  145. package/src/components/segmented/index.ts +2 -0
  146. package/src/components/segmented/segment.svelte +52 -49
  147. package/src/components/segmented/segmented-filter.svelte +79 -80
  148. package/src/components/segmented/segmented-nav.svelte +34 -40
  149. package/src/components/segmented/segmented-overflow.svelte +53 -50
  150. package/src/components/segmented/segmented-track.svelte +62 -46
  151. package/src/components/segmented/segmented.ts +2 -3
  152. package/src/components/star-rating-input/star-rating-input.svelte +103 -103
  153. package/src/components/svg/svg.svelte +4 -4
  154. package/src/components/table-view/table-view.svelte +17 -2
  155. package/src/components/table-view/table-view.test.ts +7 -1
  156. package/src/components/table-view/types.ts +11 -11
  157. package/src/components/tags-input/index.ts +13 -0
  158. package/src/components/tags-input/tags-input.svelte +305 -0
  159. package/src/components/tags-input/tags.test.ts +64 -0
  160. package/src/components/tags-input/tags.ts +142 -0
  161. package/src/components/theme/index.ts +2 -4
  162. package/src/components/theme/theme-provider.svelte +1 -3
  163. package/src/components/toolbar/toolbar-search.svelte +25 -25
  164. package/src/components/toolbar/toolbar.svelte +11 -7
  165. package/src/components/toolbar/toolbar.test.ts +10 -1
  166. package/src/design/index.css +21 -78
  167. package/src/design/polish.css +26 -35
  168. package/src/editor/editor-toolbar.svelte +104 -99
  169. package/src/editor/editor.svelte +296 -266
  170. package/src/editor/image-toolbar.svelte +20 -22
  171. package/src/editor/index.ts +1 -4
  172. package/src/editor/inline-editor.svelte +280 -238
  173. package/src/editor/markdown.ts +15 -15
  174. package/src/editor/renderer.svelte +71 -71
  175. package/src/editor/slash-menu.svelte +24 -27
  176. package/src/effects/animated-beam/diagrams/BeamSvg.svelte +1 -1
  177. package/src/effects/image-comparison/ImageComparison.svelte +11 -1
  178. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +59 -46
  179. package/src/effects/magic-card/MagicCard.css +14 -7
  180. package/src/effects/marquee/Marquee.svelte +3 -5
  181. package/src/effects/reveal/observe.ts +0 -3
  182. package/src/effects/spotlight/Spotlight.svelte +1 -1
  183. package/src/effects/theme-toggle/ThemeToggle.css +25 -22
  184. package/src/effects/theme-toggle/ThemeToggle.svelte +6 -7
  185. package/src/gallery.test.ts +14 -4
  186. package/src/inbox/README.md +1 -3
  187. package/src/inbox/inbox.test.ts +181 -138
  188. package/src/inbox/index.ts +20 -20
  189. package/src/inbox/logic.ts +114 -111
  190. package/src/inbox/thread-inbox.svelte +227 -151
  191. package/src/inbox/types.ts +130 -108
  192. package/src/lib/theme-transition.ts +2 -4
  193. package/src/lib/tone.ts +2 -4
  194. package/src/lib/utils.ts +2 -2
  195. package/src/modules.test.ts +79 -69
  196. package/src/onboarding/define-tours.test.ts +55 -40
  197. package/src/onboarding/define-tours.ts +23 -23
  198. package/src/onboarding/demo.svelte +24 -5
  199. package/src/onboarding/index.ts +1 -8
  200. package/src/onboarding/onboarding-provider.svelte +19 -7
  201. package/src/onboarding/onboarding.svelte.ts +16 -16
  202. package/src/onboarding/pick.test.ts +61 -52
  203. package/src/onboarding/pick.ts +16 -13
  204. package/src/onboarding/stores.test.ts +80 -80
  205. package/src/onboarding/stores.ts +41 -39
  206. package/src/onboarding/tour-card.svelte +30 -5
  207. package/src/onboarding/tour-runner.svelte +12 -3
  208. package/src/onboarding/types.ts +20 -12
  209. package/src/onboarding/wait.ts +22 -22
  210. package/src/panel/side-panel.svelte +94 -93
  211. package/src/record/demo-data.ts +291 -225
  212. package/src/record/field-section.svelte +38 -34
  213. package/src/record/fields.ts +148 -96
  214. package/src/record/format.ts +63 -56
  215. package/src/record/index.ts +49 -35
  216. package/src/record/info-list.svelte +145 -114
  217. package/src/record/kinds.ts +31 -31
  218. package/src/record/logic.ts +216 -214
  219. package/src/record/next-up.svelte +45 -34
  220. package/src/record/project-list.svelte +56 -50
  221. package/src/record/record-demo.svelte +130 -110
  222. package/src/record/record-header.svelte +37 -28
  223. package/src/record/record-shell.svelte +54 -54
  224. package/src/record/record-stats.svelte +25 -25
  225. package/src/record/record-timeline.svelte +134 -114
  226. package/src/record/record.test.ts +430 -309
  227. package/src/record/steps-rail.svelte +225 -216
  228. package/src/record/types.ts +234 -215
  229. package/src/record/where-we-are.svelte +62 -58
  230. package/src/shadcn/button/button.svelte +1 -1
  231. package/src/shadcn/card/card-title.svelte +1 -5
  232. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +2 -13
  233. package/src/shadcn/sheet/sheet-content.svelte +2 -4
  234. package/src/shadcn/table/table-cell.svelte +2 -3
  235. package/src/shadcn/utilities.css +3 -8
  236. package/src/slides/Controls.svelte +150 -124
  237. package/src/slides/Deck.svelte +243 -238
  238. package/src/slides/Fragment.svelte +64 -57
  239. package/src/slides/Overview.svelte +106 -101
  240. package/src/slides/Presenter.svelte +300 -266
  241. package/src/slides/Slide.svelte +185 -170
  242. package/src/slides/SlideList.svelte +16 -12
  243. package/src/slides/ambient.d.ts +1 -1
  244. package/src/slides/bridges.ts +42 -23
  245. package/src/slides/deck-controller.svelte.ts +47 -35
  246. package/src/slides/demo-slides.svelte +330 -306
  247. package/src/slides/demo.svelte +164 -160
  248. package/src/slides/export/capture.ts +20 -16
  249. package/src/slides/export/ffmpeg.ts +43 -38
  250. package/src/slides/export/index.ts +5 -5
  251. package/src/slides/export/launch.ts +3 -1
  252. package/src/slides/export/pdf.ts +30 -27
  253. package/src/slides/export/types.ts +10 -8
  254. package/src/slides/index.ts +11 -11
  255. package/src/slides/labels.ts +22 -19
  256. package/src/slides/layout.ts +9 -2
  257. package/src/slides/model.ts +4 -4
  258. package/src/slides/navigation.ts +43 -31
  259. package/src/slides/theme.css +5 -7
  260. package/src/slides/types.ts +9 -10
  261. package/src/status/define-statuses.ts +17 -24
  262. package/src/status/index.ts +5 -5
  263. package/src/status/status-badge.svelte +3 -1
  264. package/src/status/status-dot.svelte +1 -3
  265. package/src/status/status-icon.svelte +1 -1
  266. package/src/status/status-types.ts +21 -21
  267. package/src/tasks/board-order.ts +2 -4
  268. package/src/tasks/demo.svelte +189 -31
  269. package/src/tasks/detail/detail-demo.svelte +73 -13
  270. package/src/tasks/detail/detail-types.ts +45 -45
  271. package/src/tasks/detail/task-detail.svelte +40 -10
  272. package/src/tasks/detail/task-property.svelte +8 -2
  273. package/src/tasks/detail/task-thread.svelte +53 -12
  274. package/src/tasks/gantt/gantt-bar.svelte +17 -6
  275. package/src/tasks/gantt/gantt-grid.svelte +40 -9
  276. package/src/tasks/gantt/gantt-group-row.svelte +8 -2
  277. package/src/tasks/gantt/gantt-header.svelte +9 -3
  278. package/src/tasks/gantt/gantt-task-row.svelte +26 -6
  279. package/src/tasks/gantt/gantt.css +2 -1
  280. package/src/tasks/gantt/gantt.test.ts +3 -1
  281. package/src/tasks/gantt/logic.test.ts +110 -20
  282. package/src/tasks/gantt/logic.ts +42 -6
  283. package/src/tasks/inbox/inbox-card.svelte +13 -5
  284. package/src/tasks/inbox/inbox-draft.svelte +1 -1
  285. package/src/tasks/inbox/inbox-logic.ts +6 -40
  286. package/src/tasks/inbox/inbox-statuses.ts +23 -23
  287. package/src/tasks/inbox/inbox-trace.svelte +9 -3
  288. package/src/tasks/inbox/inbox-types.ts +54 -58
  289. package/src/tasks/inbox/index.ts +22 -21
  290. package/src/tasks/index.ts +49 -52
  291. package/src/tasks/kanban-board.svelte +16 -12
  292. package/src/tasks/kanban-types.ts +2 -3
  293. package/src/tasks/presets/sales.ts +6 -6
  294. package/src/tasks/task-gantt.svelte +28 -8
  295. package/src/tasks/task-list.svelte +41 -17
  296. package/src/tasks/time/duration.ts +40 -40
  297. package/src/tasks/time/time-chip.svelte +9 -2
  298. package/src/tasks/time/time-dot.svelte +1 -1
  299. package/src/tasks/time/time-ring.svelte +11 -3
  300. package/src/tasks/time/time-tracker.svelte +57 -15
  301. package/src/tasks/time/time-types.ts +57 -57
  302. package/src/tasks/use-kanban-dnd.svelte.ts +2 -3
  303. package/src/thread/index.ts +7 -7
  304. package/src/thread/logic.ts +36 -33
  305. package/src/thread/thread.svelte +82 -16
  306. package/src/thread/thread.test.ts +58 -47
  307. package/src/thread/types.ts +68 -68
  308. package/src/tokens/craft.css +17 -53
  309. package/src/tokens/index.css +3 -13
  310. package/src/tokens/palette.css +13 -11
  311. package/src/tokens/primitives.css +10 -23
  312. package/src/tasks/inbox/inbox.svelte +0 -203
@@ -42,8 +42,8 @@
42
42
  /** Rendered under a column's cards, e.g. a "Show all 12" or an add button. */
43
43
  columnFooter?: Snippet<[C, KanbanColumnContext]>;
44
44
  /**
45
- * Wraps a whole column, for a context menu or a tooltip around it. Render `column` once and
46
- * spread the `props` it hands you, so the wrapper's trigger lands on the column.
45
+ * Wraps a column for a context menu or tooltip: render `column` once and spread the
46
+ * `props` it hands you, so the trigger lands on the column.
47
47
  */
48
48
  wrapColumn?: Snippet<[C, Snippet<[Record<string, unknown>]>]>;
49
49
  /** Enter or Space on a focused card. Leave out when the card holds its own links and buttons. */
@@ -52,8 +52,8 @@
52
52
  getLabel?: (item: T) => string;
53
53
  emptyLabel?: string;
54
54
  /**
55
- * Cards a column shows before the rest fold behind a footer. Return undefined for no cap.
56
- * A capped column's empty space drops on top, since the tail is out of sight.
55
+ * Cards a column shows before the rest fold behind a footer, or undefined for no cap; a
56
+ * capped column drops on top, since its tail is out of sight.
57
57
  */
58
58
  limit?: (columnId: string) => number | undefined;
59
59
  /** Column ids whose `limit` is lifted. Controlled; leave out to let the board remember. */
@@ -158,7 +158,7 @@
158
158
  }
159
159
 
160
160
  function canDrop(columnId: string): boolean {
161
- return isDroppable?.(columnId) ?? (columns.find((c) => c.id === columnId)?.droppable ?? true);
161
+ return isDroppable?.(columnId) ?? columns.find((c) => c.id === columnId)?.droppable ?? true;
162
162
  }
163
163
 
164
164
  function expand(columnId: string): void {
@@ -216,9 +216,8 @@
216
216
  });
217
217
 
218
218
  /**
219
- * Keyboard move: Alt + ArrowLeft/Right drops the focused card in the
220
- * adjacent droppable column. Native drag stays the primary path; this
221
- * covers keyboard and touch-reader users with the same onMove contract.
219
+ * Alt + ArrowLeft/Right moves the focused card to the next droppable column, so
220
+ * keyboard and screen-reader users get drag's onMove contract.
222
221
  */
223
222
  function onCardKeyDown(e: KeyboardEvent, id: string): void {
224
223
  if (onSelect && e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) {
@@ -373,7 +372,8 @@
373
372
  <div
374
373
  role="listitem"
375
374
  tabindex="0"
376
- aria-label={getLabel?.(item) ?? "Card. Press Alt plus Left or Right arrow to move between columns."}
375
+ aria-label={getLabel?.(item) ??
376
+ "Card. Press Alt plus Left or Right arrow to move between columns."}
377
377
  aria-keyshortcuts="Alt+ArrowLeft Alt+ArrowRight"
378
378
  draggable="true"
379
379
  ondragstart={(e) => {
@@ -465,8 +465,10 @@
465
465
  "kanban-rail-item rounded-md transition-[background-color,outline-color] duration-[var(--duration-quick,120ms)] ease-[var(--ease-smooth-out,ease-out)]",
466
466
  leaving.has(column.id) && "kanban-rail-item-leaving",
467
467
  )}
468
- style:background="color-mix(in oklch, {hue} {over ? 10 : 0}%, transparent)"
469
- style:outline="1px solid color-mix(in oklch, {hue} {over ? 28 : 0}%, transparent)"
468
+ style:background="color-mix(in oklch, {hue}
469
+ {over ? 10 : 0}%, transparent)"
470
+ style:outline="1px solid color-mix(in oklch, {hue}
471
+ {over ? 28 : 0}%, transparent)"
470
472
  ondragover={(e) => onHiddenDragOver(e, column.id)}
471
473
  ondragleave={onHiddenDragLeave}
472
474
  ondrop={(e) => onHiddenDrop(e, column.id)}
@@ -481,7 +483,9 @@
481
483
  {:else}
482
484
  <span class="truncate whitespace-nowrap">{column.label}</span>
483
485
  {#if count > 0}
484
- <span class="ms-auto text-xs text-muted-foreground/60 tabular-nums">{count}</span>
486
+ <span class="ms-auto text-xs text-muted-foreground/60 tabular-nums"
487
+ >{count}</span
488
+ >
485
489
  {/if}
486
490
  {/if}
487
491
  </button>
@@ -24,9 +24,8 @@ export type KanbanMove = {
24
24
  */
25
25
  boardOrder?: string;
26
26
  /**
27
- * The cards that sit above and below the drop slot once the dragged card is
28
- * set aside. Set on manual drops only, null at an end. For a backend that
29
- * orders by neighbour instead of by key.
27
+ * The drop slot's neighbours, for a backend that orders by neighbour, not by
28
+ * key. Set on manual drops only, null at an end.
30
29
  */
31
30
  beforeId?: string | null;
32
31
  afterId?: string | null;
@@ -5,12 +5,12 @@ import { defineStatuses } from "../../status/define-statuses.js";
5
5
  * swap the keys and labels for your own domain.
6
6
  */
7
7
  export const salesStatuses = defineStatuses({
8
- new: { label: "New", tone: "accent", variant: "open" },
9
- contacted: { label: "Contacted", tone: "info", variant: "open" },
10
- proposal: { label: "Proposal", tone: "accent", variant: "progress", progress: 0.25 },
11
- negotiation: { label: "Negotiation", tone: "warning", variant: "progress", progress: 0.75 },
12
- won: { label: "Won", tone: "success", variant: "completed" },
13
- lost: { label: "Lost", tone: "destructive", variant: "lost" },
8
+ new: { label: "New", tone: "accent", variant: "open" },
9
+ contacted: { label: "Contacted", tone: "info", variant: "open" },
10
+ proposal: { label: "Proposal", tone: "accent", variant: "progress", progress: 0.25 },
11
+ negotiation: { label: "Negotiation", tone: "warning", variant: "progress", progress: 0.75 },
12
+ won: { label: "Won", tone: "success", variant: "completed" },
13
+ lost: { label: "Lost", tone: "destructive", variant: "lost" },
14
14
  });
15
15
 
16
16
  export type SalesStatus = (typeof salesStatuses.ids)[number];
@@ -33,8 +33,8 @@
33
33
  /** ISO date, inclusive. See `getStart`. */
34
34
  getEnd: (item: T) => string | null | undefined;
35
35
  /**
36
- * The id of the task this one sits under. Children nest under their parent, indented
37
- * and foldable. A parent with no dates of its own draws the span of what is below it.
36
+ * The parent task's id; a parent with no dates of its own draws the span of the
37
+ * children nested under it.
38
38
  */
39
39
  getParent?: (item: T) => string | null | undefined;
40
40
  /** Draws a flag at the end date instead of a bar. Milestones are not draggable. */
@@ -133,10 +133,16 @@
133
133
  });
134
134
 
135
135
  let days = $derived(buildDays(range.first, dayCount));
136
- let monthFormat = $derived(new Intl.DateTimeFormat(locale, { month: "long", year: "numeric", timeZone: "UTC" }));
137
- let dayFormat = $derived(new Intl.DateTimeFormat(locale, { month: "short", day: "numeric", timeZone: "UTC" }));
136
+ let monthFormat = $derived(
137
+ new Intl.DateTimeFormat(locale, { month: "long", year: "numeric", timeZone: "UTC" }),
138
+ );
139
+ let dayFormat = $derived(
140
+ new Intl.DateTimeFormat(locale, { month: "short", day: "numeric", timeZone: "UTC" }),
141
+ );
138
142
  let months = $derived(buildMonths(days, monthFormat));
139
- let rows = $derived(buildRows(items, { getId, getStart, getEnd, getParent, groups, getGroup, folded, drag }));
143
+ let rows = $derived(
144
+ buildRows(items, { getId, getStart, getEnd, getParent, groups, getGroup, folded, drag }),
145
+ );
140
146
  let arrows = $derived(getDependsOn ? buildArrows(rows, layout, getDependsOn) : []);
141
147
  let groupHue = $derived(new Map((groups ?? []).map((g) => [g.id, g.hue])));
142
148
 
@@ -148,7 +154,11 @@
148
154
  }
149
155
 
150
156
  function hueOf(item: T): string {
151
- return getHue?.(item) ?? (getGroup ? groupHue.get(getGroup(item)) : undefined) ?? "var(--color-primary)";
157
+ return (
158
+ getHue?.(item) ??
159
+ (getGroup ? groupHue.get(getGroup(item)) : undefined) ??
160
+ "var(--color-primary)"
161
+ );
152
162
  }
153
163
 
154
164
  function hover(id: string, on: boolean): void {
@@ -172,7 +182,10 @@
172
182
  export function scrollToToday(): void {
173
183
  if (!scroller) return;
174
184
  const label = corner?.offsetWidth ?? 0;
175
- scroller.scrollTo({ left: Math.max(0, dayX(layout, todayDay) - (scroller.clientWidth - label) / 3), behavior: "smooth" });
185
+ scroller.scrollTo({
186
+ left: Math.max(0, dayX(layout, todayDay) - (scroller.clientWidth - label) / 3),
187
+ behavior: "smooth",
188
+ });
176
189
  }
177
190
  </script>
178
191
 
@@ -192,7 +205,14 @@
192
205
  <GanttHeader {layout} {days} {months} {labelHeading} {locale} bind:corner />
193
206
 
194
207
  <div class="relative">
195
- <GanttGrid {layout} {days} {arrows} rowCount={rows.length} {hoveredId} draggingId={drag?.id ?? null} />
208
+ <GanttGrid
209
+ {layout}
210
+ {days}
211
+ {arrows}
212
+ rowCount={rows.length}
213
+ {hoveredId}
214
+ draggingId={drag?.id ?? null}
215
+ />
196
216
 
197
217
  <div role="list" aria-label="Timeline">
198
218
  {#each rows as row, index (row.kind === "group" ? `g:${row.group.id}` : row.id)}
@@ -7,7 +7,11 @@
7
7
  import { cn } from "../lib/utils.js";
8
8
  import { createKanbanDnd } from "./use-kanban-dnd.svelte.js";
9
9
  import { CaretRight } from "phosphor-svelte";
10
- import type { KanbanColumn as KanbanColumnDef, KanbanMove, KanbanSortKey } from "./kanban-types.js";
10
+ import type {
11
+ KanbanColumn as KanbanColumnDef,
12
+ KanbanMove,
13
+ KanbanSortKey,
14
+ } from "./kanban-types.js";
11
15
  import type { TaskListGroupContext, TaskListRowContext } from "./task-types.js";
12
16
 
13
17
  interface Props<T, C extends KanbanColumnDef> {
@@ -18,8 +22,8 @@
18
22
  /** The group id a row belongs to. */
19
23
  getGroup: (item: T) => string;
20
24
  /**
21
- * Nests a group under another by its id, e.g. a project under its client. A child draws
22
- * inside its parent's section, indented, and folds with it. Return nothing for a top-level group.
25
+ * Nests a group under another by its id, e.g. a project under its client, so it folds with
26
+ * its parent. Return nothing for a top-level group.
23
27
  */
24
28
  getParent?: (group: C) => string | null | undefined;
25
29
  /** Called on drop with what to persist. Same contract as the board. */
@@ -36,8 +40,8 @@
36
40
  /** Breaks a tie in manual order: two rows with the same key, or both without one. */
37
41
  manualTiebreak?: (a: T, b: T) => number;
38
42
  /**
39
- * Rows are draggable and take Alt plus Up or Down. Turn it off for a list that only groups,
40
- * where rows hold their own links and buttons and a drop makes no sense.
43
+ * Rows drag and take Alt plus Up or Down. Turn it off for a list that only groups, where
44
+ * rows hold their own links and buttons.
41
45
  */
42
46
  draggable?: boolean;
43
47
  /** The screen-reader name of a row. Defaults to a hint about the keyboard move. */
@@ -115,7 +119,7 @@
115
119
  groups.filter((g) => {
116
120
  const parent = getParent?.(g);
117
121
  return !parent || !groupIds.has(parent);
118
- })
122
+ }),
119
123
  );
120
124
  let childrenOf = $derived.by(() => {
121
125
  const map = new Map<string, C[]>();
@@ -137,7 +141,7 @@
137
141
  function canDrop(groupId: string): boolean {
138
142
  // A group that only holds other groups has no rows of its own to drop into.
139
143
  if (childrenOf.has(groupId)) return false;
140
- return isDroppable?.(groupId) ?? (groups.find((g) => g.id === groupId)?.droppable ?? true);
144
+ return isDroppable?.(groupId) ?? groups.find((g) => g.id === groupId)?.droppable ?? true;
141
145
  }
142
146
 
143
147
  function idAt(list: T[], index: number): string | null {
@@ -194,7 +198,6 @@
194
198
  return;
195
199
  }
196
200
  }
197
-
198
201
  </script>
199
202
 
200
203
  {#snippet section(group: C, depth: number)}
@@ -207,7 +210,10 @@
207
210
  {@const context = { count, active, open, depth, hasChildren: children.length > 0 }}
208
211
  <section
209
212
  aria-label={group.ariaLabel ?? group.label}
210
- class={cn("tl-group group/tl", depth === 0 ? "border-b border-border last:border-b-0" : "border-t border-border/60")}
213
+ class={cn(
214
+ "tl-group group/tl",
215
+ depth === 0 ? "border-b border-border last:border-b-0" : "border-t border-border/60",
216
+ )}
211
217
  class:tl-group-active={active}
212
218
  style:--tl-hue={hue}
213
219
  ondragover={children.length ? undefined : (e) => dnd.onColumnDragOver(e, group.id)}
@@ -215,7 +221,11 @@
215
221
  ondrop={children.length ? undefined : (e) => dnd.onDrop(e, group.id)}
216
222
  >
217
223
  <div
218
- class={cn("sticky z-10 flex h-10 items-center gap-2 px-3 backdrop-blur-sm", flat ? "bg-background/90" : "bg-card/90", depth === 0 && (head ? "top-9" : "top-0"))}
224
+ class={cn(
225
+ "sticky z-10 flex h-10 items-center gap-2 px-3 backdrop-blur-sm",
226
+ flat ? "bg-background/90" : "bg-card/90",
227
+ depth === 0 && (head ? "top-9" : "top-0"),
228
+ )}
219
229
  style:top={depth > 0 ? `${(head ? 2.25 : 0) + depth * 2.5}rem` : undefined}
220
230
  style:padding-inline-start={depth > 0 ? `${0.75 + depth * 1.25}rem` : undefined}
221
231
  >
@@ -227,7 +237,11 @@
227
237
  onclick={() => toggle(group.id)}
228
238
  class="-ml-1 grid size-6 place-items-center rounded-md text-muted-foreground outline-none transition-colors duration-[var(--duration-quick,150ms)] hover:bg-foreground/5 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/60"
229
239
  >
230
- <span class="tl-chevron size-3.5 [&>svg]:size-full" class:tl-chevron-open={open} aria-hidden="true">
240
+ <span
241
+ class="tl-chevron size-3.5 [&>svg]:size-full"
242
+ class:tl-chevron-open={open}
243
+ aria-hidden="true"
244
+ >
231
245
  <CaretRight weight="bold" />
232
246
  </span>
233
247
  </button>
@@ -267,7 +281,9 @@
267
281
  role="listitem"
268
282
  tabindex={draggable ? 0 : undefined}
269
283
  aria-label={getLabel?.(item) ??
270
- (draggable ? "Row. Press Alt plus Up or Down arrow to move between groups." : undefined)}
284
+ (draggable
285
+ ? "Row. Press Alt plus Up or Down arrow to move between groups."
286
+ : undefined)}
271
287
  aria-keyshortcuts={draggable ? "Alt+ArrowUp Alt+ArrowDown" : undefined}
272
288
  draggable={draggable ? "true" : undefined}
273
289
  animate:flip={{ duration: 250, easing: cubicOut }}
@@ -303,7 +319,9 @@
303
319
  {/if}
304
320
 
305
321
  {#if rows.length === 0 && children.length === 0 && !active}
306
- <p class="mx-1.5 rounded-lg border border-dashed border-border px-3 py-3 text-xs text-muted-foreground">
322
+ <p
323
+ class="mx-1.5 rounded-lg border border-dashed border-border px-3 py-3 text-xs text-muted-foreground"
324
+ >
307
325
  {emptyLabel}
308
326
  </p>
309
327
  {/if}
@@ -326,7 +344,10 @@
326
344
  >
327
345
  {#if head}
328
346
  <div
329
- class={cn("sticky top-0 z-20 flex h-9 shrink-0 items-center border-b border-border text-xs font-medium text-muted-foreground backdrop-blur-sm", flat ? "bg-background/90" : "bg-card/90")}
347
+ class={cn(
348
+ "sticky top-0 z-20 flex h-9 shrink-0 items-center border-b border-border text-xs font-medium text-muted-foreground backdrop-blur-sm",
349
+ flat ? "bg-background/90" : "bg-card/90",
350
+ )}
330
351
  >
331
352
  {@render head()}
332
353
  </div>
@@ -357,7 +378,8 @@
357
378
  }
358
379
 
359
380
  .tl-chevron {
360
- transition: rotate var(--duration-fast, 250ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
381
+ transition: rotate var(--duration-fast, 250ms)
382
+ var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
361
383
  }
362
384
 
363
385
  .tl-chevron-open {
@@ -370,7 +392,8 @@
370
392
  grid-template-rows: 0fr;
371
393
  opacity: 0;
372
394
  transition:
373
- grid-template-rows var(--duration-fast, 250ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)),
395
+ grid-template-rows var(--duration-fast, 250ms)
396
+ var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)),
374
397
  opacity var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
375
398
  }
376
399
 
@@ -385,7 +408,8 @@
385
408
  }
386
409
 
387
410
  .tl-row {
388
- animation: tl-row-in var(--duration-slow, 400ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)) backwards;
411
+ animation: tl-row-in var(--duration-slow, 400ms)
412
+ var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)) backwards;
389
413
  animation-delay: calc(var(--tl-i, 0) * var(--duration-stagger, 40ms));
390
414
  transition: background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
391
415
  }
@@ -2,68 +2,68 @@ import type { Tone } from "../../lib/tone.js";
2
2
 
3
3
  /** "90", "45m", "1h", "1.5h", "1h30", "1h 30m" and "1:30" to whole minutes. Null when it isn't a duration. */
4
4
  export function parseMinutes(text: string): number | null {
5
- const value = text.trim().toLowerCase();
6
- if (!value) return null;
7
- if (/^\d+$/.test(value)) return Number(value);
8
- const clock = /^(\d+):([0-5]?\d)$/.exec(value);
9
- if (clock) return Number(clock[1]) * 60 + Number(clock[2]);
10
- const decimalHours = /^(\d+(?:[.,]\d+)?)\s*h$/.exec(value);
11
- if (decimalHours?.[1]) return Math.round(Number(decimalHours[1].replace(",", ".")) * 60);
12
- const parts = /^(?:(\d+)\s*h)?\s*(?:(\d+)\s*m?)?$/.exec(value);
13
- if (!parts || (!parts[1] && !parts[2])) return null;
14
- return Number(parts[1] ?? 0) * 60 + Number(parts[2] ?? 0);
5
+ const value = text.trim().toLowerCase();
6
+ if (!value) return null;
7
+ if (/^\d+$/.test(value)) return Number(value);
8
+ const clock = /^(\d+):([0-5]?\d)$/.exec(value);
9
+ if (clock) return Number(clock[1]) * 60 + Number(clock[2]);
10
+ const decimalHours = /^(\d+(?:[.,]\d+)?)\s*h$/.exec(value);
11
+ if (decimalHours?.[1]) return Math.round(Number(decimalHours[1].replace(",", ".")) * 60);
12
+ const parts = /^(?:(\d+)\s*h)?\s*(?:(\d+)\s*m?)?$/.exec(value);
13
+ if (!parts || (!parts[1] && !parts[2])) return null;
14
+ return Number(parts[1] ?? 0) * 60 + Number(parts[2] ?? 0);
15
15
  }
16
16
 
17
17
  /** 150 to "2h 30m". */
18
18
  export function formatMinutes(minutes: number): string {
19
- const whole = Math.max(0, Math.round(Number.isFinite(minutes) ? minutes : 0));
20
- const hours = Math.floor(whole / 60);
21
- const rest = whole % 60;
22
- if (hours === 0) return `${rest}m`;
23
- return rest === 0 ? `${hours}h` : `${hours}h ${rest}m`;
19
+ const whole = Math.max(0, Math.round(Number.isFinite(minutes) ? minutes : 0));
20
+ const hours = Math.floor(whole / 60);
21
+ const rest = whole % 60;
22
+ if (hours === 0) return `${rest}m`;
23
+ return rest === 0 ? `${hours}h` : `${hours}h ${rest}m`;
24
24
  }
25
25
 
26
26
  /** Milliseconds to the timer readout, "1:05:09". */
27
27
  export function formatClock(ms: number): { hours: string; minutes: string; seconds: string } {
28
- const total = Math.max(0, Math.floor(ms / 1000));
29
- return {
30
- hours: String(Math.floor(total / 3600)),
31
- minutes: String(Math.floor((total % 3600) / 60)).padStart(2, "0"),
32
- seconds: String(total % 60).padStart(2, "0"),
33
- };
28
+ const total = Math.max(0, Math.floor(ms / 1000));
29
+ return {
30
+ hours: String(Math.floor(total / 3600)),
31
+ minutes: String(Math.floor((total % 3600) / 60)).padStart(2, "0"),
32
+ seconds: String(total % 60).padStart(2, "0"),
33
+ };
34
34
  }
35
35
 
36
36
  /** The reader's own calendar day as `YYYY-MM-DD`. */
37
37
  export function localDay(date: Date = new Date()): string {
38
- const month = String(date.getMonth() + 1).padStart(2, "0");
39
- const day = String(date.getDate()).padStart(2, "0");
40
- return `${date.getFullYear()}-${month}-${day}`;
38
+ const month = String(date.getMonth() + 1).padStart(2, "0");
39
+ const day = String(date.getDate()).padStart(2, "0");
40
+ return `${date.getFullYear()}-${month}-${day}`;
41
41
  }
42
42
 
43
43
  /** "Today", "Yesterday", then "Mon, Oct 12", in the given locale. */
44
44
  export function dayLabel(day: string, locale?: string): string {
45
- const date = new Date(`${day}T12:00:00`);
46
- const now = new Date();
47
- now.setHours(12, 0, 0, 0);
48
- const gap = Math.round((date.getTime() - now.getTime()) / 86_400_000);
49
- if (gap === 0 || gap === -1) {
50
- const word = new Intl.RelativeTimeFormat(locale, { numeric: "auto" }).format(gap, "day");
51
- return word.charAt(0).toLocaleUpperCase(locale) + word.slice(1);
52
- }
53
- return date.toLocaleDateString(locale, { weekday: "short", month: "short", day: "numeric" });
45
+ const date = new Date(`${day}T12:00:00`);
46
+ const now = new Date();
47
+ now.setHours(12, 0, 0, 0);
48
+ const gap = Math.round((date.getTime() - now.getTime()) / 86_400_000);
49
+ if (gap === 0 || gap === -1) {
50
+ const word = new Intl.RelativeTimeFormat(locale, { numeric: "auto" }).format(gap, "day");
51
+ return word.charAt(0).toLocaleUpperCase(locale) + word.slice(1);
52
+ }
53
+ return date.toLocaleDateString(locale, { weekday: "short", month: "short", day: "numeric" });
54
54
  }
55
55
 
56
56
  /** The tone of a budget: quiet until 90%, warning to 100%, destructive past it. */
57
57
  export function budgetTone(logged: number, estimate: number | null | undefined): Tone {
58
- if (!estimate || estimate <= 0) return "neutral";
59
- const ratio = logged / estimate;
60
- if (ratio > 1) return "destructive";
61
- return ratio >= 0.9 ? "warning" : "neutral";
58
+ if (!estimate || estimate <= 0) return "neutral";
59
+ const ratio = logged / estimate;
60
+ if (ratio > 1) return "destructive";
61
+ return ratio >= 0.9 ? "warning" : "neutral";
62
62
  }
63
63
 
64
64
  /** A stable color for a person, from their name or id. */
65
65
  export function personColor(key: string): string {
66
- let hash = 0;
67
- for (const char of key) hash = (hash * 31 + char.charCodeAt(0)) % 360;
68
- return `oklch(0.72 0.12 ${hash})`;
66
+ let hash = 0;
67
+ for (const char of key) hash = (hash * 31 + char.charCodeAt(0)) % 360;
68
+ return `oklch(0.72 0.12 ${hash})`;
69
69
  }
@@ -16,7 +16,7 @@
16
16
  /** Replaces the spoken name. Defaults to a sentence built from the numbers. */
17
17
  label?: string;
18
18
  class?: string;
19
- };
19
+ }
20
20
 
21
21
  let { minutes, estimate, running = false, label, class: className }: Props = $props();
22
22
 
@@ -56,7 +56,14 @@
56
56
  <span class="size-3 shrink-0 [&>svg]:size-full" aria-hidden="true"><Clock /></span>
57
57
  {/if}
58
58
  <span aria-hidden="true">
59
- {formatMinutes(minutes)}{#if estimate}<span class={cn("font-normal", tone === "neutral" ? "text-muted-foreground/70" : TONE_TEXT[tone])}> / {formatMinutes(estimate)}</span>{/if}
59
+ {formatMinutes(minutes)}{#if estimate}<span
60
+ class={cn(
61
+ "font-normal",
62
+ tone === "neutral" ? "text-muted-foreground/70" : TONE_TEXT[tone],
63
+ )}
64
+ >
65
+ / {formatMinutes(estimate)}</span
66
+ >{/if}
60
67
  </span>
61
68
  </span>
62
69
  {/if}
@@ -6,7 +6,7 @@
6
6
  running?: boolean;
7
7
  size?: number;
8
8
  class?: string;
9
- };
9
+ }
10
10
 
11
11
  let { running = false, size = 8, class: className }: Props = $props();
12
12
  </script>
@@ -17,7 +17,7 @@
17
17
  /** Rendered in the middle, e.g. the percentage. */
18
18
  children?: Snippet;
19
19
  class?: string;
20
- };
20
+ }
21
21
 
22
22
  let { value, max, size = 56, stroke = 4, label, children, class: className }: Props = $props();
23
23
 
@@ -36,7 +36,14 @@
36
36
  aria-hidden={label ? undefined : "true"}
37
37
  >
38
38
  <svg viewBox="0 0 36 36" class="size-full -rotate-90" fill="none" aria-hidden="true">
39
- <circle cx="18" cy="18" r={R} stroke="currentColor" stroke-width={stroke} class="text-foreground/10" />
39
+ <circle
40
+ cx="18"
41
+ cy="18"
42
+ r={R}
43
+ stroke="currentColor"
44
+ stroke-width={stroke}
45
+ class="text-foreground/10"
46
+ />
40
47
  <circle
41
48
  cx="18"
42
49
  cy="18"
@@ -58,7 +65,8 @@
58
65
  <style>
59
66
  .time-ring-arc {
60
67
  transition:
61
- stroke-dasharray var(--duration-slow, 400ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)),
68
+ stroke-dasharray var(--duration-slow, 400ms)
69
+ var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)),
62
70
  color var(--duration-fast, 250ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
63
71
  }
64
72