@heyramzi/ui 0.0.0-stage → 1.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +592 -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 +110 -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 +181 -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 +256 -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,106 @@
1
+ <script lang="ts" module>
2
+ import type { Component } from "svelte";
3
+
4
+ /**
5
+ * The one way out to a tool: mark, name, go-to arrow, never the verb, which
6
+ * survives as the accessible name.
7
+ */
8
+ export interface OpenInTool {
9
+ /** The visible text, and the word the tool calls itself. */
10
+ name: string;
11
+ /** The default accessible sentence, when the caller passes no translated one. */
12
+ action: string;
13
+ icon: Component<{ size?: number; class?: string }>;
14
+ }
15
+
16
+ /** `chip` is the bordered inline tag, `button` sits in a header beside other
17
+ * buttons, `icon` is the bare mark for a dense row. */
18
+ export type OpenInVariant = "chip" | "button" | "icon";
19
+ export type OpenInSize = "sm" | "default";
20
+ </script>
21
+
22
+ <script lang="ts">
23
+ // =========================
24
+ // IMPORTS
25
+ // =========================
26
+ import { ArrowSquareOut } from "phosphor-svelte";
27
+ import { cn } from "@heyramzi/utils";
28
+ import { buttonVariants } from "../../shadcn/button/index.js";
29
+
30
+ // =========================
31
+ // TYPES
32
+ // =========================
33
+ interface Props {
34
+ /** The tool to link to: its name, accessible sentence and mark. */
35
+ tool: OpenInTool;
36
+ href: string;
37
+ variant?: OpenInVariant;
38
+ size?: OpenInSize;
39
+ /** Overrides the visible text. The accessible name is `title`, never this. */
40
+ label?: string;
41
+ /** The accessible sentence, translated by the caller. */
42
+ title?: string;
43
+ class?: string;
44
+ }
45
+
46
+ // =========================
47
+ // PROPS
48
+ // =========================
49
+ let {
50
+ tool,
51
+ href,
52
+ variant = "chip",
53
+ size = "default",
54
+ label,
55
+ title,
56
+ class: className,
57
+ }: Props = $props();
58
+
59
+ // =========================
60
+ // DERIVED
61
+ // =========================
62
+ let Icon = $derived(tool.icon);
63
+ let iconSize = $derived(size === "sm" ? 12 : 14);
64
+ let action = $derived(title ?? tool.action);
65
+ let text = $derived(label ?? tool.name);
66
+ </script>
67
+
68
+ <!-- =========================== -->
69
+ <!-- MARKUP -->
70
+ <!-- =========================== -->
71
+ <!-- The click stops here because these usually sit in a clickable row, and a
72
+ call site that forgot would open the row as well. -->
73
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
74
+ <div
75
+ class="inline-flex shrink-0"
76
+ onclick={(e) => e.stopPropagation()}
77
+ onkeydown={(e) => e.stopPropagation()}
78
+ >
79
+ <a
80
+ {href}
81
+ target="_blank"
82
+ rel="noopener noreferrer"
83
+ aria-label={action}
84
+ title={action}
85
+ class={cn(
86
+ variant === "button"
87
+ ? buttonVariants({ variant: "outline", size: size === "sm" ? "sm" : "default" })
88
+ : variant === "icon"
89
+ ? "inline-flex shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
90
+ : cn(
91
+ "inline-flex shrink-0 items-center gap-1.5 whitespace-nowrap rounded-md border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
92
+ size === "sm" ? "px-1.5 py-0.5 text-[11px]" : "px-2 py-1 text-xs",
93
+ ),
94
+ variant === "icon" && (size === "sm" ? "size-5" : "size-6"),
95
+ className,
96
+ )}
97
+ >
98
+ <Icon size={iconSize} />
99
+ {#if variant !== "icon"}
100
+ {text}
101
+ <!-- The one thing the mark can't say: this leaves the app. It stays muted
102
+ so the eye lands on the tool name first. -->
103
+ <ArrowSquareOut size={iconSize} class="text-muted-foreground" />
104
+ {/if}
105
+ </a>
106
+ </div>
@@ -0,0 +1,4 @@
1
+ export { default as Picker } from "./picker.svelte";
2
+ export { default as MarkPicker } from "./mark-picker.svelte";
3
+ export { pickerTriggerClass } from "./trigger.js";
4
+ export type { PickerGroup, PickerOption } from "./types.js";
@@ -0,0 +1,84 @@
1
+ <script lang="ts" generics="V extends string">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import { CaretDown } from "phosphor-svelte";
6
+ import { Mark } from "../mark/index.js";
7
+ import { cn } from "@heyramzi/utils";
8
+ import Picker from "./picker.svelte";
9
+ import { pickerTriggerClass } from "./trigger.js";
10
+ import type { PickerOption } from "./types.js";
11
+
12
+ // =========================
13
+ // TYPES
14
+ // =========================
15
+
16
+ /**
17
+ * The picker for named things carrying a mark: a kit, a channel, a client. It
18
+ * wraps `Picker` because the kit strip and the channel row wrote the same two
19
+ * snippets.
20
+ */
21
+ type Props = {
22
+ options: PickerOption<V>[];
23
+ value: V | null;
24
+ onSelect: (value: V) => void;
25
+ ariaLabel: string;
26
+ /** Said by the trigger when nothing in the list is the current value. */
27
+ placeholder?: string;
28
+ /** The search field's own prompt, on a list long enough to show one. */
29
+ searchPlaceholder?: string;
30
+ /** `circle` for a face or an avatar, `square` for a logo. */
31
+ shape?: "square" | "circle";
32
+ width?: "sm" | "default" | "lg";
33
+ class?: string;
34
+ };
35
+
36
+ // =========================
37
+ // PROPS
38
+ // =========================
39
+ let {
40
+ options,
41
+ value,
42
+ onSelect,
43
+ ariaLabel,
44
+ placeholder = "Select",
45
+ searchPlaceholder = "Search",
46
+ shape = "square",
47
+ width = "lg",
48
+ class: className,
49
+ }: Props = $props();
50
+
51
+ // =========================
52
+ // DERIVED
53
+ // =========================
54
+ let selected = $derived(options.find((option) => option.value === value));
55
+ </script>
56
+
57
+ <!-- =========================== -->
58
+ <!-- MARKUP -->
59
+ <!-- =========================== -->
60
+ <Picker
61
+ {options}
62
+ {value}
63
+ {onSelect}
64
+ {width}
65
+ {searchPlaceholder}
66
+ triggerClass={cn(pickerTriggerClass(), className)}
67
+ triggerLabel={ariaLabel}
68
+ >
69
+ {#snippet trigger()}
70
+ {#if selected?.image}
71
+ <Mark src={selected.image} label={selected.label} {shape} />
72
+ {:else if selected?.icon}
73
+ {@const Icon = selected.icon}
74
+ <Icon class="text-muted-foreground size-4" />
75
+ {/if}
76
+ <span class="truncate">{selected?.label ?? placeholder}</span>
77
+ <CaretDown class="text-muted-foreground size-3" />
78
+ {/snippet}
79
+ <!-- A row with an icon of its own is drawn by `Picker`; this is every other
80
+ row, the ones that carry a mark. -->
81
+ {#snippet lead(option)}
82
+ <Mark src={option.image} label={option.label} {shape} />
83
+ {/snippet}
84
+ </Picker>
@@ -0,0 +1,143 @@
1
+ <script lang="ts" generics="V extends string">
2
+ import type { Snippet } from "svelte";
3
+ import * as Command from "../../shadcn/command/index.js";
4
+ import * as Popover from "../../shadcn/popover/index.js";
5
+ import { cn } from "@heyramzi/utils";
6
+ import type { PickerGroup, PickerOption } from "./types.js";
7
+
8
+ /**
9
+ * One menu for every "pick one of these" in the app: priority, person, week,
10
+ * goal, status. It's the Command palette at popover scale, same keys.
11
+ */
12
+ // It replaced four pickers in one sidebar, each built differently. Only one
13
+ // had search, only one had arrow keys, and the check mark sat in three places.
14
+
15
+ type Props = {
16
+ /** Flat list. Mutually exclusive with `groups`. */
17
+ options?: PickerOption<V>[];
18
+ /** Titled runs, when the menu has a shape worth naming. */
19
+ groups?: PickerGroup<V>[];
20
+ value: V | null;
21
+ onSelect: (value: V) => void;
22
+ /** The control that opens the menu. Rendered inside `Popover.Trigger`. */
23
+ trigger: Snippet;
24
+ /** Class for the trigger. Use `pickerTriggerClass` to build it. */
25
+ triggerClass?: string;
26
+ triggerLabel?: string;
27
+ /**
28
+ * A mark drawn at the head of a row when the option carries no icon: an
29
+ * avatar, a client logo, a colour swatch.
30
+ */
31
+ lead?: Snippet<[PickerOption<V>]>;
32
+ /** Pinned under the list: a clear action, a hint, a secondary surface. */
33
+ footer?: Snippet<[{ close: () => void }]>;
34
+ /**
35
+ * Search field. `auto` shows it from seven rows, the point at which the
36
+ * eye stops finding a name faster than the fingers can type it.
37
+ */
38
+ searchable?: boolean | "auto";
39
+ searchPlaceholder?: string;
40
+ emptyLabel?: string;
41
+ open?: boolean;
42
+ align?: "start" | "center" | "end";
43
+ /** `sm` fits a word, `default` a name, `lg` a name and a date. */
44
+ width?: "sm" | "default" | "lg";
45
+ disabled?: boolean;
46
+ };
47
+
48
+ let {
49
+ options,
50
+ groups,
51
+ value,
52
+ onSelect,
53
+ trigger,
54
+ triggerClass,
55
+ triggerLabel,
56
+ lead,
57
+ footer,
58
+ searchable = "auto",
59
+ searchPlaceholder,
60
+ emptyLabel = "No matches",
61
+ open = $bindable(false),
62
+ align = "start",
63
+ width = "default",
64
+ disabled = false
65
+ }: Props = $props();
66
+
67
+ let resolved = $derived<PickerGroup<V>[]>(groups ?? [{ options: options ?? [] }]);
68
+ let count = $derived(resolved.reduce((n, g) => n + g.options.length, 0));
69
+ let showSearch = $derived(searchable === "auto" ? count >= 7 : searchable);
70
+
71
+ // 176 / 208 / 240px. The reference runs a 5-row priority menu at 206px, so
72
+ // `default` is the common case. `lg` fits a name and a date without cutting.
73
+ const WIDTH = { sm: "w-44", default: "w-52", lg: "w-60" } as const;
74
+
75
+ function pick(next: V) {
76
+ open = false;
77
+ onSelect(next);
78
+ }
79
+
80
+ /**
81
+ * WHY a custom filter: the default one scores `value`, which is often an id.
82
+ * Typing "an" fuzzy-matched every uuid, so only the visible words are searched.
83
+ */
84
+ function filter(_value: string, search: string, keywords?: string[]) {
85
+ return keywords ? Command.computeCommandScore(keywords.join(" "), search) : 0;
86
+ }
87
+
88
+ function keywordsFor(option: PickerOption<V>): string[] {
89
+ return [option.label, option.hint, option.keywords].filter(Boolean) as string[];
90
+ }
91
+ </script>
92
+
93
+ <Popover.Root bind:open>
94
+ <Popover.Trigger class={triggerClass} aria-label={triggerLabel} {disabled}>
95
+ {@render trigger()}
96
+ </Popover.Trigger>
97
+ <!-- A picker opens below its trigger. `side` was a prop taking four edges and
98
+ no caller in any repo had ever passed one. Deleted 4 Sep 2026. -->
99
+ <Popover.Content {align} side="bottom" class={cn("p-0", WIDTH[width])}>
100
+ <Command.Root {filter} loop>
101
+ {#if showSearch}
102
+ <Command.Input placeholder={searchPlaceholder} />
103
+ {/if}
104
+ <Command.List class={cn(!showSearch && "py-1.5")}>
105
+ {#if showSearch}
106
+ <Command.Empty>{emptyLabel}</Command.Empty>
107
+ {/if}
108
+ {#each resolved as group, i (group.heading ?? i)}
109
+ <Command.Group heading={group.heading}>
110
+ {#each group.options as option (option.value)}
111
+ {@const active = option.value === value}
112
+ <Command.Item
113
+ value={option.value}
114
+ keywords={keywordsFor(option)}
115
+ disabled={option.disabled}
116
+ data-checked={active ? "true" : undefined}
117
+ onSelect={() => pick(option.value)}
118
+ >
119
+ {#if option.icon}
120
+ {@const Icon = option.icon}
121
+ <Icon class={cn("size-4", option.tone)} weight="bold" />
122
+ {:else if lead}
123
+ {@render lead(option)}
124
+ {/if}
125
+ <span class={cn("min-w-0 flex-1 truncate text-left", active && option.tone)}>
126
+ {option.label}
127
+ </span>
128
+ {#if option.hint}
129
+ <span class="text-muted-foreground shrink-0 text-xs tabular-nums">{option.hint}</span>
130
+ {/if}
131
+ </Command.Item>
132
+ {/each}
133
+ </Command.Group>
134
+ {/each}
135
+ </Command.List>
136
+ {#if footer}
137
+ <div class="border-border/70 border-t p-1.5">
138
+ {@render footer({ close: () => (open = false) })}
139
+ </div>
140
+ {/if}
141
+ </Command.Root>
142
+ </Popover.Content>
143
+ </Popover.Root>
@@ -0,0 +1,42 @@
1
+ import { tv } from "tailwind-variants";
2
+
3
+ /**
4
+ * The one look for a control that opens a picker, and for the plain values
5
+ * that sit in the same column beside it.
6
+ */
7
+ // WHY a recipe: a property row can be a menu trigger, a link or a plain
8
+ // string. All three draw the same box, and only one is a button.
9
+
10
+ // It replaced eight geometries in one sidebar: three heights, three radii, two
11
+ // paddings, and a border on three rows out of eight.
12
+
13
+ // Borderless at rest on purpose. A value that's set is a fact to read, so the
14
+ // box shows only under the pointer and on focus.
15
+ export const pickerTriggerClass = tv({
16
+ base: [
17
+ // 13px at weight 500 is Linear's own property row, measured 31 Aug 2026 on a
18
+ // 16px root. 14px reads looser than the reference, and it shows in the sidebar.
19
+ "t-press inline-flex min-w-0 items-center gap-1.5 rounded-md text-[13px] font-medium whitespace-nowrap select-none",
20
+ "hover:bg-muted data-[state=open]:bg-muted",
21
+ "disabled:pointer-events-none disabled:opacity-50",
22
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0",
23
+ ],
24
+ variants: {
25
+ // Heights match `segmentedTrackClass`, so a strip and a picker line up.
26
+ // Padding is 6px leading, 10px trailing: an even 8px shunted icon rows right.
27
+ size: {
28
+ sm: "h-6 ps-1.5 pe-2 any-pointer-coarse:h-9 any-pointer-coarse:pe-2.5",
29
+ default: "h-7 ps-1.5 pe-2.5 any-pointer-coarse:h-10",
30
+ },
31
+ /**
32
+ * Fill the row. Off by default: Linear's property rows sit at natural width
33
+ * (80px for "Assign"), so a set of rows reads as a set of chips.
34
+ */
35
+ block: { true: "flex w-full", false: "w-fit" },
36
+ /** No value yet: the label is a prompt, so it recedes until hovered. */
37
+ empty: { true: "text-muted-foreground/70 hover:text-muted-foreground", false: "" },
38
+ /** A row that carries no action still needs the column's box. */
39
+ inert: { true: "hover:bg-transparent cursor-default", false: "" },
40
+ },
41
+ defaultVariants: { size: "default", block: false, empty: false, inert: false },
42
+ });
@@ -0,0 +1,26 @@
1
+ import type { PhosphorIcon } from "../../lib/icon-types.js";
2
+
3
+ /** One row in a picker menu. */
4
+ export type PickerOption<V extends string = string> = {
5
+ /** The value written when the row is chosen. */
6
+ value: V;
7
+ label: string;
8
+ /** Right-aligned muted text: a date, a count, a unit. Never a second label. */
9
+ hint?: string;
10
+ /** Phosphor component drawn at the head of the row. */
11
+ icon?: PhosphorIcon;
12
+ /** A mark drawn at the head of a row that has no icon: a logo, an avatar.
13
+ * Drawn by whatever fills `lead`, which is what `MarkPicker` is for. */
14
+ image?: string;
15
+ /** Tone for the icon and, when the row is the current value, for the label. */
16
+ tone?: string;
17
+ /** Extra words the search matches, so "team@" finds a person by email. */
18
+ keywords?: string;
19
+ disabled?: boolean;
20
+ };
21
+
22
+ /** A titled run of options. An untitled group is seamless; a titled one rules off. */
23
+ export type PickerGroup<V extends string = string> = {
24
+ heading?: string;
25
+ options: PickerOption<V>[];
26
+ };
@@ -0,0 +1,175 @@
1
+ <script lang="ts" module>
2
+ import type { SegmentContent } from "./segment.svelte";
3
+
4
+ /** One tab. See `SegmentContent` for everything a segment can say. */
5
+ export type TabDef<T extends string = string> = SegmentContent & { value: T };
6
+
7
+ /**
8
+ * Where the tab row sits, the only thing that legitimately differs between
9
+ * two tab sets. A closed union and not a class string: 23 files each decided
10
+ * their own.
11
+ */
12
+ export type TabsPlacement = "toolbar" | "section" | "inline";
13
+
14
+ const PLACEMENT_WRAPPER: Record<TabsPlacement, string | undefined> = {
15
+ toolbar: undefined,
16
+ section: "shrink-0 border-b border-border py-2",
17
+ inline: undefined,
18
+ };
19
+
20
+ const PLACEMENT_ROOT: Record<TabsPlacement, string | undefined> = {
21
+ toolbar: undefined,
22
+ section: "gap-0",
23
+ inline: "shrink-0",
24
+ };
25
+ </script>
26
+
27
+ <script lang="ts" generics="T extends string">
28
+ import { Tabs as TabsPrimitive } from "bits-ui";
29
+ import { cn } from "@heyramzi/utils";
30
+ import Segment from "./segment.svelte";
31
+ import SegmentedTrack from "./segmented-track.svelte";
32
+ import SegmentedOverflow from "./segmented-overflow.svelte";
33
+ import { SEGMENT_MAX, splitSegments } from "./collapse.js";
34
+ import {
35
+ segmentedActiveClass,
36
+ segmentedTriggerClass,
37
+ type SegmentedVariant,
38
+ } from "./segmented.js";
39
+
40
+ // WHY re-declare `value` and `onValueChange`: the primitive types them as a
41
+ // bare `string`, so fourteen pages cast back to their union. Generic now, the
42
+ // one narrowing happens here.
43
+ type Props = Omit<TabsPrimitive.RootProps, "value" | "onValueChange"> & {
44
+ tabs: readonly TabDef<T>[];
45
+ value?: T;
46
+ onValueChange?: (value: T) => void;
47
+ // Where the row sits. See `TabsPlacement`.
48
+ placement?: TabsPlacement;
49
+ // WHY: when true the track stretches to its container and triggers share width equally.
50
+ // Use for narrow side panels where pickers should fill the row.
51
+ block?: boolean;
52
+ // `underline`: bare labels over a hairline, a travelling rule marks the active one.
53
+ // Same height and overflow menu. Colour the rule with `--segmented-rule` on `class`.
54
+ variant?: SegmentedVariant;
55
+ ariaLabel?: string;
56
+ toolbar?: import("svelte").Snippet;
57
+ // WHY: content ABOVE the tab row but inside the root. A detail pane names the
58
+ // record first, and the row can't leave the root without losing its tabpanel wiring.
59
+ lead?: import("svelte").Snippet;
60
+ // The page's own horizontal gutter, merged over the placement chrome.
61
+ listWrapperClass?: string;
62
+ // WHY: when only one tab is left the pill row is just decoration. Opt in to
63
+ // drop the row entirely (the lone tab's content still renders).
64
+ hideSingle?: boolean;
65
+ // Objects the row may draw before its tail becomes a menu, the menu
66
+ // counted as one of them. See `collapse.ts`.
67
+ max?: number;
68
+ // What the menu segment says. Translate at the call site.
69
+ moreLabel?: string;
70
+ };
71
+
72
+ let {
73
+ ref = $bindable(null),
74
+ value = $bindable(),
75
+ onValueChange,
76
+ tabs,
77
+ placement = "toolbar",
78
+ block = false,
79
+ variant = "pill",
80
+ ariaLabel,
81
+ toolbar,
82
+ lead,
83
+ class: className,
84
+ listWrapperClass,
85
+ hideSingle = false,
86
+ max = SEGMENT_MAX,
87
+ moreLabel,
88
+ children,
89
+ ...restProps
90
+ }: Props = $props();
91
+
92
+ let split = $derived(
93
+ splitSegments(
94
+ tabs,
95
+ max,
96
+ tabs.findIndex((tab) => tab.value === value),
97
+ ),
98
+ );
99
+ </script>
100
+
101
+ <TabsPrimitive.Root
102
+ bind:ref
103
+ value={value ?? ""}
104
+ onValueChange={(next) => {
105
+ // The single narrowing: bits-ui hands back the string it was given, which
106
+ // is one of `tabs[].value`, so it is a `T`.
107
+ const picked = next as T;
108
+ value = picked;
109
+ onValueChange?.(picked);
110
+ }}
111
+ data-slot="tabs"
112
+ class={cn(
113
+ "group/tabs flex max-w-full min-w-0 flex-col gap-2",
114
+ PLACEMENT_ROOT[placement],
115
+ className,
116
+ )}
117
+ {...restProps}
118
+ >
119
+ {@render lead?.()}
120
+
121
+ {#if !(hideSingle && tabs.length <= 1)}
122
+ <!-- WHY scroll, not wrap: it's one object. Seven categories ran 828px,
123
+ past an iPad's edge. Past `max` the tail collapses first. -->
124
+ <div
125
+ class={cn(
126
+ "no-scrollbar flex max-w-full min-w-0 items-center gap-3 overflow-x-auto",
127
+ PLACEMENT_WRAPPER[placement],
128
+ listWrapperClass,
129
+ )}
130
+ >
131
+ <SegmentedTrack fill={block} {variant}>
132
+ <!-- WHY an inner `display: contents` tablist: a tablist owns only tabs, and the
133
+ overflow trigger isn't one. `contents` keeps segments as direct flex children. -->
134
+ <div class="contents" data-slot="tabs-list" role="tablist" aria-label={ariaLabel}>
135
+ {#each split.visible as tab (tab.value)}
136
+ <TabsPrimitive.Trigger
137
+ value={tab.value}
138
+ class={cn(segmentedTriggerClass({ variant, fill: block }), segmentedActiveClass)}
139
+ >
140
+ <Segment
141
+ label={tab.label}
142
+ count={tab.count}
143
+ icon={tab.icon}
144
+ image={tab.image}
145
+ done={tab.done}
146
+ badge={tab.badge}
147
+ active="auto"
148
+ />
149
+ </TabsPrimitive.Trigger>
150
+ {/each}
151
+ </div>
152
+ {#snippet after()}
153
+ {#if split.hidden.length > 0}
154
+ <SegmentedOverflow
155
+ items={split.hidden}
156
+ onSelect={(next) => {
157
+ value = next;
158
+ onValueChange?.(next);
159
+ }}
160
+ label={moreLabel}
161
+ fill={block}
162
+ {variant}
163
+ />
164
+ {/if}
165
+ {/snippet}
166
+ </SegmentedTrack>
167
+
168
+ {#if toolbar}
169
+ {@render toolbar()}
170
+ {/if}
171
+ </div>
172
+ {/if}
173
+
174
+ {@render children?.()}
175
+ </TabsPrimitive.Root>
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from "vite-plus/test";
2
+ import { splitSegments } from "./collapse.js";
3
+
4
+ const items = ["a", "b", "c", "d", "e", "f", "g", "h"] as const;
5
+
6
+ describe("splitSegments", () => {
7
+ it("keeps everything when the row can hold it", () => {
8
+ expect(splitSegments(items.slice(0, 6), 6, 0)).toEqual({
9
+ visible: ["a", "b", "c", "d", "e", "f"],
10
+ hidden: [],
11
+ });
12
+ });
13
+
14
+ it("leaves one slot for the menu once the row overflows", () => {
15
+ const { visible, hidden } = splitSegments(items, 6, 0);
16
+ expect(visible).toEqual(["a", "b", "c", "d", "e"]);
17
+ expect(hidden).toEqual(["f", "g", "h"]);
18
+ });
19
+
20
+ it("pulls the current item into the last visible slot", () => {
21
+ const { visible, hidden } = splitSegments(items, 6, 6);
22
+ expect(visible).toEqual(["a", "b", "c", "d", "g"]);
23
+ expect(hidden).toEqual(["e", "f", "h"]);
24
+ });
25
+
26
+ it("loses nothing whichever item is current", () => {
27
+ for (let i = 0; i < items.length; i++) {
28
+ const { visible, hidden } = splitSegments(items, 6, i);
29
+ expect([...visible, ...hidden].sort()).toEqual([...items].sort());
30
+ expect(visible).toContain(items[i]);
31
+ expect(visible).toHaveLength(5);
32
+ }
33
+ });
34
+
35
+ it("draws the whole strip when nothing is current and it fits", () => {
36
+ expect(splitSegments(items.slice(0, 3), 6, -1).hidden).toEqual([]);
37
+ });
38
+
39
+ it("collapses the tail when nothing is current", () => {
40
+ expect(splitSegments(items, 6, -1)).toEqual({
41
+ visible: ["a", "b", "c", "d", "e"],
42
+ hidden: ["f", "g", "h"],
43
+ });
44
+ });
45
+
46
+ it("refuses to split into a menu plus one segment", () => {
47
+ expect(splitSegments(items, 2, 5).hidden).toEqual([]);
48
+ });
49
+ });
@@ -0,0 +1,39 @@
1
+ /** How a segmented strip that outgrew its row decides what to keep. */
2
+
3
+ // WHY not a scrollbar: a sideways scroll hid four of five tabs
4
+ // behind a gesture nobody makes with a mouse. The tail becomes a menu instead.
5
+
6
+ // WHY a count, not a measurement: the strip sits in a toolbar sized by its own
7
+ // content, so measuring reads its own width back and never collapses.
8
+
9
+ /**
10
+ * The most objects a strip draws before its tail becomes a menu, the menu
11
+ * counted as one of them. Two to six values is where a segmented control reads best.
12
+ */
13
+ export const SEGMENT_MAX = 6;
14
+
15
+ /**
16
+ * Split items into the drawn ones and the menu's. The current item always stays visible.
17
+ * @param max objects the row may draw, the menu counted as one
18
+ */
19
+ export function splitSegments<S>(
20
+ items: readonly S[],
21
+ max: number,
22
+ activeIndex: number,
23
+ ): { visible: S[]; hidden: S[] } {
24
+ // Under three the row is the menu and a single segment beside it, which says
25
+ // less than the menu alone. Below that, and whenever everything fits, no split.
26
+ if (max < 3 || items.length <= max) return { visible: [...items], hidden: [] };
27
+
28
+ const slots = max - 1;
29
+ if (activeIndex < slots && activeIndex >= 0) {
30
+ return { visible: items.slice(0, slots), hidden: items.slice(slots) };
31
+ }
32
+ if (activeIndex < 0 || activeIndex >= items.length) {
33
+ return { visible: items.slice(0, slots), hidden: items.slice(slots) };
34
+ }
35
+ return {
36
+ visible: [...items.slice(0, slots - 1), ...items.slice(activeIndex, activeIndex + 1)],
37
+ hidden: items.filter((_, i) => i >= slots - 1 && i !== activeIndex),
38
+ };
39
+ }