@heyramzi/ui 0.0.0-stage → 1.53.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 (564) hide show
  1. package/COMPONENTS.md +133 -0
  2. package/README.md +136 -2
  3. package/package.json +217 -3
  4. package/scripts/lib/sync-core.mjs +113 -0
  5. package/scripts/relative-imports.mjs +39 -0
  6. package/scripts/sf-symbol.mjs +93 -0
  7. package/scripts/sync-shadcn.mjs +354 -0
  8. package/scripts/sync-transitions.mjs +146 -0
  9. package/scripts/validate-apple-symbol-exports.mjs +45 -0
  10. package/src/agents/agent-avatar.svelte +590 -0
  11. package/src/agents/agent-cast.svelte +197 -0
  12. package/src/agents/demo.svelte +125 -0
  13. package/src/agents/helpers.ts +58 -0
  14. package/src/agents/index.ts +11 -0
  15. package/src/agents/page-state.ts +70 -0
  16. package/src/agents/types.ts +74 -0
  17. package/src/agents/vendor/LICENSE +21 -0
  18. package/src/agents/vendor/color.ts +155 -0
  19. package/src/agents/vendor/draw.ts +840 -0
  20. package/src/agents/vendor/engine.ts +845 -0
  21. package/src/agents/vendor/mesh.ts +838 -0
  22. package/src/agents/vendor/plastic.ts +4958 -0
  23. package/src/agents/vendor/presets.ts +103 -0
  24. package/src/agents/vendor/shapes.ts +41 -0
  25. package/src/agents/vendor/ticker.ts +67 -0
  26. package/src/agents/vendor/types.ts +261 -0
  27. package/src/agents/vendor/wear.ts +1699 -0
  28. package/src/ai/ai-button/AiButton.svelte +86 -0
  29. package/src/ai/ai-button/index.ts +4 -0
  30. package/src/ai/confirmation/Confirmation.svelte +54 -0
  31. package/src/ai/confirmation/ConfirmationAccepted.svelte +23 -0
  32. package/src/ai/confirmation/ConfirmationAction.svelte +53 -0
  33. package/src/ai/confirmation/ConfirmationActions.svelte +24 -0
  34. package/src/ai/confirmation/ConfirmationFailed.svelte +23 -0
  35. package/src/ai/confirmation/ConfirmationRejected.svelte +23 -0
  36. package/src/ai/confirmation/ConfirmationRequest.svelte +25 -0
  37. package/src/ai/confirmation/ConfirmationTitle.svelte +25 -0
  38. package/src/ai/confirmation/confirmation-context.svelte.ts +22 -0
  39. package/src/ai/confirmation/index.ts +50 -0
  40. package/src/ai/generation-progress/GenerationProgress.svelte +354 -0
  41. package/src/ai/generation-progress/index.ts +7 -0
  42. package/src/ai/index.ts +111 -0
  43. package/src/ai/loading-state/PixelLoader.svelte +138 -0
  44. package/src/ai/loading-state/index.ts +5 -0
  45. package/src/ai/prompt-input/PromptInput.svelte +56 -0
  46. package/src/ai/prompt-input/PromptInputBody.svelte +19 -0
  47. package/src/ai/prompt-input/PromptInputSubmit.svelte +79 -0
  48. package/src/ai/prompt-input/PromptInputTextarea.svelte +74 -0
  49. package/src/ai/prompt-input/PromptInputToolbar.svelte +19 -0
  50. package/src/ai/prompt-input/index.ts +34 -0
  51. package/src/ai/reasoning/Reasoning.svelte +50 -0
  52. package/src/ai/reasoning/ReasoningContent.svelte +27 -0
  53. package/src/ai/reasoning/ReasoningTrigger.svelte +48 -0
  54. package/src/ai/reasoning/index.ts +22 -0
  55. package/src/ai/reasoning/reasoning-context.svelte.ts +20 -0
  56. package/src/ai/response/Response.css +32 -0
  57. package/src/ai/response/Response.svelte +130 -0
  58. package/src/ai/response/index.ts +5 -0
  59. package/src/ai/suggestion/Suggestion.svelte +43 -0
  60. package/src/ai/suggestion/Suggestions.svelte +25 -0
  61. package/src/ai/suggestion/index.ts +14 -0
  62. package/src/ai/thinking-dots/ThinkingDots.svelte +42 -0
  63. package/src/ai/thinking-dots/index.ts +5 -0
  64. package/src/ai/typing-dots/TypingDots.css +39 -0
  65. package/src/ai/typing-dots/TypingDots.svelte +27 -0
  66. package/src/ai/typing-dots/index.ts +10 -0
  67. package/src/ai/voice-waveform/VoiceWaveform.css +40 -0
  68. package/src/ai/voice-waveform/VoiceWaveform.svelte +46 -0
  69. package/src/ai/voice-waveform/index.ts +5 -0
  70. package/src/ambient.d.ts +4 -0
  71. package/src/animations/README.md +84 -0
  72. package/src/animations/avatar-group-hover.css +47 -0
  73. package/src/animations/card-tilt.css +74 -0
  74. package/src/animations/digit-pop-in.css +78 -0
  75. package/src/animations/dropdown.css +121 -0
  76. package/src/animations/index.css +23 -0
  77. package/src/animations/input-clear-dissolve.css +86 -0
  78. package/src/animations/input-shake.css +114 -0
  79. package/src/animations/learn-more-hover.css +48 -0
  80. package/src/animations/like-button.css +93 -0
  81. package/src/animations/modal.css +75 -0
  82. package/src/animations/page-side-by-side.css +63 -0
  83. package/src/animations/plus-menu-morph.css +97 -0
  84. package/src/animations/resize.css +20 -0
  85. package/src/animations/shimmer-text.css +72 -0
  86. package/src/animations/spinning-counter.css +52 -0
  87. package/src/animations/success-check.css +120 -0
  88. package/src/animations/text-swap.css +40 -0
  89. package/src/animations/text-swap.ts +40 -0
  90. package/src/animations/thinking-states.css +65 -0
  91. package/src/animations/upstream-coverage.test.ts +65 -0
  92. package/src/components/copy/clipboard.test.ts +206 -0
  93. package/src/components/copy/clipboard.ts +49 -0
  94. package/src/components/copy/copied.svelte.ts +39 -0
  95. package/src/components/copy/copy-button.svelte +175 -0
  96. package/src/components/copy/copy-button.test.ts +112 -0
  97. package/src/components/copy/index.ts +3 -0
  98. package/src/components/data-table/data-table.test.ts +134 -0
  99. package/src/components/data-table/data-table.ts +77 -0
  100. package/src/components/data-table/index.ts +20 -0
  101. package/src/components/data-table/infinite-scroll.svelte +69 -0
  102. package/src/components/data-table/pagination.svelte +62 -0
  103. package/src/components/data-table/row-arrow.svelte +28 -0
  104. package/src/components/data-table/row-context-menu.svelte +106 -0
  105. package/src/components/data-table/row-context-menu.test.ts +18 -0
  106. package/src/components/data-table/sortable-head.svelte +56 -0
  107. package/src/components/data-table/table-skeleton.svelte +83 -0
  108. package/src/components/hold-to-confirm/hold-to-confirm.svelte +232 -0
  109. package/src/components/hold-to-confirm/index.ts +1 -0
  110. package/src/components/icon/icon.svelte +23 -0
  111. package/src/components/icon/index.ts +1 -0
  112. package/src/components/language-flag/index.ts +1 -0
  113. package/src/components/language-flag/language-flag.svelte +60 -0
  114. package/src/components/list-page/detail-empty.svelte +50 -0
  115. package/src/components/list-page/detail-header.svelte +107 -0
  116. package/src/components/list-page/detail-panel.svelte +90 -0
  117. package/src/components/list-page/detail-row.svelte +36 -0
  118. package/src/components/list-page/detail-stack.svelte +33 -0
  119. package/src/components/list-page/empty-state.svelte +91 -0
  120. package/src/components/list-page/filter-item.svelte +49 -0
  121. package/src/components/list-page/filter-popover.svelte +88 -0
  122. package/src/components/list-page/filter-section.ts +12 -0
  123. package/src/components/list-page/grouped-list.svelte +116 -0
  124. package/src/components/list-page/index.ts +28 -0
  125. package/src/components/list-page/list-page-shell.svelte +195 -0
  126. package/src/components/list-page/list-table.ts +19 -0
  127. package/src/components/list-page/master-detail.svelte +206 -0
  128. package/src/components/list-page/master-detail.test.ts +37 -0
  129. package/src/components/list-page/notice-bar.svelte +68 -0
  130. package/src/components/list-page/prev-next.svelte +104 -0
  131. package/src/components/list-page/queue-rail.ts +25 -0
  132. package/src/components/list-page/rail-body.svelte +29 -0
  133. package/src/components/list-page/rail-empty.svelte +51 -0
  134. package/src/components/list-page/rail-head.svelte +49 -0
  135. package/src/components/list-page/rail-row.svelte +163 -0
  136. package/src/components/list-page/section-host.ts +24 -0
  137. package/src/components/list-page/section-label.svelte +39 -0
  138. package/src/components/list-page/step.test.ts +21 -0
  139. package/src/components/list-page/step.ts +10 -0
  140. package/src/components/list-page/toolbar-search.svelte +63 -0
  141. package/src/components/list-page/toolbar-trigger.ts +23 -0
  142. package/src/components/list-page/types.ts +1 -0
  143. package/src/components/locale-switch/index.ts +1 -0
  144. package/src/components/locale-switch/locale-switch.svelte +59 -0
  145. package/src/components/mark/index.ts +1 -0
  146. package/src/components/mark/mark.svelte +70 -0
  147. package/src/components/open-in/index.ts +6 -0
  148. package/src/components/open-in/open-in.svelte +106 -0
  149. package/src/components/picker/index.ts +4 -0
  150. package/src/components/picker/mark-picker.svelte +84 -0
  151. package/src/components/picker/picker.svelte +143 -0
  152. package/src/components/picker/trigger.ts +42 -0
  153. package/src/components/picker/types.ts +26 -0
  154. package/src/components/segmented/animated-tabs.svelte +175 -0
  155. package/src/components/segmented/collapse.test.ts +49 -0
  156. package/src/components/segmented/collapse.ts +39 -0
  157. package/src/components/segmented/index.ts +41 -0
  158. package/src/components/segmented/section-runs.svelte +79 -0
  159. package/src/components/segmented/segment.svelte +106 -0
  160. package/src/components/segmented/segmented-filter.svelte +125 -0
  161. package/src/components/segmented/segmented-nav.svelte +82 -0
  162. package/src/components/segmented/segmented-overflow.svelte +85 -0
  163. package/src/components/segmented/segmented-track.svelte +186 -0
  164. package/src/components/segmented/segmented.test.ts +20 -0
  165. package/src/components/segmented/segmented.ts +80 -0
  166. package/src/components/star-rating-input/index.ts +9 -0
  167. package/src/components/star-rating-input/star-rating-input.svelte +263 -0
  168. package/src/components/svg/index.ts +1 -0
  169. package/src/components/svg/svg.svelte +9 -0
  170. package/src/design/index.css +270 -0
  171. package/src/design/polish.css +159 -0
  172. package/src/effects/animated-beam/AnimatedBeam.svelte +132 -0
  173. package/src/effects/animated-beam/diagrams/BeamSvg.svelte +91 -0
  174. package/src/effects/animated-beam/index.ts +2 -0
  175. package/src/effects/animated-beam/path-calculator.svelte.ts +70 -0
  176. package/src/effects/animated-beam/types.ts +23 -0
  177. package/src/effects/border-beam/BorderBeam.svelte +223 -0
  178. package/src/effects/border-beam/css.test.ts +46 -0
  179. package/src/effects/border-beam/css.ts +574 -0
  180. package/src/effects/border-beam/presets.ts +699 -0
  181. package/src/effects/border-beam/register-properties.test.ts +74 -0
  182. package/src/effects/border-beam/register-properties.ts +73 -0
  183. package/src/effects/border-beam/types.ts +38 -0
  184. package/src/effects/image-comparison/ImageComparison.css +100 -0
  185. package/src/effects/image-comparison/ImageComparison.svelte +209 -0
  186. package/src/effects/image-comparison/compare.test.ts +17 -0
  187. package/src/effects/image-comparison/compare.ts +5 -0
  188. package/src/effects/image-reveal/ImageReveal.css +10 -0
  189. package/src/effects/image-reveal/ImageReveal.svelte +357 -0
  190. package/src/effects/image-reveal/index.ts +2 -0
  191. package/src/effects/image-reveal/types.ts +35 -0
  192. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +338 -0
  193. package/src/effects/magic-card/MagicCard.css +47 -0
  194. package/src/effects/magic-card/MagicCard.svelte +57 -0
  195. package/src/effects/magnetic/magnetic.ts +81 -0
  196. package/src/effects/marquee/Marquee.css +78 -0
  197. package/src/effects/marquee/Marquee.svelte +160 -0
  198. package/src/effects/progressive-blur/ProgressiveBlur.css +9 -0
  199. package/src/effects/progressive-blur/ProgressiveBlur.svelte +63 -0
  200. package/src/effects/reveal/AnimatedGroup.svelte +101 -0
  201. package/src/effects/reveal/InView.svelte +77 -0
  202. package/src/effects/reveal/TextEffect.svelte +142 -0
  203. package/src/effects/reveal/index.ts +11 -0
  204. package/src/effects/reveal/observe.test.ts +60 -0
  205. package/src/effects/reveal/observe.ts +65 -0
  206. package/src/effects/reveal/reveal.css +141 -0
  207. package/src/effects/reveal/types.ts +32 -0
  208. package/src/effects/shader-gradient/ShaderGradient.svelte +227 -0
  209. package/src/effects/shader-gradient/color.ts +44 -0
  210. package/src/effects/shader-gradient/index.ts +5 -0
  211. package/src/effects/shader-gradient/renderer.ts +456 -0
  212. package/src/effects/shader-gradient/shaders.ts +324 -0
  213. package/src/effects/shader-gradient/types.ts +68 -0
  214. package/src/effects/spotlight/Spotlight.css +14 -0
  215. package/src/effects/spotlight/Spotlight.svelte +61 -0
  216. package/src/effects/theme-toggle/ThemeToggle.css +214 -0
  217. package/src/effects/theme-toggle/ThemeToggle.svelte +126 -0
  218. package/src/gallery.test.ts +52 -0
  219. package/src/icons/TradeIcon.svelte +16 -0
  220. package/src/icons/apple-symbol-exports.ts +135 -0
  221. package/src/icons/sf-symbols.ts +527 -0
  222. package/src/icons/trades.ts +89 -0
  223. package/src/index.ts +53 -0
  224. package/src/lib/arrive.ts +26 -0
  225. package/src/lib/icon-types.ts +17 -0
  226. package/src/lib/layout.ts +8 -0
  227. package/src/lib/panel.ts +21 -0
  228. package/src/lib/sliding-pill.ts +68 -0
  229. package/src/lib/theme-transition.ts +79 -0
  230. package/src/lib/tone.ts +48 -0
  231. package/src/shadcn/accordion/accordion-content.svelte +22 -0
  232. package/src/shadcn/accordion/accordion-item.svelte +17 -0
  233. package/src/shadcn/accordion/accordion-trigger.svelte +44 -0
  234. package/src/shadcn/accordion/accordion.svelte +19 -0
  235. package/src/shadcn/accordion/index.ts +16 -0
  236. package/src/shadcn/alert/alert-description.svelte +23 -0
  237. package/src/shadcn/alert/alert-title.svelte +20 -0
  238. package/src/shadcn/alert/alert.svelte +46 -0
  239. package/src/shadcn/alert/index.ts +14 -0
  240. package/src/shadcn/alert-dialog/alert-dialog-action.svelte +18 -0
  241. package/src/shadcn/alert-dialog/alert-dialog-cancel.svelte +18 -0
  242. package/src/shadcn/alert-dialog/alert-dialog-content.svelte +29 -0
  243. package/src/shadcn/alert-dialog/alert-dialog-description.svelte +17 -0
  244. package/src/shadcn/alert-dialog/alert-dialog-footer.svelte +20 -0
  245. package/src/shadcn/alert-dialog/alert-dialog-header.svelte +20 -0
  246. package/src/shadcn/alert-dialog/alert-dialog-overlay.svelte +17 -0
  247. package/src/shadcn/alert-dialog/alert-dialog-portal.svelte +7 -0
  248. package/src/shadcn/alert-dialog/alert-dialog-title.svelte +17 -0
  249. package/src/shadcn/alert-dialog/alert-dialog-trigger.svelte +7 -0
  250. package/src/shadcn/alert-dialog/alert-dialog.svelte +7 -0
  251. package/src/shadcn/alert-dialog/index.ts +37 -0
  252. package/src/shadcn/attachment/attachment-action.svelte +23 -0
  253. package/src/shadcn/attachment/attachment-actions.svelte +23 -0
  254. package/src/shadcn/attachment/attachment-content.svelte +23 -0
  255. package/src/shadcn/attachment/attachment-description.svelte +23 -0
  256. package/src/shadcn/attachment/attachment-group.svelte +23 -0
  257. package/src/shadcn/attachment/attachment-media.svelte +44 -0
  258. package/src/shadcn/attachment/attachment-title.svelte +23 -0
  259. package/src/shadcn/attachment/attachment-trigger.svelte +29 -0
  260. package/src/shadcn/attachment/attachment.svelte +55 -0
  261. package/src/shadcn/attachment/index.ts +40 -0
  262. package/src/shadcn/avatar/avatar-badge.svelte +26 -0
  263. package/src/shadcn/avatar/avatar-fallback.svelte +20 -0
  264. package/src/shadcn/avatar/avatar-group-count.svelte +23 -0
  265. package/src/shadcn/avatar/avatar-group.svelte +23 -0
  266. package/src/shadcn/avatar/avatar-image.svelte +17 -0
  267. package/src/shadcn/avatar/avatar.svelte +26 -0
  268. package/src/shadcn/avatar/index.ts +22 -0
  269. package/src/shadcn/badge/badge.svelte +54 -0
  270. package/src/shadcn/badge/index.ts +2 -0
  271. package/src/shadcn/breadcrumb/breadcrumb-ellipsis.svelte +23 -0
  272. package/src/shadcn/breadcrumb/breadcrumb-item.svelte +20 -0
  273. package/src/shadcn/breadcrumb/breadcrumb-link.svelte +31 -0
  274. package/src/shadcn/breadcrumb/breadcrumb-list.svelte +23 -0
  275. package/src/shadcn/breadcrumb/breadcrumb-page.svelte +23 -0
  276. package/src/shadcn/breadcrumb/breadcrumb-separator.svelte +27 -0
  277. package/src/shadcn/breadcrumb/breadcrumb.svelte +21 -0
  278. package/src/shadcn/breadcrumb/index.ts +25 -0
  279. package/src/shadcn/bubble/bubble-content.svelte +31 -0
  280. package/src/shadcn/bubble/bubble-group.svelte +20 -0
  281. package/src/shadcn/bubble/bubble-reactions.svelte +52 -0
  282. package/src/shadcn/bubble/bubble.svelte +58 -0
  283. package/src/shadcn/bubble/index.ts +25 -0
  284. package/src/shadcn/button/button.svelte +121 -0
  285. package/src/shadcn/button/index.ts +17 -0
  286. package/src/shadcn/calendar/calendar-caption.svelte +76 -0
  287. package/src/shadcn/calendar/calendar-cell.svelte +19 -0
  288. package/src/shadcn/calendar/calendar-day.svelte +35 -0
  289. package/src/shadcn/calendar/calendar-grid-body.svelte +12 -0
  290. package/src/shadcn/calendar/calendar-grid-head.svelte +12 -0
  291. package/src/shadcn/calendar/calendar-grid-row.svelte +12 -0
  292. package/src/shadcn/calendar/calendar-grid.svelte +16 -0
  293. package/src/shadcn/calendar/calendar-head-cell.svelte +19 -0
  294. package/src/shadcn/calendar/calendar-header.svelte +19 -0
  295. package/src/shadcn/calendar/calendar-heading.svelte +16 -0
  296. package/src/shadcn/calendar/calendar-month-select.svelte +44 -0
  297. package/src/shadcn/calendar/calendar-month.svelte +15 -0
  298. package/src/shadcn/calendar/calendar-months.svelte +19 -0
  299. package/src/shadcn/calendar/calendar-nav.svelte +19 -0
  300. package/src/shadcn/calendar/calendar-next-button.svelte +31 -0
  301. package/src/shadcn/calendar/calendar-prev-button.svelte +31 -0
  302. package/src/shadcn/calendar/calendar-year-select.svelte +43 -0
  303. package/src/shadcn/calendar/calendar.svelte +124 -0
  304. package/src/shadcn/calendar/index.ts +40 -0
  305. package/src/shadcn/card/card-action.svelte +23 -0
  306. package/src/shadcn/card/card-content.svelte +20 -0
  307. package/src/shadcn/card/card-description.svelte +20 -0
  308. package/src/shadcn/card/card-footer.svelte +23 -0
  309. package/src/shadcn/card/card-header.svelte +23 -0
  310. package/src/shadcn/card/card-title.svelte +29 -0
  311. package/src/shadcn/card/card.svelte +25 -0
  312. package/src/shadcn/card/index.ts +25 -0
  313. package/src/shadcn/checkbox/checkbox.svelte +38 -0
  314. package/src/shadcn/checkbox/index.ts +6 -0
  315. package/src/shadcn/code-block/code-block-actions.svelte +23 -0
  316. package/src/shadcn/code-block/code-block-content.svelte +55 -0
  317. package/src/shadcn/code-block/code-block-copy-button.svelte +10 -0
  318. package/src/shadcn/code-block/code-block-footer.svelte +20 -0
  319. package/src/shadcn/code-block/code-block-header.svelte +23 -0
  320. package/src/shadcn/code-block/code-block-line.svelte +71 -0
  321. package/src/shadcn/code-block/code-block-title.svelte +23 -0
  322. package/src/shadcn/code-block/code-block.svelte +47 -0
  323. package/src/shadcn/code-block/index.ts +42 -0
  324. package/src/shadcn/collapsible/collapsible-content.svelte +7 -0
  325. package/src/shadcn/collapsible/collapsible-trigger.svelte +7 -0
  326. package/src/shadcn/collapsible/collapsible.svelte +11 -0
  327. package/src/shadcn/collapsible/index.ts +13 -0
  328. package/src/shadcn/command/command-dialog.svelte +66 -0
  329. package/src/shadcn/command/command-empty.svelte +17 -0
  330. package/src/shadcn/command/command-group.svelte +40 -0
  331. package/src/shadcn/command/command-input.svelte +64 -0
  332. package/src/shadcn/command/command-item.svelte +37 -0
  333. package/src/shadcn/command/command-link-item.svelte +20 -0
  334. package/src/shadcn/command/command-list.svelte +20 -0
  335. package/src/shadcn/command/command-loading.svelte +7 -0
  336. package/src/shadcn/command/command-separator.svelte +17 -0
  337. package/src/shadcn/command/command-shortcut.svelte +23 -0
  338. package/src/shadcn/command/command.svelte +28 -0
  339. package/src/shadcn/command/index.ts +40 -0
  340. package/src/shadcn/context-menu/context-menu-checkbox-item.svelte +41 -0
  341. package/src/shadcn/context-menu/context-menu-content.svelte +28 -0
  342. package/src/shadcn/context-menu/context-menu-group-heading.svelte +21 -0
  343. package/src/shadcn/context-menu/context-menu-group.svelte +7 -0
  344. package/src/shadcn/context-menu/context-menu-item.svelte +27 -0
  345. package/src/shadcn/context-menu/context-menu-label.svelte +27 -0
  346. package/src/shadcn/context-menu/context-menu-portal.svelte +7 -0
  347. package/src/shadcn/context-menu/context-menu-radio-group.svelte +16 -0
  348. package/src/shadcn/context-menu/context-menu-radio-item.svelte +35 -0
  349. package/src/shadcn/context-menu/context-menu-separator.svelte +17 -0
  350. package/src/shadcn/context-menu/context-menu-shortcut.svelte +23 -0
  351. package/src/shadcn/context-menu/context-menu-sub-content.svelte +25 -0
  352. package/src/shadcn/context-menu/context-menu-sub-trigger.svelte +29 -0
  353. package/src/shadcn/context-menu/context-menu-sub.svelte +7 -0
  354. package/src/shadcn/context-menu/context-menu-trigger.svelte +17 -0
  355. package/src/shadcn/context-menu/context-menu.svelte +7 -0
  356. package/src/shadcn/context-menu/index.ts +52 -0
  357. package/src/shadcn/dialog/dialog-close.svelte +11 -0
  358. package/src/shadcn/dialog/dialog-content.svelte +57 -0
  359. package/src/shadcn/dialog/dialog-description.svelte +20 -0
  360. package/src/shadcn/dialog/dialog-footer.svelte +32 -0
  361. package/src/shadcn/dialog/dialog-header.svelte +20 -0
  362. package/src/shadcn/dialog/dialog-overlay.svelte +20 -0
  363. package/src/shadcn/dialog/dialog-portal.svelte +7 -0
  364. package/src/shadcn/dialog/dialog-title.svelte +17 -0
  365. package/src/shadcn/dialog/dialog-trigger.svelte +11 -0
  366. package/src/shadcn/dialog/dialog.svelte +7 -0
  367. package/src/shadcn/dialog/index.ts +34 -0
  368. package/src/shadcn/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
  369. package/src/shadcn/dropdown-menu/dropdown-menu-checkbox-item.svelte +43 -0
  370. package/src/shadcn/dropdown-menu/dropdown-menu-content.svelte +31 -0
  371. package/src/shadcn/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  372. package/src/shadcn/dropdown-menu/dropdown-menu-group.svelte +7 -0
  373. package/src/shadcn/dropdown-menu/dropdown-menu-item.svelte +29 -0
  374. package/src/shadcn/dropdown-menu/dropdown-menu-label.svelte +27 -0
  375. package/src/shadcn/dropdown-menu/dropdown-menu-portal.svelte +7 -0
  376. package/src/shadcn/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
  377. package/src/shadcn/dropdown-menu/dropdown-menu-radio-item.svelte +34 -0
  378. package/src/shadcn/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  379. package/src/shadcn/dropdown-menu/dropdown-menu-shortcut.svelte +23 -0
  380. package/src/shadcn/dropdown-menu/dropdown-menu-sub-content.svelte +20 -0
  381. package/src/shadcn/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  382. package/src/shadcn/dropdown-menu/dropdown-menu-sub.svelte +7 -0
  383. package/src/shadcn/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  384. package/src/shadcn/dropdown-menu/dropdown-menu.svelte +7 -0
  385. package/src/shadcn/dropdown-menu/index.ts +56 -0
  386. package/src/shadcn/dropdown-menu/menu-item.ts +43 -0
  387. package/src/shadcn/empty/empty-content.svelte +23 -0
  388. package/src/shadcn/empty/empty-description.svelte +23 -0
  389. package/src/shadcn/empty/empty-header.svelte +20 -0
  390. package/src/shadcn/empty/empty-media.svelte +41 -0
  391. package/src/shadcn/empty/empty-title.svelte +20 -0
  392. package/src/shadcn/empty/empty.svelte +23 -0
  393. package/src/shadcn/empty/index.ts +22 -0
  394. package/src/shadcn/field/field-content.svelte +20 -0
  395. package/src/shadcn/field/field-description.svelte +25 -0
  396. package/src/shadcn/field/field-error.svelte +58 -0
  397. package/src/shadcn/field/field-group.svelte +23 -0
  398. package/src/shadcn/field/field-label.svelte +26 -0
  399. package/src/shadcn/field/field-legend.svelte +29 -0
  400. package/src/shadcn/field/field-separator.svelte +35 -0
  401. package/src/shadcn/field/field-set.svelte +24 -0
  402. package/src/shadcn/field/field-title.svelte +23 -0
  403. package/src/shadcn/field/field.svelte +53 -0
  404. package/src/shadcn/field/index.ts +36 -0
  405. package/src/shadcn/field/simple-field.svelte +71 -0
  406. package/src/shadcn/field/simple-field.test.ts +47 -0
  407. package/src/shadcn/hooks/highlighter.ts +132 -0
  408. package/src/shadcn/hooks/is-mobile.svelte.ts +9 -0
  409. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +98 -0
  410. package/src/shadcn/hover-card/hover-card-content.svelte +31 -0
  411. package/src/shadcn/hover-card/hover-card-portal.svelte +7 -0
  412. package/src/shadcn/hover-card/hover-card-trigger.svelte +7 -0
  413. package/src/shadcn/hover-card/hover-card.svelte +7 -0
  414. package/src/shadcn/hover-card/index.ts +15 -0
  415. package/src/shadcn/input/index.ts +9 -0
  416. package/src/shadcn/input/input.svelte +48 -0
  417. package/src/shadcn/input/secret-input.svelte +75 -0
  418. package/src/shadcn/input-otp/index.ts +15 -0
  419. package/src/shadcn/input-otp/input-otp-group.svelte +20 -0
  420. package/src/shadcn/input-otp/input-otp-separator.svelte +25 -0
  421. package/src/shadcn/input-otp/input-otp-slot.svelte +26 -0
  422. package/src/shadcn/input-otp/input-otp.svelte +22 -0
  423. package/src/shadcn/kbd/index.ts +10 -0
  424. package/src/shadcn/kbd/kbd-group.svelte +20 -0
  425. package/src/shadcn/kbd/kbd.svelte +23 -0
  426. package/src/shadcn/label/index.ts +7 -0
  427. package/src/shadcn/label/label.svelte +20 -0
  428. package/src/shadcn/marker/index.ts +15 -0
  429. package/src/shadcn/marker/marker-content.svelte +23 -0
  430. package/src/shadcn/marker/marker-icon.svelte +21 -0
  431. package/src/shadcn/marker/marker.svelte +52 -0
  432. package/src/shadcn/message/index.ts +22 -0
  433. package/src/shadcn/message/message-avatar.svelte +23 -0
  434. package/src/shadcn/message/message-content.svelte +23 -0
  435. package/src/shadcn/message/message-footer.svelte +23 -0
  436. package/src/shadcn/message/message-group.svelte +20 -0
  437. package/src/shadcn/message/message-header.svelte +23 -0
  438. package/src/shadcn/message/message.svelte +27 -0
  439. package/src/shadcn/message-scroller/index.ts +28 -0
  440. package/src/shadcn/message-scroller/message-scroller-button.svelte +52 -0
  441. package/src/shadcn/message-scroller/message-scroller-content.svelte +17 -0
  442. package/src/shadcn/message-scroller/message-scroller-item.svelte +22 -0
  443. package/src/shadcn/message-scroller/message-scroller-provider.svelte +7 -0
  444. package/src/shadcn/message-scroller/message-scroller-viewport.svelte +20 -0
  445. package/src/shadcn/message-scroller/message-scroller.svelte +20 -0
  446. package/src/shadcn/popover/index.ts +28 -0
  447. package/src/shadcn/popover/popover-close.svelte +7 -0
  448. package/src/shadcn/popover/popover-content.svelte +38 -0
  449. package/src/shadcn/popover/popover-description.svelte +20 -0
  450. package/src/shadcn/popover/popover-header.svelte +20 -0
  451. package/src/shadcn/popover/popover-portal.svelte +7 -0
  452. package/src/shadcn/popover/popover-title.svelte +15 -0
  453. package/src/shadcn/popover/popover-trigger.svelte +17 -0
  454. package/src/shadcn/popover/popover.svelte +7 -0
  455. package/src/shadcn/progress/index.ts +7 -0
  456. package/src/shadcn/progress/progress.svelte +30 -0
  457. package/src/shadcn/radio-group/index.ts +10 -0
  458. package/src/shadcn/radio-group/radio-group-item.svelte +32 -0
  459. package/src/shadcn/radio-group/radio-group.svelte +19 -0
  460. package/src/shadcn/scroll-area/index.ts +10 -0
  461. package/src/shadcn/scroll-area/scroll-area-scrollbar.svelte +30 -0
  462. package/src/shadcn/scroll-area/scroll-area.svelte +43 -0
  463. package/src/shadcn/select/index.ts +37 -0
  464. package/src/shadcn/select/select-content.svelte +45 -0
  465. package/src/shadcn/select/select-group-heading.svelte +21 -0
  466. package/src/shadcn/select/select-group.svelte +17 -0
  467. package/src/shadcn/select/select-item.svelte +38 -0
  468. package/src/shadcn/select/select-label.svelte +20 -0
  469. package/src/shadcn/select/select-portal.svelte +7 -0
  470. package/src/shadcn/select/select-scroll-down-button.svelte +23 -0
  471. package/src/shadcn/select/select-scroll-up-button.svelte +23 -0
  472. package/src/shadcn/select/select-separator.svelte +18 -0
  473. package/src/shadcn/select/select-trigger.svelte +33 -0
  474. package/src/shadcn/select/select.svelte +11 -0
  475. package/src/shadcn/separator/index.ts +7 -0
  476. package/src/shadcn/separator/separator.svelte +23 -0
  477. package/src/shadcn/sheet/index.ts +34 -0
  478. package/src/shadcn/sheet/sheet-close.svelte +7 -0
  479. package/src/shadcn/sheet/sheet-content.svelte +73 -0
  480. package/src/shadcn/sheet/sheet-description.svelte +17 -0
  481. package/src/shadcn/sheet/sheet-footer.svelte +20 -0
  482. package/src/shadcn/sheet/sheet-header.svelte +20 -0
  483. package/src/shadcn/sheet/sheet-overlay.svelte +20 -0
  484. package/src/shadcn/sheet/sheet-portal.svelte +7 -0
  485. package/src/shadcn/sheet/sheet-title.svelte +17 -0
  486. package/src/shadcn/sheet/sheet-trigger.svelte +7 -0
  487. package/src/shadcn/sheet/sheet.css +48 -0
  488. package/src/shadcn/sheet/sheet.svelte +7 -0
  489. package/src/shadcn/sidebar/constants.ts +6 -0
  490. package/src/shadcn/sidebar/context.svelte.ts +81 -0
  491. package/src/shadcn/sidebar/index.ts +75 -0
  492. package/src/shadcn/sidebar/sidebar-content.svelte +24 -0
  493. package/src/shadcn/sidebar/sidebar-footer.svelte +21 -0
  494. package/src/shadcn/sidebar/sidebar-group-action.svelte +33 -0
  495. package/src/shadcn/sidebar/sidebar-group-content.svelte +21 -0
  496. package/src/shadcn/sidebar/sidebar-group-label.svelte +33 -0
  497. package/src/shadcn/sidebar/sidebar-group.svelte +21 -0
  498. package/src/shadcn/sidebar/sidebar-header.svelte +21 -0
  499. package/src/shadcn/sidebar/sidebar-input.svelte +21 -0
  500. package/src/shadcn/sidebar/sidebar-inset.svelte +23 -0
  501. package/src/shadcn/sidebar/sidebar-menu-action.svelte +37 -0
  502. package/src/shadcn/sidebar/sidebar-menu-badge.svelte +24 -0
  503. package/src/shadcn/sidebar/sidebar-menu-button.svelte +103 -0
  504. package/src/shadcn/sidebar/sidebar-menu-item.svelte +21 -0
  505. package/src/shadcn/sidebar/sidebar-menu-skeleton.svelte +36 -0
  506. package/src/shadcn/sidebar/sidebar-menu-sub-button.svelte +39 -0
  507. package/src/shadcn/sidebar/sidebar-menu-sub-item.svelte +21 -0
  508. package/src/shadcn/sidebar/sidebar-menu-sub.svelte +24 -0
  509. package/src/shadcn/sidebar/sidebar-menu.svelte +21 -0
  510. package/src/shadcn/sidebar/sidebar-provider.svelte +62 -0
  511. package/src/shadcn/sidebar/sidebar-rail.svelte +36 -0
  512. package/src/shadcn/sidebar/sidebar-separator.svelte +19 -0
  513. package/src/shadcn/sidebar/sidebar-trigger.svelte +37 -0
  514. package/src/shadcn/sidebar/sidebar.svelte +109 -0
  515. package/src/shadcn/skeleton/index.ts +7 -0
  516. package/src/shadcn/skeleton/skeleton.svelte +17 -0
  517. package/src/shadcn/slider/index.ts +7 -0
  518. package/src/shadcn/slider/slider.svelte +52 -0
  519. package/src/shadcn/sonner/index.ts +1 -0
  520. package/src/shadcn/sonner/sonner.svelte +31 -0
  521. package/src/shadcn/spinner/index.ts +1 -0
  522. package/src/shadcn/spinner/spinner.svelte +26 -0
  523. package/src/shadcn/switch/index.ts +7 -0
  524. package/src/shadcn/switch/switch.svelte +31 -0
  525. package/src/shadcn/table/index.ts +28 -0
  526. package/src/shadcn/table/table-body.svelte +20 -0
  527. package/src/shadcn/table/table-caption.svelte +20 -0
  528. package/src/shadcn/table/table-cell.svelte +20 -0
  529. package/src/shadcn/table/table-footer.svelte +20 -0
  530. package/src/shadcn/table/table-head.svelte +23 -0
  531. package/src/shadcn/table/table-header.svelte +20 -0
  532. package/src/shadcn/table/table-row.svelte +23 -0
  533. package/src/shadcn/table/table.svelte +25 -0
  534. package/src/shadcn/tabs/index.ts +16 -0
  535. package/src/shadcn/tabs/tabs-content.svelte +17 -0
  536. package/src/shadcn/tabs/tabs-list.svelte +36 -0
  537. package/src/shadcn/tabs/tabs-trigger.svelte +20 -0
  538. package/src/shadcn/tabs/tabs.svelte +19 -0
  539. package/src/shadcn/textarea/index.ts +7 -0
  540. package/src/shadcn/textarea/textarea.svelte +23 -0
  541. package/src/shadcn/toggle/index.ts +13 -0
  542. package/src/shadcn/toggle/toggle.svelte +53 -0
  543. package/src/shadcn/toggle-group/index.ts +10 -0
  544. package/src/shadcn/toggle-group/toggle-group-item.svelte +35 -0
  545. package/src/shadcn/toggle-group/toggle-group.svelte +66 -0
  546. package/src/shadcn/tooltip/context.ts +16 -0
  547. package/src/shadcn/tooltip/index.ts +19 -0
  548. package/src/shadcn/tooltip/tooltip-content.svelte +38 -0
  549. package/src/shadcn/tooltip/tooltip-portal.svelte +7 -0
  550. package/src/shadcn/tooltip/tooltip-provider.svelte +16 -0
  551. package/src/shadcn/tooltip/tooltip-trigger.svelte +7 -0
  552. package/src/shadcn/tooltip/tooltip.svelte +19 -0
  553. package/src/shadcn/trace/index.ts +29 -0
  554. package/src/shadcn/trace/trace-collapsible-content.svelte +29 -0
  555. package/src/shadcn/trace/trace-collapsible-trigger.svelte +29 -0
  556. package/src/shadcn/trace/trace-collapsible.svelte +20 -0
  557. package/src/shadcn/trace/trace-description.svelte +20 -0
  558. package/src/shadcn/trace/trace-icon.svelte +24 -0
  559. package/src/shadcn/trace/trace-line.svelte +31 -0
  560. package/src/shadcn/trace/trace-title.svelte +20 -0
  561. package/src/shadcn/trace/trace.svelte +23 -0
  562. package/src/shadcn/utilities.css +549 -0
  563. package/src/test-setup.ts +34 -0
  564. package/src/tokens.css +993 -0
@@ -0,0 +1,195 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import type { Snippet } from "svelte";
6
+ import EmptyState from "./empty-state.svelte";
7
+ import { cn } from "@heyramzi/utils";
8
+ import { pageGutterClass } from "../../lib/layout.js";
9
+ import { getSectionHost } from "./section-host.js";
10
+ import SectionRuns from "../segmented/section-runs.svelte";
11
+ import type { PhosphorIcon, IconWeight } from "../../lib/icon-types.js";
12
+
13
+ // =========================
14
+ // TYPES
15
+ // =========================
16
+ type EmptyConfig = {
17
+ icon?: PhosphorIcon;
18
+ iconWeight?: IconWeight;
19
+ title: string;
20
+ description?: string;
21
+ actionLabel?: string;
22
+ onAction?: () => void;
23
+ actionHref?: string;
24
+ };
25
+
26
+ interface Props {
27
+ isEmpty?: boolean;
28
+ emptyState?: EmptyConfig;
29
+ hideToolbarWhenEmpty?: boolean;
30
+ maxWidth?: string | false;
31
+ fillHeight?: boolean;
32
+ containerClass?: string;
33
+ toolbarClass?: string;
34
+ tabs?: Snippet;
35
+ search?: Snippet;
36
+ filters?: Snippet;
37
+ rightActions?: Snippet;
38
+ summary?: Snippet;
39
+ content?: Snippet;
40
+ empty?: Snippet;
41
+ extra?: Snippet;
42
+ };
43
+
44
+ // =========================
45
+ // PROPS
46
+ // =========================
47
+ let {
48
+ isEmpty = false,
49
+ emptyState,
50
+ hideToolbarWhenEmpty = false,
51
+ maxWidth = "1600px",
52
+ fillHeight = false,
53
+ containerClass,
54
+ toolbarClass,
55
+ tabs,
56
+ search,
57
+ filters,
58
+ rightActions,
59
+ summary,
60
+ content,
61
+ empty,
62
+ extra,
63
+ }: Props = $props();
64
+
65
+ // =========================
66
+ // STATE
67
+ // =========================
68
+ // Null off an app shell, where the toolbar just has no section strip to draw.
69
+ const section = getSectionHost();
70
+
71
+ // =========================
72
+ // DERIVED
73
+ // =========================
74
+ let hideChrome = $derived(isEmpty && hideToolbarWhenEmpty);
75
+ // WHY the section strip counts: on a page whose only chrome is the section
76
+ // nav, this row is what draws it, and skipping the row would drop the tabs.
77
+ let hasToolbar = $derived(
78
+ Boolean(search || filters || tabs || rightActions || section?.strip) && !hideChrome,
79
+ );
80
+ // Whether anything holds the left of the row. With nothing there, the tabs
81
+ // take that edge instead of floating mid-row beside the actions.
82
+ let hasLeading = $derived(Boolean(search || filters || section?.strip));
83
+
84
+ // =========================
85
+ // EFFECTS
86
+ // =========================
87
+ // Claiming stops the app shell drawing its own strip above this page.
88
+ // Unmount releases it, so shell-less pages get the standalone row back.
89
+ $effect(() => {
90
+ if (!hasToolbar) return;
91
+ return section?.claim();
92
+ });
93
+ // The shared gutter from `lib/layout.ts`. A typed one put the search field
94
+ // 8px left of the section tabs above it.
95
+ let outerClass = $derived(
96
+ cn(
97
+ `flex flex-col gap-4 py-4 ${pageGutterClass}`,
98
+ fillHeight && "min-h-0 flex-1",
99
+ !fillHeight && "flex-1",
100
+ containerClass,
101
+ ),
102
+ );
103
+ // flex-1 lets a snippet pin a footer with mt-auto. min-w-0 keeps wide
104
+ // tables and chip rows from causing horizontal scroll.
105
+ let innerClass = $derived(
106
+ maxWidth === false
107
+ ? cn("flex min-w-0 flex-1 flex-col gap-4", fillHeight && "min-h-0")
108
+ : cn(
109
+ "mx-auto flex w-full min-w-0 flex-1 flex-col gap-4",
110
+ fillHeight && "min-h-0",
111
+ ),
112
+ );
113
+ let innerStyle = $derived(maxWidth ? `max-width: ${maxWidth}` : undefined);
114
+ </script>
115
+
116
+ <!-- =========================== -->
117
+ <!-- MARKUP -->
118
+ <!-- =========================== -->
119
+ <div class={outerClass}>
120
+ <div class={innerClass} style={innerStyle}>
121
+ {#if hasToolbar}
122
+ <!-- One row on every list page: search and filters left, then tabs,
123
+ view toggle and primary action right. -->
124
+ <!-- Without room, `order` keeps the actions beside search and drops
125
+ filters to a scrolling line below. flex-wrap stays on at every width. -->
126
+ <!-- A container query, not a breakpoint: an 834px iPad read as desktop
127
+ and clipped a 733px toolbar into a 538px column. -->
128
+ <!-- Sticky, because the section tabs live here. `-mx-4 px-4` covers the
129
+ gutter, `-mt-4 py-4` pulls the top padding inside the sticky box. -->
130
+ <div
131
+ class="@container/toolbar sticky top-0 z-20 -mx-4 -mt-4 bg-background/95 px-4 py-4 backdrop-blur supports-[backdrop-filter]:bg-background/70"
132
+ >
133
+ <div class={cn("flex flex-wrap items-center gap-2", toolbarClass)}>
134
+ <!-- The section tabs lead: which page comes before which slice of it.
135
+ One row replaces two rows that held four controls. -->
136
+ {#if section?.strip}
137
+ <SectionRuns
138
+ strip={section.strip}
139
+ class="order-first w-full max-w-full @3xl/toolbar:w-auto"
140
+ />
141
+ {/if}
142
+ {#if search}
143
+ <div class="order-1 flex min-w-0 flex-1 items-center @3xl/toolbar:flex-none">
144
+ {@render search()}
145
+ </div>
146
+ {/if}
147
+ {#if tabs || rightActions}
148
+ <!-- Shrinks and wraps, not shrink-0: /tasks needs 610px here, and
149
+ shrink-0 clipped its last control off an iPad screen. -->
150
+ <div
151
+ class={cn(
152
+ "order-2 flex min-w-0 flex-wrap items-center gap-2 @3xl/toolbar:order-3",
153
+ hasLeading ? "@3xl/toolbar:ms-auto" : "flex-1",
154
+ )}
155
+ >
156
+ {#if tabs}
157
+ <div class={cn("flex min-w-0 items-center", !hasLeading && "me-auto")}>
158
+ {@render tabs()}
159
+ </div>
160
+ {/if}
161
+ {#if rightActions}
162
+ {@render rightActions()}
163
+ {/if}
164
+ </div>
165
+ {/if}
166
+ {#if filters}
167
+ <div
168
+ class="no-scrollbar order-3 flex w-full max-w-full min-w-0 items-center gap-2 overflow-x-auto @3xl/toolbar:order-2 @3xl/toolbar:w-auto"
169
+ >
170
+ {@render filters()}
171
+ </div>
172
+ {/if}
173
+ </div>
174
+ </div>
175
+ {/if}
176
+
177
+ {#if summary && !hideChrome}
178
+ {@render summary()}
179
+ {/if}
180
+
181
+ {#if extra && !hideChrome}
182
+ {@render extra()}
183
+ {/if}
184
+
185
+ {#if isEmpty}
186
+ {#if empty}
187
+ {@render empty()}
188
+ {:else if emptyState}
189
+ <EmptyState {...emptyState} />
190
+ {/if}
191
+ {:else if content}
192
+ {@render content()}
193
+ {/if}
194
+ </div>
195
+ </div>
@@ -0,0 +1,19 @@
1
+ import { cn } from "@heyramzi/utils";
2
+
3
+ /**
4
+ * The list-page table rhythm, Attio's: one 40px row, a hairline under each, a flat hover.
5
+ */
6
+ export const listTable = {
7
+ /** The header row: 36px, a hairline under it. On the cells: `thead` zeroes its row's border. */
8
+ headRow: (className?: string) =>
9
+ cn(
10
+ "hover:translate-y-0 hover:bg-transparent hover:shadow-none [&>th]:h-9 [&>th]:border-b [&>th]:border-border",
11
+ className,
12
+ ),
13
+ /** A body row. The shared row lifts on hover; a flush list row does not. */
14
+ row: (className?: string) =>
15
+ cn(
16
+ "border-b border-border/50 last:border-b-0 hover:translate-y-0 hover:bg-muted/40 hover:shadow-none [&>td]:h-10 [&>td]:py-0",
17
+ className,
18
+ ),
19
+ };
@@ -0,0 +1,206 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import type { Snippet } from "svelte";
6
+ import { setContext } from "svelte";
7
+ import { CaretLeft } from "phosphor-svelte";
8
+ import { cn } from "@heyramzi/utils";
9
+ import { Button } from "../../shadcn/button/index.js";
10
+ import { MASTER_DETAIL_PANE, type MasterDetailPane } from "./queue-rail.js";
11
+
12
+ // =========================
13
+ // TYPES
14
+ // =========================
15
+ interface Props {
16
+ // The list rail (an <aside>) and the content pane. Pages style both; this
17
+ // shell owns the two columns and the min-h-0/min-w-0 scroll containment.
18
+ rail: Snippet;
19
+ detail: Snippet;
20
+ // A full-width strip above both columns for facts to read before acting,
21
+ // like a posting cap. Pages wrapping the shell to add one broke the padding.
22
+ banner?: Snippet;
23
+ // WHY: a CSS grid track for the rail column ("16rem", "minmax(280px, 340px)").
24
+ // Queue pages pass `queueRailWidth` rather than a literal.
25
+ railWidth?: string;
26
+ // Stack to one column below this. "lg" because rail and detail can't both be
27
+ // read under 1024px. null only for a rail narrow enough for a phone.
28
+ stackBelow?: "lg" | "xl" | null;
29
+ // WHY: the phone back button says where it goes back to. "Back" alone is the
30
+ // browser's word and the reader has to remember what they left.
31
+ backLabel?: string;
32
+ // Bindable so a url naming a record opens on it after a refresh or a pasted
33
+ // link. The page sets it. Ignored above the stack breakpoint.
34
+ pane?: "rail" | "detail";
35
+ gap?: boolean;
36
+ pad?: boolean;
37
+ railClass?: string;
38
+ detailClass?: string;
39
+ class?: string;
40
+ };
41
+
42
+ // =========================
43
+ // PROPS
44
+ // =========================
45
+ let {
46
+ rail,
47
+ detail,
48
+ banner,
49
+ railWidth = "minmax(260px, 320px)",
50
+ stackBelow = "lg",
51
+ backLabel = "Back to the list",
52
+ // The rail wins by default: an unpicked detail is an empty state or a
53
+ // guessed row, both worse than the list.
54
+ pane = $bindable("rail"),
55
+ gap = true,
56
+ pad = true,
57
+ railClass,
58
+ detailClass,
59
+ class: className,
60
+ }: Props = $props();
61
+
62
+ // =========================
63
+ // DERIVED
64
+ // =========================
65
+ // One pane at a time when stacked. Two rows put a 34svh list scroller on top of
66
+ // a record scroller, and neither was readable.
67
+ let gridClass = $derived(
68
+ stackBelow === "xl"
69
+ ? "grid-cols-1 grid-rows-[minmax(0,1fr)] xl:grid-rows-1 xl:[grid-template-columns:var(--md-cols)]"
70
+ : stackBelow === "lg"
71
+ ? "grid-cols-1 grid-rows-[minmax(0,1fr)] lg:grid-rows-1 lg:[grid-template-columns:var(--md-cols)]"
72
+ : "[grid-template-columns:var(--md-cols)]",
73
+ );
74
+ // Hidden while stacked; back to normal above the breakpoint, where both panes show.
75
+ let hideWhileStacked = $derived(stackBelow === "xl" ? "max-xl:hidden" : "max-lg:hidden");
76
+ // Drawn only while stacked. The back bar has no job beside a visible rail.
77
+ let stackedOnly = $derived(stackBelow === "xl" ? "xl:hidden" : "lg:hidden");
78
+
79
+ // =========================
80
+ // FUNCTIONS
81
+ // =========================
82
+ // `RailRow` calls this when a record is picked. Above the breakpoint the classes
83
+ // that read `pane` are all prefixed away, so the flag changes nothing there.
84
+ setContext<MasterDetailPane>(MASTER_DETAIL_PANE, {
85
+ open: () => {
86
+ if (stackBelow) pane = "detail";
87
+ },
88
+ });
89
+ </script>
90
+
91
+ <!-- =========================== -->
92
+ <!-- MARKUP -->
93
+ <!-- =========================== -->
94
+ {#snippet columns(inBanner: boolean)}
95
+ <div
96
+ class={cn(
97
+ "md-cols grid min-h-0",
98
+ inBanner ? "flex-1" : "h-full",
99
+ gap && "gap-4",
100
+ pad && !inBanner && "p-4",
101
+ gridClass,
102
+ className,
103
+ )}
104
+ style="--md-cols: {railWidth} minmax(0, 1fr)"
105
+ data-pane={stackBelow ? pane : undefined}
106
+ data-stack={stackBelow ?? undefined}
107
+ >
108
+ <aside
109
+ class={cn(
110
+ "md-rail min-h-0",
111
+ railClass,
112
+ stackBelow && pane === "detail" && hideWhileStacked,
113
+ )}
114
+ >
115
+ {@render rail()}
116
+ </aside>
117
+ <div
118
+ class={cn(
119
+ "md-detail flex min-h-0 min-w-0 flex-col",
120
+ stackBelow && pane === "rail" && hideWhileStacked,
121
+ )}
122
+ >
123
+ {#if stackBelow}
124
+ <!-- WHY a button and not a link: nothing navigated, so there is no url to
125
+ go back to and the browser's own back would leave the page. -->
126
+ <Button
127
+ variant="ghost"
128
+ size="sm"
129
+ class={cn("-mx-1 mb-2 self-start px-1 text-muted-foreground", stackedOnly)}
130
+ onclick={() => (pane = "rail")}
131
+ >
132
+ <CaretLeft size={16} weight="bold" />
133
+ {backLabel}
134
+ </Button>
135
+ {/if}
136
+ <div class={cn("min-h-0 min-w-0 flex-1", detailClass)}>
137
+ {@render detail()}
138
+ </div>
139
+ </div>
140
+ </div>
141
+ {/snippet}
142
+
143
+ {#if banner}
144
+ <div class={cn("flex h-full min-h-0 flex-col", gap && "gap-4", pad && "p-4")}>
145
+ {@render banner()}
146
+ {@render columns(true)}
147
+ </div>
148
+ {:else}
149
+ {@render columns(false)}
150
+ {/if}
151
+
152
+ <!-- =========================== -->
153
+ <!-- STYLE -->
154
+ <!-- =========================== -->
155
+ <style>
156
+ /* The stacked list-to-record swap. The incoming pane moves: forward from the
157
+ right, back from the left. It runs when `data-pane` flips. */
158
+ @media (max-width: 1023px) {
159
+ .md-cols[data-stack="lg"][data-pane="detail"] > .md-detail {
160
+ animation: md-in-forward var(--duration-fast) var(--ease-smooth-out) both;
161
+ }
162
+
163
+ .md-cols[data-stack="lg"][data-pane="rail"] > .md-rail {
164
+ animation: md-in-back var(--duration-fast) var(--ease-smooth-out) both;
165
+ }
166
+ }
167
+
168
+ @media (max-width: 1279px) {
169
+ .md-cols[data-stack="xl"][data-pane="detail"] > .md-detail {
170
+ animation: md-in-forward var(--duration-fast) var(--ease-smooth-out) both;
171
+ }
172
+
173
+ .md-cols[data-stack="xl"][data-pane="rail"] > .md-rail {
174
+ animation: md-in-back var(--duration-fast) var(--ease-smooth-out) both;
175
+ }
176
+ }
177
+
178
+ @keyframes md-in-forward {
179
+ from {
180
+ opacity: 0;
181
+ transform: translateX(var(--distance-large));
182
+ }
183
+ to {
184
+ opacity: 1;
185
+ transform: none;
186
+ }
187
+ }
188
+
189
+ @keyframes md-in-back {
190
+ from {
191
+ opacity: 0;
192
+ transform: translateX(calc(var(--distance-large) * -1));
193
+ }
194
+ to {
195
+ opacity: 1;
196
+ transform: none;
197
+ }
198
+ }
199
+
200
+ @media (prefers-reduced-motion: reduce) {
201
+ .md-cols > .md-rail,
202
+ .md-cols > .md-detail {
203
+ animation-duration: 1ms;
204
+ }
205
+ }
206
+ </style>
@@ -0,0 +1,37 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve, dirname } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { describe, expect, it } from "vite-plus/test";
5
+
6
+ const here = dirname(fileURLToPath(import.meta.url));
7
+ const shell = readFileSync(resolve(here, "master-detail.svelte"), "utf8");
8
+ const row = readFileSync(resolve(here, "rail-row.svelte"), "utf8");
9
+
10
+ describe("master detail on a phone", () => {
11
+ it("shows one pane at a time below the stack breakpoint", () => {
12
+ // Two rows put a 34svh rail scroller above a second one. One row, one
13
+ // pane, hidden only while stacked so desktop stays untouched.
14
+ expect(shell).toContain("grid-rows-[minmax(0,1fr)] lg:grid-rows-1");
15
+ expect(shell).toContain("grid-rows-[minmax(0,1fr)] xl:grid-rows-1");
16
+ expect(shell).not.toContain("max-h-[34svh]");
17
+ expect(shell).toContain('stackBelow === "xl" ? "max-xl:hidden" : "max-lg:hidden"');
18
+ expect(shell).toContain('stackBelow && pane === "detail" && hideWhileStacked');
19
+ expect(shell).toContain('stackBelow && pane === "rail" && hideWhileStacked');
20
+ });
21
+
22
+ it("draws a way back to the list, and only while stacked", () => {
23
+ expect(shell).toContain('stackBelow === "xl" ? "xl:hidden" : "lg:hidden"');
24
+ expect(shell).toContain('onclick={() => (pane = "rail")}');
25
+ expect(shell).toContain("{backLabel}");
26
+ });
27
+
28
+ it("lets a page open on the record its url names", () => {
29
+ expect(shell).toContain('pane = $bindable("rail")');
30
+ });
31
+
32
+ it("moves to the record when a rail row is picked", () => {
33
+ expect(shell).toContain("setContext<MasterDetailPane>(MASTER_DETAIL_PANE");
34
+ expect(row).toContain("getContext<MasterDetailPane | undefined>(MASTER_DETAIL_PANE)");
35
+ expect(row).toContain("pane?.open()");
36
+ });
37
+ });
@@ -0,0 +1,68 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import type { Snippet } from "svelte";
6
+ import { Warning, Info, CheckCircle, WarningCircle } from "phosphor-svelte";
7
+ import { cn } from "@heyramzi/utils";
8
+ import { Alert } from "../../shadcn/alert/index.js";
9
+ import { TONE_CLASS, type Tone as SeverityTone } from "../../lib/tone.js";
10
+ import type { PhosphorIcon } from "../../lib/icon-types.js";
11
+
12
+ // =========================
13
+ // TYPES
14
+ // =========================
15
+ // A strip that stops the reader is a severity. Neutral and accent are chip tones.
16
+ type Tone = Extract<SeverityTone, "destructive" | "warning" | "info" | "success">;
17
+
18
+ interface Props {
19
+ // The "look at this before you act" strip. Hand-rolled ones used raw amber
20
+ // and sky, so one severity showed two colours one route apart.
21
+ tone?: Tone;
22
+ icon?: PhosphorIcon;
23
+ // The trailing control: a retry, a jump to the thing that needs fixing.
24
+ actions?: Snippet;
25
+ children: Snippet;
26
+ class?: string;
27
+ }
28
+
29
+ const TONE_ICON: Record<Tone, PhosphorIcon> = {
30
+ destructive: Warning,
31
+ warning: WarningCircle,
32
+ info: Info,
33
+ success: CheckCircle,
34
+ };
35
+
36
+ // =========================
37
+ // PROPS
38
+ // =========================
39
+ let { tone = "destructive", icon, actions, children, class: className }: Props = $props();
40
+
41
+ // =========================
42
+ // DERIVED
43
+ // =========================
44
+ let Icon = $derived(icon ?? TONE_ICON[tone]);
45
+ </script>
46
+
47
+ <!-- =========================== -->
48
+ <!-- MARKUP -->
49
+ <!-- =========================== -->
50
+ <!-- WHY `status` for the calm tones: info and success shouldn't interrupt a
51
+ screen reader the way a failure does. -->
52
+ <Alert
53
+ variant={tone === "destructive" ? "destructive" : "default"}
54
+ role={tone === "destructive" || tone === "warning" ? "alert" : "status"}
55
+ class={cn(
56
+ "flex flex-wrap items-center gap-x-3 gap-y-2 rounded-xl [&>svg]:translate-y-0",
57
+ TONE_CLASS[tone],
58
+ className,
59
+ )}
60
+ >
61
+ <Icon class="size-4 shrink-0" aria-hidden="true" />
62
+ <span class="min-w-0 flex-1 font-medium">{@render children()}</span>
63
+ {#if actions}
64
+ <div class="flex shrink-0 items-center gap-2">
65
+ {@render actions()}
66
+ </div>
67
+ {/if}
68
+ </Alert>
@@ -0,0 +1,104 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import { CaretLeft, CaretRight } from "phosphor-svelte";
6
+ import { cn } from "@heyramzi/utils";
7
+ import { Button } from "../../shadcn/button/index.js";
8
+ import { stepTo } from "./step.js";
9
+
10
+ // =========================
11
+ // TYPES
12
+ // =========================
13
+ interface Props {
14
+ /** Ids in the order the list shows them. */
15
+ ids: readonly string[];
16
+ current: string | null | undefined;
17
+ onselect: (id: string) => void;
18
+ previousLabel?: string;
19
+ nextLabel?: string;
20
+ /** Off when two steppers share a page, so one key press moves one list. */
21
+ keys?: boolean;
22
+ class?: string;
23
+ };
24
+
25
+ // =========================
26
+ // PROPS
27
+ // =========================
28
+ let {
29
+ ids,
30
+ current,
31
+ onselect,
32
+ previousLabel = "Previous",
33
+ nextLabel = "Next",
34
+ keys = true,
35
+ class: className,
36
+ }: Props = $props();
37
+
38
+ // =========================
39
+ // DERIVED
40
+ // =========================
41
+ let index = $derived(current ? ids.indexOf(current) : -1);
42
+ let prev = $derived(stepTo(ids, current, -1));
43
+ let next = $derived(stepTo(ids, current, 1));
44
+
45
+ // =========================
46
+ // HANDLERS
47
+ // =========================
48
+ function isTyping(event: KeyboardEvent): boolean {
49
+ const target = event.target as HTMLElement | null;
50
+ return Boolean(target?.closest("input, textarea, select, [contenteditable='true']"));
51
+ }
52
+
53
+ function ignored(event: KeyboardEvent): boolean {
54
+ const modified = event.metaKey || event.ctrlKey || event.altKey;
55
+ return !keys || modified || event.defaultPrevented || isTyping(event);
56
+ }
57
+
58
+ // K and J, as in Gmail: the arrows already belong to slide viewers and text fields.
59
+ function targetFor(key: string): string | null {
60
+ if (key === "k") return prev;
61
+ if (key === "j") return next;
62
+ return null;
63
+ }
64
+
65
+ function onkeydown(event: KeyboardEvent) {
66
+ if (ignored(event)) return;
67
+ const id = targetFor(event.key);
68
+ if (!id) return;
69
+ event.preventDefault();
70
+ onselect(id);
71
+ }
72
+ </script>
73
+
74
+ <svelte:window {onkeydown} />
75
+
76
+ {#if index >= 0 && ids.length > 1}
77
+ <div class={cn("flex items-center gap-0.5 text-muted-foreground", className)}>
78
+ <Button
79
+ variant="ghost"
80
+ size="sm"
81
+ icon
82
+ class="text-muted-foreground"
83
+ disabled={!prev}
84
+ onclick={() => prev && onselect(prev)}
85
+ aria-label={previousLabel}
86
+ title={`${previousLabel} (K)`}
87
+ >
88
+ <CaretLeft size={15} />
89
+ </Button>
90
+ <span class="min-w-9 text-center text-xs tabular-nums">{index + 1}/{ids.length}</span>
91
+ <Button
92
+ variant="ghost"
93
+ size="sm"
94
+ icon
95
+ class="text-muted-foreground"
96
+ disabled={!next}
97
+ onclick={() => next && onselect(next)}
98
+ aria-label={nextLabel}
99
+ title={`${nextLabel} (J)`}
100
+ >
101
+ <CaretRight size={15} />
102
+ </Button>
103
+ </div>
104
+ {/if}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The rail surface of a queue page, passed to `MasterDetail`'s `railClass`.
3
+ * A class, not a wrapper: the shell's `<aside>` already owns the scroll height.
4
+ */
5
+ // Copies of this string drifted to `rounded-lg`, the control radius, not the container one.
6
+ export const queueRailClass =
7
+ "flex flex-col overflow-hidden rounded-xl border border-border bg-card";
8
+
9
+ /**
10
+ * The rail column track, passed to `MasterDetail`'s `railWidth`. One page ran
11
+ * 20px wider, so the detail pane jumped between pages.
12
+ */
13
+ // 280px fits a two-line thread title. Past 340px the list competes with the record.
14
+ export const queueRailWidth = "minmax(280px, 340px)";
15
+
16
+ /**
17
+ * Context key for the pane `MasterDetail` shows on a phone. Picking a row moves
18
+ * to the record, so `RailRow` reads this instead of each page passing a callback.
19
+ */
20
+ export const MASTER_DETAIL_PANE = Symbol("master-detail-pane");
21
+
22
+ export type MasterDetailPane = {
23
+ /** Show the detail pane. No-op above the stack breakpoint, where both are visible. */
24
+ open: () => void;
25
+ };
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import type { Snippet } from "svelte";
6
+ import { cn } from "@heyramzi/utils";
7
+
8
+ // =========================
9
+ // TYPES
10
+ // =========================
11
+ interface Props {
12
+ // The scroller under `RailHead`. `min-h-0` makes a flex child scroll instead
13
+ // of growing its parent, and it's the line hand-rolled rails forgot.
14
+ children: Snippet;
15
+ class?: string;
16
+ };
17
+
18
+ // =========================
19
+ // PROPS
20
+ // =========================
21
+ let { children, class: className }: Props = $props();
22
+ </script>
23
+
24
+ <!-- =========================== -->
25
+ <!-- MARKUP -->
26
+ <!-- =========================== -->
27
+ <div class={cn("min-h-0 flex-1 overflow-y-auto", className)}>
28
+ {@render children()}
29
+ </div>