@heyramzi/ui 1.54.2 → 2.0.1

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 (363) hide show
  1. package/COMPONENTS.md +3 -2
  2. package/README.md +34 -9
  3. package/package.json +205 -13
  4. package/scripts/sync-shadcn.mjs +2 -0
  5. package/src/ai/generation-progress/GenerationProgress.svelte +12 -12
  6. package/src/ambient.d.ts +8 -0
  7. package/src/brand/brand.test.ts +67 -0
  8. package/src/brand/index.ts +137 -0
  9. package/src/canvas/README.md +236 -0
  10. package/src/canvas/card/card-frame.svelte +61 -0
  11. package/src/canvas/card/card.css +39 -0
  12. package/src/canvas/card/resize-handles.svelte +119 -0
  13. package/src/canvas/card/resize.ts +33 -0
  14. package/src/canvas/chrome/chrome-zoom.svelte +120 -0
  15. package/src/canvas/chrome/chrome.css +231 -0
  16. package/src/canvas/chrome/lock-badge.svelte +18 -0
  17. package/src/canvas/chrome/side-handles.svelte +30 -0
  18. package/src/canvas/chrome/size-chip.svelte +45 -0
  19. package/src/canvas/chrome/snap-guides.svelte +69 -0
  20. package/src/canvas/components/icons/ToolIcon.svelte +30 -0
  21. package/src/canvas/components/shared/svg/PreviewSvg.svelte +67 -0
  22. package/src/canvas/components/shared/svg/ShapeOutline.svelte +76 -0
  23. package/src/canvas/components/shared/svg/StrokeNode.svelte +69 -0
  24. package/src/canvas/components/shared/svg/TextRule.svelte +37 -0
  25. package/src/canvas/edges/bend-handle.svelte +76 -0
  26. package/src/canvas/edges/edge-gradient.svelte +30 -0
  27. package/src/canvas/edges/edge-path.test.ts +58 -0
  28. package/src/canvas/edges/edge-path.ts +293 -0
  29. package/src/canvas/edges/gradient-edge.svelte +43 -0
  30. package/src/canvas/editor/context-menu.test.ts +65 -0
  31. package/src/canvas/editor/context-menu.ts +48 -0
  32. package/src/canvas/editor/dispatch.svelte.test.ts +243 -0
  33. package/src/canvas/editor/dispatch.svelte.ts +205 -0
  34. package/src/canvas/editor/editor-context.ts +62 -0
  35. package/src/canvas/editor/editor-mode.ts +67 -0
  36. package/src/canvas/editor/frame.ts +98 -0
  37. package/src/canvas/editor/hand-context.ts +15 -0
  38. package/src/canvas/editor/history-core.test.ts +239 -0
  39. package/src/canvas/editor/history-core.ts +247 -0
  40. package/src/canvas/editor/keymap.test.ts +161 -0
  41. package/src/canvas/editor/keymap.ts +151 -0
  42. package/src/canvas/editor/layout-actions.test.ts +156 -0
  43. package/src/canvas/editor/layout-actions.ts +219 -0
  44. package/src/canvas/editor/lock.test.ts +68 -0
  45. package/src/canvas/editor/lock.ts +87 -0
  46. package/src/canvas/editor/rebase.test.ts +208 -0
  47. package/src/canvas/editor/rebase.ts +272 -0
  48. package/src/canvas/editor/style.ts +184 -0
  49. package/src/canvas/editor/text-field-context.ts +41 -0
  50. package/src/canvas/editor/z-order.test.ts +75 -0
  51. package/src/canvas/editor/z-order.ts +87 -0
  52. package/src/canvas/headless/alignment.test.ts +59 -0
  53. package/src/canvas/headless/alignment.ts +23 -0
  54. package/src/canvas/headless/break-apart-find.ts +418 -0
  55. package/src/canvas/headless/break-apart-pieces.ts +104 -0
  56. package/src/canvas/headless/break-apart-plan.ts +439 -0
  57. package/src/canvas/headless/break-apart-style.ts +106 -0
  58. package/src/canvas/headless/break-apart.ts +244 -0
  59. package/src/canvas/headless/canvas-cleanup.test.ts +181 -0
  60. package/src/canvas/headless/canvas-cleanup.ts +102 -0
  61. package/src/canvas/headless/edge-anchors.test.ts +70 -0
  62. package/src/canvas/headless/edge-anchors.ts +143 -0
  63. package/src/canvas/headless/edge-memo.test.ts +58 -0
  64. package/src/canvas/headless/edge-memo.ts +32 -0
  65. package/src/canvas/headless/edge-serialization.test.ts +55 -0
  66. package/src/canvas/headless/edge-serialization.ts +32 -0
  67. package/src/canvas/headless/figure-geometry.ts +293 -0
  68. package/src/canvas/headless/fit-viewport.test.ts +85 -0
  69. package/src/canvas/headless/fit-viewport.ts +137 -0
  70. package/src/canvas/headless/grid.test.ts +34 -0
  71. package/src/canvas/headless/grid.ts +94 -0
  72. package/src/canvas/headless/icon-node.test.ts +23 -0
  73. package/src/canvas/headless/icon-node.ts +66 -0
  74. package/src/canvas/headless/index.ts +9 -0
  75. package/src/canvas/headless/linkify.test.ts +23 -0
  76. package/src/canvas/headless/linkify.ts +25 -0
  77. package/src/canvas/headless/pen-marks.ts +133 -0
  78. package/src/canvas/headless/sketch.test.ts +99 -0
  79. package/src/canvas/headless/sketch.ts +302 -0
  80. package/src/canvas/headless/snap-guides.test.ts +61 -0
  81. package/src/canvas/headless/snap-guides.ts +146 -0
  82. package/src/canvas/headless/svg-figure.test.ts +49 -0
  83. package/src/canvas/headless/svg-figure.ts +176 -0
  84. package/src/canvas/headless/table-layout.test.ts +158 -0
  85. package/src/canvas/headless/table-layout.ts +309 -0
  86. package/src/canvas/index.ts +35 -0
  87. package/src/canvas/lod/lod-mode.svelte.ts +30 -0
  88. package/src/canvas/lod/lod-switch.svelte +140 -0
  89. package/src/canvas/lod/render-all.svelte.ts +52 -0
  90. package/src/canvas/ops/arrange.ts +108 -0
  91. package/src/canvas/ops/ops.test.ts +98 -0
  92. package/src/canvas/ops/ops.ts +347 -0
  93. package/src/canvas/pen/PenField.svelte +128 -0
  94. package/src/canvas/pen/PenField.test.ts +87 -0
  95. package/src/canvas/pen/PenOverlay.svelte +172 -0
  96. package/src/canvas/pen/ShapeNode.svelte +64 -0
  97. package/src/canvas/pen/TextNode.svelte +97 -0
  98. package/src/canvas/pen/colors.ts +28 -0
  99. package/src/canvas/pen/eraser.ts +46 -0
  100. package/src/canvas/pen/geometry.ts +148 -0
  101. package/src/canvas/pen/host-field.test.svelte +13 -0
  102. package/src/canvas/pen/index.ts +40 -0
  103. package/src/canvas/pen/ops.ts +16 -0
  104. package/src/canvas/pen/pen-session.ts +177 -0
  105. package/src/canvas/pen/pen.css +149 -0
  106. package/src/canvas/pen/plugin.ts +139 -0
  107. package/src/canvas/pen/shapes.ts +104 -0
  108. package/src/canvas/pen/stroke-path.ts +54 -0
  109. package/src/canvas/pen/types.ts +96 -0
  110. package/src/canvas/pen/ui.ts +26 -0
  111. package/src/canvas/plugin.test.ts +45 -0
  112. package/src/canvas/plugin.ts +159 -0
  113. package/src/canvas/screen/GlyphIcon.svelte +25 -0
  114. package/src/canvas/screen/PartView.svelte +176 -0
  115. package/src/canvas/screen/ScreenBody.svelte +142 -0
  116. package/src/canvas/screen/ScreenCard.svelte +32 -0
  117. package/src/canvas/screen/glyphs/button.svg +1 -0
  118. package/src/canvas/screen/glyphs/close.svg +1 -0
  119. package/src/canvas/screen/glyphs/down.svg +1 -0
  120. package/src/canvas/screen/glyphs/header.svg +1 -0
  121. package/src/canvas/screen/glyphs/hills.svg +1 -0
  122. package/src/canvas/screen/glyphs/image.svg +1 -0
  123. package/src/canvas/screen/glyphs/input.svg +1 -0
  124. package/src/canvas/screen/glyphs/list.svg +1 -0
  125. package/src/canvas/screen/glyphs/modal.svg +1 -0
  126. package/src/canvas/screen/glyphs/plus.svg +1 -0
  127. package/src/canvas/screen/glyphs/up.svg +1 -0
  128. package/src/canvas/screen/glyphs.ts +40 -0
  129. package/src/canvas/screen/labels.ts +41 -0
  130. package/src/canvas/screen/parts.ts +164 -0
  131. package/src/canvas/screen/plugin.ts +67 -0
  132. package/src/canvas/screen/screen.css +593 -0
  133. package/src/canvas/screen/types.ts +47 -0
  134. package/src/canvas/screen/ui.ts +13 -0
  135. package/src/canvas/test-utils.ts +18 -0
  136. package/src/canvas/types.ts +53 -0
  137. package/src/canvas/ui/CanvasToolbar.svelte +169 -0
  138. package/src/canvas/ui/FrameLayer.svelte +116 -0
  139. package/src/canvas/ui/PenLayer.svelte +39 -0
  140. package/src/canvas/ui/StylePanel.svelte +268 -0
  141. package/src/canvas/ui/canvas-grid.svelte +156 -0
  142. package/src/canvas/ui/canvas-ui.css +350 -0
  143. package/src/canvas/ui/forward-wheel.ts +29 -0
  144. package/src/canvas/ui/icons.ts +124 -0
  145. package/src/canvas/ui/place-panel.ts +80 -0
  146. package/src/canvas/ui/shortcut-tip.test.ts +16 -0
  147. package/src/canvas/ui/shortcut-tip.ts +17 -0
  148. package/src/canvas/ui/tools.ts +146 -0
  149. package/src/charts/README.md +104 -0
  150. package/src/charts/ambient.d.ts +4 -0
  151. package/src/charts/animated-value.css +7 -0
  152. package/src/charts/animated-value.svelte +110 -0
  153. package/src/charts/area-chart.css +29 -0
  154. package/src/charts/area-chart.svelte +204 -0
  155. package/src/charts/bar-chart.css +119 -0
  156. package/src/charts/bar-chart.svelte +275 -0
  157. package/src/charts/charts.css +201 -0
  158. package/src/charts/crosshair.svelte +35 -0
  159. package/src/charts/dashboard/chart.ts +5 -0
  160. package/src/charts/dashboard/dashboard-empty.svelte +55 -0
  161. package/src/charts/dashboard/dashboard-section.svelte +58 -0
  162. package/src/charts/dashboard/data-panel.svelte +81 -0
  163. package/src/charts/dashboard/data-sort-head.svelte +102 -0
  164. package/src/charts/dashboard/data-table.svelte +278 -0
  165. package/src/charts/dashboard/data-table.ts +26 -0
  166. package/src/charts/dashboard/index.ts +25 -0
  167. package/src/charts/dashboard/panel-grid.svelte +43 -0
  168. package/src/charts/dashboard/period-filter.svelte +53 -0
  169. package/src/charts/dashboard/rating-chip.svelte +35 -0
  170. package/src/charts/dashboard/stat-band.svelte +47 -0
  171. package/src/charts/dashboard/stat-tile.svelte +176 -0
  172. package/src/charts/dashboard/windows.ts +8 -0
  173. package/src/charts/donut-chart.css +56 -0
  174. package/src/charts/donut-chart.svelte +158 -0
  175. package/src/charts/edge-fade.svelte +43 -0
  176. package/src/charts/edge-fade.ts +16 -0
  177. package/src/charts/figure.css +100 -0
  178. package/src/charts/figure.svelte +122 -0
  179. package/src/charts/gauge-chart.css +94 -0
  180. package/src/charts/gauge-chart.svelte +302 -0
  181. package/src/charts/hover-band.test.ts +30 -0
  182. package/src/charts/hover-band.ts +21 -0
  183. package/src/charts/hover-focus.svelte +85 -0
  184. package/src/charts/index.ts +31 -0
  185. package/src/charts/legend.css +136 -0
  186. package/src/charts/legend.svelte +112 -0
  187. package/src/charts/line-chart.css +26 -0
  188. package/src/charts/line-chart.svelte +256 -0
  189. package/src/charts/motion.ts +19 -0
  190. package/src/charts/number-chart.css +179 -0
  191. package/src/charts/number-chart.svelte +187 -0
  192. package/src/charts/primitives/chart-container.svelte +57 -0
  193. package/src/charts/primitives/chart-style.svelte +36 -0
  194. package/src/charts/primitives/chart-tooltip.svelte +161 -0
  195. package/src/charts/primitives/chart-utils.ts +54 -0
  196. package/src/charts/primitives/index.ts +11 -0
  197. package/src/charts/reference-line.css +18 -0
  198. package/src/charts/reference-line.svelte +31 -0
  199. package/src/charts/reveal.svelte +61 -0
  200. package/src/charts/ring-chart.css +104 -0
  201. package/src/charts/ring-chart.svelte +166 -0
  202. package/src/charts/sparkline.css +45 -0
  203. package/src/charts/sparkline.svelte +141 -0
  204. package/src/charts/stat-card.css +105 -0
  205. package/src/charts/stat-card.svelte +267 -0
  206. package/src/charts/time-series-chart.css +28 -0
  207. package/src/charts/time-series-chart.svelte +179 -0
  208. package/src/charts/trend-pill.css +38 -0
  209. package/src/charts/trend-pill.svelte +48 -0
  210. package/src/components/copy/copy-button.svelte +1 -1
  211. package/src/components/docs/doc-page.svelte +1 -1
  212. package/src/components/docs/on-this-page.svelte +2 -2
  213. package/src/components/list-page/empty-state.svelte +1 -1
  214. package/src/components/list-page/filter-popover.svelte +1 -1
  215. package/src/components/list-page/section-label.svelte +1 -1
  216. package/src/components/segmented/segment.svelte +4 -4
  217. package/src/components/theme/theme-provider.svelte +6 -4
  218. package/src/components/toolbar/index.ts +5 -0
  219. package/src/components/toolbar/toolbar.svelte +168 -0
  220. package/src/components/toolbar/toolbar.test.ts +66 -0
  221. package/src/editor/README.md +58 -0
  222. package/src/editor/ambient.d.ts +1 -0
  223. package/src/editor/block-id.ts +77 -0
  224. package/src/editor/block-ops.test.ts +237 -0
  225. package/src/editor/block-ops.ts +157 -0
  226. package/src/editor/content.css +418 -0
  227. package/src/editor/editor-toolbar.svelte +303 -0
  228. package/src/editor/editor.svelte +469 -0
  229. package/src/editor/extensions.ts +145 -0
  230. package/src/editor/image-toolbar.svelte +123 -0
  231. package/src/editor/index.ts +17 -0
  232. package/src/editor/inline-editor.svelte +261 -0
  233. package/src/editor/markdown-escaping.test.ts +158 -0
  234. package/src/editor/markdown-marks.test.ts +89 -0
  235. package/src/editor/markdown-test-helpers.ts +13 -0
  236. package/src/editor/markdown-youtube.test.ts +93 -0
  237. package/src/editor/markdown.test.ts +250 -0
  238. package/src/editor/markdown.ts +511 -0
  239. package/src/editor/renderer.svelte +92 -0
  240. package/src/editor/serializer.test.ts +133 -0
  241. package/src/editor/serializer.ts +370 -0
  242. package/src/editor/slash-items.ts +115 -0
  243. package/src/editor/slash-menu.svelte +159 -0
  244. package/src/editor/youtube-url.ts +35 -0
  245. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +13 -13
  246. package/src/gallery.test.ts +20 -1
  247. package/src/lib/result.ts +19 -0
  248. package/src/onboarding/README.md +120 -0
  249. package/src/onboarding/define-tours.test.ts +50 -0
  250. package/src/onboarding/define-tours.ts +31 -0
  251. package/src/onboarding/demo.svelte +86 -0
  252. package/src/onboarding/index.ts +12 -0
  253. package/src/onboarding/onboarding-provider.svelte +120 -0
  254. package/src/onboarding/onboarding.svelte.ts +24 -0
  255. package/src/onboarding/pick.test.ts +79 -0
  256. package/src/onboarding/pick.ts +36 -0
  257. package/src/onboarding/stores.test.ts +103 -0
  258. package/src/onboarding/stores.ts +58 -0
  259. package/src/onboarding/tour-card.svelte +70 -0
  260. package/src/onboarding/tour-runner.svelte +197 -0
  261. package/src/onboarding/types.ts +34 -0
  262. package/src/onboarding/wait.ts +29 -0
  263. package/src/shadcn/command/command-input.svelte +1 -1
  264. package/src/slides/Controls.svelte +138 -0
  265. package/src/slides/Deck.svelte +271 -0
  266. package/src/slides/Fragment.svelte +71 -0
  267. package/src/slides/Overview.svelte +113 -0
  268. package/src/slides/Presenter.svelte +292 -0
  269. package/src/slides/README.md +89 -0
  270. package/src/slides/Slide.svelte +179 -0
  271. package/src/slides/SlideList.svelte +19 -0
  272. package/src/slides/ambient.d.ts +1 -0
  273. package/src/slides/bridges.ts +69 -0
  274. package/src/slides/deck-controller.svelte.ts +292 -0
  275. package/src/slides/demo-slides.svelte +358 -0
  276. package/src/slides/demo.svelte +176 -0
  277. package/src/slides/export/capture.ts +113 -0
  278. package/src/slides/export/ffmpeg.ts +120 -0
  279. package/src/slides/export/index.ts +9 -0
  280. package/src/slides/export/launch.ts +10 -0
  281. package/src/slides/export/pdf.ts +152 -0
  282. package/src/slides/export/types.ts +48 -0
  283. package/src/slides/index.ts +19 -0
  284. package/src/slides/labels.ts +48 -0
  285. package/src/slides/layout.ts +51 -0
  286. package/src/slides/model.ts +47 -0
  287. package/src/slides/navigation.ts +100 -0
  288. package/src/slides/theme.css +205 -0
  289. package/src/slides/types.ts +86 -0
  290. package/src/tasks/README.md +312 -0
  291. package/src/tasks/ambient.d.ts +3 -0
  292. package/src/tasks/board-order.ts +137 -0
  293. package/src/tasks/dates.ts +31 -0
  294. package/src/tasks/define-statuses.ts +32 -0
  295. package/src/tasks/demo.svelte +389 -0
  296. package/src/tasks/detail/detail-demo.svelte +186 -0
  297. package/src/tasks/detail/detail-types.ts +54 -0
  298. package/src/tasks/detail/task-chip.svelte +28 -0
  299. package/src/tasks/detail/task-detail.svelte +315 -0
  300. package/src/tasks/detail/task-property.svelte +17 -0
  301. package/src/tasks/detail/task-thread.svelte +258 -0
  302. package/src/tasks/gantt/gantt-bar.svelte +243 -0
  303. package/src/tasks/gantt/gantt-grid.svelte +94 -0
  304. package/src/tasks/gantt/gantt-group-row.svelte +55 -0
  305. package/src/tasks/gantt/gantt-header.svelte +71 -0
  306. package/src/tasks/gantt/gantt-task-row.svelte +186 -0
  307. package/src/tasks/gantt/gantt.css +22 -0
  308. package/src/tasks/gantt/gantt.test.ts +77 -0
  309. package/src/tasks/gantt/logic.test.ts +163 -0
  310. package/src/tasks/gantt/logic.ts +248 -0
  311. package/src/tasks/inbox/inbox-card.svelte +283 -0
  312. package/src/tasks/inbox/inbox-draft.svelte +84 -0
  313. package/src/tasks/inbox/inbox-logic.ts +55 -0
  314. package/src/tasks/inbox/inbox-statuses.ts +48 -0
  315. package/src/tasks/inbox/inbox-trace.svelte +45 -0
  316. package/src/tasks/inbox/inbox-types.ts +108 -0
  317. package/src/tasks/inbox/inbox.svelte +203 -0
  318. package/src/tasks/inbox/index.ts +23 -0
  319. package/src/tasks/index.ts +85 -0
  320. package/src/tasks/kanban-board.svelte +561 -0
  321. package/src/tasks/kanban-column.svelte +117 -0
  322. package/src/tasks/kanban-drag.ts +38 -0
  323. package/src/tasks/kanban-types.ts +65 -0
  324. package/src/tasks/presets/sales.ts +16 -0
  325. package/src/tasks/record/demo-data.ts +286 -0
  326. package/src/tasks/record/field-section.svelte +53 -0
  327. package/src/tasks/record/fields.ts +142 -0
  328. package/src/tasks/record/format.ts +87 -0
  329. package/src/tasks/record/gutter.ts +2 -0
  330. package/src/tasks/record/index.ts +54 -0
  331. package/src/tasks/record/info-list.svelte +316 -0
  332. package/src/tasks/record/kinds.ts +42 -0
  333. package/src/tasks/record/logic.ts +324 -0
  334. package/src/tasks/record/next-up.svelte +70 -0
  335. package/src/tasks/record/project-list.svelte +133 -0
  336. package/src/tasks/record/record-demo.svelte +324 -0
  337. package/src/tasks/record/record-header.svelte +70 -0
  338. package/src/tasks/record/record-shell.svelte +138 -0
  339. package/src/tasks/record/record-stats.svelte +48 -0
  340. package/src/tasks/record/record-timeline.svelte +231 -0
  341. package/src/tasks/record/record.test.ts +350 -0
  342. package/src/tasks/record/steps-rail.svelte +474 -0
  343. package/src/tasks/record/types.ts +280 -0
  344. package/src/tasks/record/where-we-are.svelte +121 -0
  345. package/src/tasks/reorder.ts +19 -0
  346. package/src/tasks/side-panel-types.ts +16 -0
  347. package/src/tasks/side-panel.svelte +193 -0
  348. package/src/tasks/status-badge.svelte +63 -0
  349. package/src/tasks/status-dot.svelte +228 -0
  350. package/src/tasks/status-icon.svelte +30 -0
  351. package/src/tasks/status-types.ts +40 -0
  352. package/src/tasks/task-gantt.svelte +230 -0
  353. package/src/tasks/task-list.svelte +470 -0
  354. package/src/tasks/task-types.ts +33 -0
  355. package/src/tasks/time/duration.ts +69 -0
  356. package/src/tasks/time/time-chip.svelte +62 -0
  357. package/src/tasks/time/time-dot.svelte +58 -0
  358. package/src/tasks/time/time-ring.svelte +74 -0
  359. package/src/tasks/time/time-tracker.svelte +565 -0
  360. package/src/tasks/time/time-types.ts +74 -0
  361. package/src/tasks/use-kanban-dnd.svelte.ts +173 -0
  362. package/src/tokens/base.css +5 -5
  363. package/src/tokens/palette.css +115 -120
@@ -0,0 +1,324 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import { DotsThree, NotePencil } from "phosphor-svelte";
6
+ import { SegmentedFilter } from "../../components/segmented/index.js";
7
+ import { Button } from "../../shadcn/button/index.js";
8
+ import { Input } from "../../shadcn/input/index.js";
9
+ import * as Tabs from "../../shadcn/tabs/index.js";
10
+ import RecordShell from "./record-shell.svelte";
11
+ import RecordHeader from "./record-header.svelte";
12
+ import WhereWeAre from "./where-we-are.svelte";
13
+ import NextUp from "./next-up.svelte";
14
+ import RecordStats from "./record-stats.svelte";
15
+ import StepsRail from "./steps-rail.svelte";
16
+ import RecordTimeline from "./record-timeline.svelte";
17
+ import ProjectList from "./project-list.svelte";
18
+ import FieldSection from "./field-section.svelte";
19
+ import InfoList from "./info-list.svelte";
20
+ import { formatAge, formatMoney, formatRange } from "./format.js";
21
+ import {
22
+ latestWhereWeAre,
23
+ mergeClientEvents,
24
+ nextAcrossProjects,
25
+ rollupProjects,
26
+ stepProgress,
27
+ } from "./logic.js";
28
+ import type { FieldValue, RecordEvent, StepDef } from "./types.js";
29
+ import {
30
+ DEMO_NOW,
31
+ DEMO_TODAY,
32
+ clientFields,
33
+ clientOnlyEvents,
34
+ clientProjects,
35
+ clientSections,
36
+ clientValues,
37
+ projectFields,
38
+ projectSections,
39
+ stages,
40
+ stepGroups,
41
+ website,
42
+ websiteEvents,
43
+ websiteSteps,
44
+ websiteValues,
45
+ } from "./demo-data.js";
46
+
47
+ // =========================
48
+ // STATE
49
+ // =========================
50
+ type Fixture = "project" | "client";
51
+ let fixture = $state<Fixture>("project");
52
+ let narrow = $state(false);
53
+ let tab = $state<"activity" | "notes">("activity");
54
+ let filter = $state("all");
55
+ let steps = $state<StepDef[]>(structuredClone(websiteSteps));
56
+ let activeStep = $state<string | null>("s2");
57
+ let projectValues = $state<Record<string, FieldValue | undefined>>({ ...websiteValues });
58
+ let personValues = $state<Record<string, FieldValue | undefined>>({ ...clientValues });
59
+ let extra = $state<RecordEvent[]>([]);
60
+ let draftNote = $state("");
61
+ let railOpen = $state(false);
62
+
63
+ // =========================
64
+ // DERIVED
65
+ // =========================
66
+ const locale = "en-GB";
67
+ let projectEvents = $derived([...extra, ...websiteEvents]);
68
+ let clientEvents = $derived(
69
+ mergeClientEvents(
70
+ clientOnlyEvents,
71
+ clientProjects.map((p) => ({
72
+ id: p.summary.id,
73
+ name: p.summary.name,
74
+ href: p.summary.href,
75
+ events: p.events,
76
+ })),
77
+ ),
78
+ );
79
+ let summaries = $derived(clientProjects.map((p) => p.summary));
80
+ let rollup = $derived(rollupProjects(summaries, stages));
81
+ let latest = $derived(latestWhereWeAre(summaries));
82
+ let next = $derived(nextAcrossProjects(summaries, stages));
83
+ let progress = $derived(stepProgress(steps));
84
+ let notes = $derived(projectEvents.filter((e) => e.kind === "note"));
85
+ let stage = $derived(stages.find((s) => s.key === website.stageKey));
86
+
87
+ let clientStats = $derived.by(() => {
88
+ const eur = rollup.totals.EUR;
89
+ return [
90
+ { label: "Projects", value: String(rollup.count), sub: `${rollup.activeCount} active` },
91
+ { label: "Pipeline", value: formatMoney(eur?.pipeline ?? 0, "EUR", locale, true) },
92
+ { label: "Won", value: formatMoney(eur?.won ?? 0, "EUR", locale, true) },
93
+ { label: "Paid", value: formatMoney(eur?.paid ?? 0, "EUR", locale, true), tone: "good" as const },
94
+ { label: "Last call", value: rollup.lastCallAt ? formatAge(rollup.lastCallAt, DEMO_NOW, locale) : "None yet" },
95
+ ];
96
+ });
97
+
98
+ // =========================
99
+ // FUNCTIONS
100
+ // =========================
101
+ function addNote() {
102
+ const title = draftNote.trim();
103
+ if (!title) return;
104
+ extra = [
105
+ { id: `n${extra.length}`, kind: "note", occurredAt: DEMO_NOW.toISOString(), title, actor: "Ramzi" },
106
+ ...extra,
107
+ ];
108
+ draftNote = "";
109
+ }
110
+
111
+ // A real host would round-trip here. The pause lets the rail show its optimistic order.
112
+ async function pause() {
113
+ await new Promise((resolve) => setTimeout(resolve, 250));
114
+ }
115
+ </script>
116
+
117
+ <!-- =========================== -->
118
+ <!-- MARKUP -->
119
+ <!-- =========================== -->
120
+ {#snippet composer()}
121
+ <form
122
+ class="flex items-center gap-2"
123
+ onsubmit={(e) => {
124
+ e.preventDefault();
125
+ addNote();
126
+ }}
127
+ >
128
+ <Input bind:value={draftNote} placeholder="Write a note" aria-label="Write a note" class="flex-1" />
129
+ <Button type="submit" variant="outline" disabled={!draftNote.trim()}>
130
+ <NotePencil data-icon="inline-start" />
131
+ Add
132
+ </Button>
133
+ </form>
134
+ {/snippet}
135
+
136
+ {#snippet payment(event: RecordEvent)}
137
+ <p class="text-sm leading-snug font-medium">{event.title}</p>
138
+ <p class="text-lg font-semibold tabular-nums text-success">
139
+ {formatMoney(event.amount ?? 0, event.currency ?? "EUR", locale)}
140
+ </p>
141
+ {/snippet}
142
+
143
+ <div class="flex flex-col gap-3">
144
+ <div class="flex flex-wrap items-center gap-3">
145
+ <SegmentedFilter
146
+ ariaLabel="Record"
147
+ block={false}
148
+ value={fixture}
149
+ onValueChange={(v) => (fixture = v)}
150
+ options={[
151
+ { value: "project", label: "One project" },
152
+ { value: "client", label: "Client with 3 projects" },
153
+ ]}
154
+ />
155
+ <SegmentedFilter
156
+ ariaLabel="Width"
157
+ block={false}
158
+ value={narrow ? "narrow" : "full"}
159
+ onValueChange={(v) => (narrow = v === "narrow")}
160
+ options={[
161
+ { value: "full", label: "Full width" },
162
+ { value: "narrow", label: "390px" },
163
+ ]}
164
+ />
165
+ </div>
166
+
167
+ <div class={narrow ? "mx-auto w-full max-w-[390px]" : "w-full"}>
168
+ <div class="overflow-hidden rounded-xl border border-border">
169
+ {#if fixture === "project"}
170
+ <RecordShell
171
+ bind:tab
172
+ bind:railOpen
173
+ tabs={[
174
+ { value: "activity", label: "Activity", count: projectEvents.length },
175
+ { value: "notes", label: "Notes", count: notes.length },
176
+ ]}
177
+ tabsLabel="Project views"
178
+ railProgress={progress}
179
+ >
180
+ {#snippet header()}
181
+ <RecordHeader
182
+ title={website.name}
183
+ parent={{ label: "Marlow & Finch", href: "#client" }}
184
+ stage={stage ? { label: stage.label, tone: stage.tone } : null}
185
+ facts={[
186
+ formatMoney(website.value ?? 0, "EUR", locale),
187
+ formatRange(website.startDate ?? "", website.endDate, locale),
188
+ ]}
189
+ >
190
+ {#snippet actions()}
191
+ <Button variant="ghost" size="sm" icon aria-label="More actions"><DotsThree weight="bold" /></Button>
192
+ {/snippet}
193
+ </RecordHeader>
194
+ {/snippet}
195
+ {#snippet banner()}
196
+ <WhereWeAre
197
+ note={website.whereWeAre?.text}
198
+ at={website.whereWeAre?.at}
199
+ actor={website.whereWeAre?.actor}
200
+ health={website.whereWeAre?.health}
201
+ now={DEMO_NOW}
202
+ {locale}
203
+ />
204
+ <NextUp next={website.next} today={DEMO_TODAY} {locale} />
205
+ {/snippet}
206
+ <Tabs.Content value="activity" class="mt-0">
207
+ <RecordTimeline
208
+ events={projectEvents}
209
+ bind:filter
210
+ {composer}
211
+ entry={{ payment }}
212
+ today={DEMO_TODAY}
213
+ timeZone="UTC"
214
+ {locale}
215
+ />
216
+ </Tabs.Content>
217
+ <Tabs.Content value="notes" class="mt-0">
218
+ <RecordTimeline events={notes} {composer} today={DEMO_TODAY} timeZone="UTC" {locale} />
219
+ </Tabs.Content>
220
+ {#snippet rail()}
221
+ <div class="flex flex-col gap-6">
222
+ <StepsRail
223
+ {steps}
224
+ groups={stepGroups}
225
+ activeId={activeStep}
226
+ today={DEMO_TODAY}
227
+ {locale}
228
+ onSelect={(id) => (activeStep = id)}
229
+ onToggle={(id, visible) => {
230
+ steps = steps.map((s) => (s.id === id ? { ...s, hidden: !visible } : s));
231
+ }}
232
+ onReorder={async (ids) => {
233
+ await pause();
234
+ const byId = new Map(steps.map((s) => [s.id, s]));
235
+ steps = ids.flatMap((id) => byId.get(id) ?? []);
236
+ }}
237
+ onRename={(id, label) => {
238
+ const original = websiteSteps.find((s) => s.id === id)?.label ?? "";
239
+ steps = steps.map((s) =>
240
+ s.id === id ? { ...s, label: label || original, renamed: label !== "" } : s,
241
+ );
242
+ }}
243
+ />
244
+ <InfoList
245
+ title="People"
246
+ rows={[
247
+ { id: "contact", label: "Contact", value: "Jane Roe, CFO" },
248
+ { id: "owner", label: "Owner", value: "Ramzi" },
249
+ ]}
250
+ />
251
+ <FieldSection
252
+ fields={projectFields}
253
+ sections={projectSections}
254
+ values={projectValues}
255
+ currency="EUR"
256
+ {locale}
257
+ onSave={async (key, value) => {
258
+ await pause();
259
+ projectValues = { ...projectValues, [key]: value };
260
+ }}
261
+ />
262
+ </div>
263
+ {/snippet}
264
+ </RecordShell>
265
+ {:else}
266
+ <RecordShell bind:railOpen railTitle="Projects">
267
+ {#snippet header()}
268
+ <RecordHeader
269
+ title="Marlow & Finch"
270
+ parent={{ label: "Clients", href: "#clients" }}
271
+ facts={[
272
+ `Pipeline ${formatMoney(rollup.totals.EUR?.pipeline ?? 0, "EUR", locale, true)}`,
273
+ `Won ${formatMoney(rollup.totals.EUR?.won ?? 0, "EUR", locale, true)}`,
274
+ ]}
275
+ >
276
+ <RecordStats stats={clientStats} gutter={false} />
277
+ </RecordHeader>
278
+ {/snippet}
279
+ {#snippet banner()}
280
+ <WhereWeAre
281
+ note={latest?.text}
282
+ at={latest?.at}
283
+ actor={latest?.actor}
284
+ health={latest?.health}
285
+ project={latest ? { name: latest.projectName } : null}
286
+ now={DEMO_NOW}
287
+ {locale}
288
+ />
289
+ <NextUp {next} showProject today={DEMO_TODAY} {locale} />
290
+ {/snippet}
291
+ <RecordTimeline
292
+ events={clientEvents}
293
+ bind:filter
294
+ showProject
295
+ entry={{ payment }}
296
+ today={DEMO_TODAY}
297
+ timeZone="UTC"
298
+ {locale}
299
+ />
300
+ {#snippet rail()}
301
+ <div class="flex flex-col gap-6">
302
+ <section class="flex flex-col gap-2">
303
+ <h2 class="px-2 text-xs font-medium tracking-wide text-muted-foreground uppercase">
304
+ Projects
305
+ </h2>
306
+ <ProjectList projects={summaries} {stages} today={DEMO_TODAY} {locale} />
307
+ </section>
308
+ <FieldSection
309
+ fields={clientFields}
310
+ sections={clientSections}
311
+ values={personValues}
312
+ {locale}
313
+ onSave={async (key, value) => {
314
+ await pause();
315
+ personValues = { ...personValues, [key]: value };
316
+ }}
317
+ />
318
+ </div>
319
+ {/snippet}
320
+ </RecordShell>
321
+ {/if}
322
+ </div>
323
+ </div>
324
+ </div>
@@ -0,0 +1,70 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import type { Snippet } from "svelte";
6
+ import { CaretLeft } from "phosphor-svelte";
7
+ import { cn } from "../../lib/utils.js";
8
+ import type { Tone } from "../../lib/tone.js";
9
+ import StatusBadge from "../status-badge.svelte";
10
+ import { gutterClass } from "./gutter.js";
11
+
12
+ // =========================
13
+ // PROPS
14
+ // =========================
15
+ interface Props {
16
+ title: string;
17
+ /** A mark drawn before the title: a logo, an avatar. */
18
+ logo?: Snippet;
19
+ /** The stage, in the agency's own word and the tone of its category. */
20
+ stage?: { label: string; tone: Tone; detail?: string | null } | null;
21
+ /** What this record sits under, such as the client of a project. */
22
+ parent?: { label: string; href: string } | null;
23
+ /** Quiet facts after the stage: a value, dates. */
24
+ facts?: string[];
25
+ actions?: Snippet;
26
+ /** Under the title row, for a stat strip. */
27
+ children?: Snippet;
28
+ class?: string;
29
+ }
30
+ let { title, logo, stage, parent, facts = [], actions, children, class: className }: Props = $props();
31
+ </script>
32
+
33
+ <!-- =========================== -->
34
+ <!-- MARKUP -->
35
+ <!-- =========================== -->
36
+ <header class={cn("flex flex-col gap-2 border-b border-border py-3", gutterClass, className)}>
37
+ {#if parent}
38
+ <a
39
+ href={parent.href}
40
+ class="-ml-1 inline-flex min-h-6 w-fit max-w-full items-center gap-0.5 rounded-sm text-sm text-muted-foreground transition-colors hover:text-foreground any-pointer-coarse:min-h-11"
41
+ >
42
+ <CaretLeft class="size-3.5 shrink-0" aria-hidden="true" />
43
+ <span class="truncate">{parent.label}</span>
44
+ </a>
45
+ {/if}
46
+ <div class="flex items-start justify-between gap-3">
47
+ <div class="flex min-w-0 items-center gap-3">
48
+ {#if logo}<span class="shrink-0">{@render logo()}</span>{/if}
49
+ <div class="min-w-0">
50
+ <h1 class="truncate text-lg leading-tight font-semibold tracking-tight">{title}</h1>
51
+ {#if stage || facts.length > 0}
52
+ <p class="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-muted-foreground">
53
+ {#if stage}
54
+ <StatusBadge tone={stage.tone} dot capitalize={false}
55
+ >{stage.detail ? `${stage.label} · ${stage.detail}` : stage.label}</StatusBadge
56
+ >
57
+ {/if}
58
+ {#each facts as fact, i (i)}
59
+ <span class="tabular-nums">{fact}</span>
60
+ {/each}
61
+ </p>
62
+ {/if}
63
+ </div>
64
+ </div>
65
+ {#if actions}
66
+ <div class="flex shrink-0 items-center gap-1">{@render actions()}</div>
67
+ {/if}
68
+ </div>
69
+ {@render children?.()}
70
+ </header>
@@ -0,0 +1,138 @@
1
+ <script lang="ts" generics="T extends string = string">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import type { Snippet } from "svelte";
6
+ import { CaretRight } from "phosphor-svelte";
7
+ import { cn } from "../../lib/utils.js";
8
+ import { AnimatedTabs, type TabDef } from "../../components/segmented/index.js";
9
+ import * as Sheet from "../../shadcn/sheet/index.js";
10
+ import { Progress } from "../../shadcn/progress/index.js";
11
+ import { gutterClass } from "./gutter.js";
12
+ import { resolveLabels, type RecordLabels } from "./types.js";
13
+
14
+ // =========================
15
+ // PROPS
16
+ // =========================
17
+ interface Props {
18
+ header: Snippet;
19
+ /** Where we are, next up, stats: whatever sits between the header and the body. */
20
+ banner?: Snippet;
21
+ /** The tab row. Put each panel in a `Tabs.Content` from `@heyramzi/ui/shadcn/tabs`. */
22
+ tabs?: readonly TabDef<T>[];
23
+ tab?: T;
24
+ tabsLabel?: string;
25
+ /** The side rail. Docked at 48rem and wider, a sheet behind one row under that. */
26
+ rail?: Snippet;
27
+ /** The sheet's title, read out by screen readers. Defaults to the steps label. */
28
+ railTitle?: string;
29
+ /** Fills the narrow row's bar. Leave it out for a row with no progress. */
30
+ railProgress?: { done: number; total: number } | null;
31
+ railOpen?: boolean;
32
+ children: Snippet;
33
+ labels?: Partial<RecordLabels>;
34
+ class?: string;
35
+ }
36
+ let {
37
+ header,
38
+ banner,
39
+ tabs,
40
+ tab = $bindable(),
41
+ tabsLabel,
42
+ rail,
43
+ railTitle,
44
+ railProgress = null,
45
+ railOpen = $bindable(false),
46
+ children,
47
+ labels,
48
+ class: className,
49
+ }: Props = $props();
50
+
51
+ // =========================
52
+ // DERIVED
53
+ // =========================
54
+ let t = $derived(resolveLabels(labels));
55
+ let percent = $derived(
56
+ railProgress && railProgress.total > 0 ? (railProgress.done / railProgress.total) * 100 : 0,
57
+ );
58
+ </script>
59
+
60
+ <!-- =========================== -->
61
+ <!-- MARKUP -->
62
+ <!-- =========================== -->
63
+ <!-- WHY a container query: the record sits in a page with its own sidebar, so the
64
+ viewport says nothing about how much room it has. -->
65
+ <div class={cn("@container flex min-w-0 flex-col bg-background", className)}>
66
+ {@render header()}
67
+ {@render banner?.()}
68
+
69
+ {#if rail}
70
+ <button
71
+ type="button"
72
+ class={cn(
73
+ "t-press flex min-h-11 w-full items-center gap-3 border-b border-border py-2 text-left @3xl:hidden",
74
+ gutterClass,
75
+ )}
76
+ aria-haspopup="dialog"
77
+ aria-expanded={railOpen}
78
+ onclick={() => (railOpen = true)}
79
+ >
80
+ <span class="text-sm font-medium">{railTitle ?? t.steps}</span>
81
+ {#if railProgress}
82
+ <span class="text-xs tabular-nums text-muted-foreground">
83
+ {t.stepsCount(railProgress.done, railProgress.total)}
84
+ </span>
85
+ <Progress
86
+ value={percent}
87
+ max={100}
88
+ class="h-1 flex-1"
89
+ aria-label={t.stepsCount(railProgress.done, railProgress.total)}
90
+ />
91
+ {:else}
92
+ <span class="flex-1"></span>
93
+ {/if}
94
+ <CaretRight class="size-4 shrink-0 text-muted-foreground" aria-hidden="true" />
95
+ </button>
96
+ {/if}
97
+
98
+ <div class="flex min-w-0 flex-col @3xl:flex-row">
99
+ <div class="min-w-0 flex-1">
100
+ {#if tabs}
101
+ <AnimatedTabs
102
+ {tabs}
103
+ bind:value={tab}
104
+ variant="underline"
105
+ placement="section"
106
+ ariaLabel={tabsLabel}
107
+ listWrapperClass={gutterClass}
108
+ class="gap-0"
109
+ >
110
+ <div class={cn("py-4 @3xl:py-5", gutterClass)}>{@render children()}</div>
111
+ </AnimatedTabs>
112
+ {:else}
113
+ <div class={cn("py-4 @3xl:py-5", gutterClass)}>{@render children()}</div>
114
+ {/if}
115
+ </div>
116
+
117
+ {#if rail}
118
+ <aside
119
+ class="hidden w-72 shrink-0 self-start border-l border-border @3xl:sticky @3xl:top-0 @3xl:block @5xl:w-80"
120
+ >
121
+ <div class="p-4">{@render rail()}</div>
122
+ </aside>
123
+ {/if}
124
+ </div>
125
+ </div>
126
+
127
+ <!-- The rail is drawn twice, once docked and once here. Only one is ever on screen,
128
+ and the sheet mounts its copy on open. -->
129
+ {#if rail}
130
+ <Sheet.Root bind:open={railOpen}>
131
+ <Sheet.Content side="bottom" class="gap-0 p-0">
132
+ <Sheet.Header class="sr-only">
133
+ <Sheet.Title>{railTitle ?? t.steps}</Sheet.Title>
134
+ </Sheet.Header>
135
+ <div class="overflow-y-auto p-4 pt-6">{@render rail()}</div>
136
+ </Sheet.Content>
137
+ </Sheet.Root>
138
+ {/if}
@@ -0,0 +1,48 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import { cn } from "../../lib/utils.js";
6
+ import type { PanelStat } from "../side-panel-types.js";
7
+ import { gutterClass } from "./gutter.js";
8
+
9
+ // =========================
10
+ // PROPS
11
+ // =========================
12
+ interface Props {
13
+ stats: PanelStat[];
14
+ /** Sits flush with the record's gutter. Off inside a card. */
15
+ gutter?: boolean;
16
+ class?: string;
17
+ }
18
+ let { stats, gutter = true, class: className }: Props = $props();
19
+
20
+ // =========================
21
+ // DERIVED
22
+ // =========================
23
+ const TONE = {
24
+ default: "",
25
+ muted: "text-muted-foreground",
26
+ good: "text-success",
27
+ bad: "text-destructive",
28
+ } as const;
29
+ </script>
30
+
31
+ <!-- =========================== -->
32
+ <!-- MARKUP -->
33
+ <!-- =========================== -->
34
+ {#if stats.length > 0}
35
+ <dl class={cn("flex flex-wrap gap-x-6 gap-y-2", gutter && gutterClass, className)}>
36
+ {#each stats as stat (stat.label)}
37
+ <div class="min-w-0">
38
+ <dt class="text-xs text-muted-foreground">{stat.label}</dt>
39
+ <dd class={cn("text-sm font-medium tabular-nums", TONE[stat.tone ?? "default"])}>
40
+ {stat.value}
41
+ {#if stat.sub}
42
+ <span class="text-xs font-normal text-muted-foreground">{stat.sub}</span>
43
+ {/if}
44
+ </dd>
45
+ </div>
46
+ {/each}
47
+ </dl>
48
+ {/if}