@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,186 @@
1
+ <script lang="ts">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import type { Snippet } from "svelte";
6
+ import { cn } from "@heyramzi/utils";
7
+ import { segmentedTrackClass, type SegmentedVariant } from "./segmented.js";
8
+ import { slidingPill } from "../../lib/sliding-pill.js";
9
+
10
+ // =========================
11
+ // TYPES
12
+ // =========================
13
+ interface Props {
14
+ /** Fill the row rather than sit at its natural width. */
15
+ fill?: boolean;
16
+ /** `pill`, a bordered well with a travelling highlight, or `underline`, a travelling rule. */
17
+ variant?: SegmentedVariant;
18
+ class?: string;
19
+ children: Snippet;
20
+ /**
21
+ * A last segment that isn't an item: today the overflow trigger. It's inside
22
+ * the track but outside the ARIA container, so a `tablist` owns only tabs.
23
+ */
24
+ after?: Snippet;
25
+ // What the ARIA shapes differ by: `role`/`aria-label` for the filter, nothing
26
+ // for the nav (its <nav> has it), bits-ui's wiring for tabs.
27
+ [key: string]: unknown;
28
+ };
29
+
30
+ // =========================
31
+ // PROPS
32
+ // =========================
33
+ let {
34
+ fill = false,
35
+ variant = "pill",
36
+ class: className,
37
+ children,
38
+ after,
39
+ ...rest
40
+ }: Props = $props();
41
+ </script>
42
+
43
+ <!-- =========================== -->
44
+ <!-- MARKUP -->
45
+ <!-- =========================== -->
46
+ <!-- WHY its own component: the active segment is a travelling pill, and the
47
+ pill needs one element inside the track that no caller can forget. -->
48
+ <div
49
+ {...rest}
50
+ data-variant={variant}
51
+ class={cn("track relative", segmentedTrackClass({ variant, fill }), className)}
52
+ >
53
+ <!-- Renders at zero width until the attachment measures, so a server-rendered
54
+ strip shows the segment's own background and never an empty pill. -->
55
+ <span class="pill" aria-hidden="true" {@attach slidingPill}></span>
56
+ {@render children()}
57
+ {@render after?.()}
58
+ </div>
59
+
60
+ <!-- =========================== -->
61
+ <!-- STYLES -->
62
+ <!-- =========================== -->
63
+ <style>
64
+ /* Inset separators keep the strip one bordered object. */
65
+ .track :global(.ui-segment)::before {
66
+ content: "";
67
+ position: absolute;
68
+ top: 22%;
69
+ bottom: 22%;
70
+ left: 0;
71
+ width: 1px;
72
+ background-color: var(--color-border);
73
+ transition: opacity var(--duration-fast) var(--ease-smooth-out);
74
+ pointer-events: none;
75
+ }
76
+
77
+ .track :global(.pill + .ui-segment)::before,
78
+ .track :global([data-slot="tabs-list"] > .ui-segment:first-child)::before {
79
+ display: none;
80
+ }
81
+
82
+ /* The highlight and hover wash hide the seams on either side. */
83
+ .track :global(.ui-segment[data-state="active"])::before,
84
+ .track :global(.ui-segment[aria-current="page"])::before,
85
+ .track :global(.ui-segment[aria-pressed="true"])::before,
86
+ .track :global(.ui-segment[data-state="active"] + .ui-segment)::before,
87
+ .track :global(.ui-segment[aria-current="page"] + .ui-segment)::before,
88
+ .track :global(.ui-segment[aria-pressed="true"] + .ui-segment)::before,
89
+ .track :global([data-slot="tabs-list"]:has(> .ui-segment:last-child[data-state="active"]) + .ui-segment)::before {
90
+ opacity: 0;
91
+ }
92
+
93
+ @media (hover: hover) and (pointer: fine) {
94
+ .track :global(.ui-segment:hover)::before,
95
+ .track :global(.ui-segment:hover + .ui-segment)::before {
96
+ opacity: 0;
97
+ }
98
+ }
99
+
100
+ /* The label's colour lands with the pill, on the same fast step. */
101
+ .track :global(.ui-segment) {
102
+ transition:
103
+ background-color var(--duration-quick) var(--ease-smooth-out),
104
+ color var(--duration-fast) var(--ease-smooth-out),
105
+ transform var(--duration-micro) var(--ease-smooth-out);
106
+ }
107
+
108
+ /* A faint wash in the well says which segment the pointer would pick. */
109
+ @media (hover: hover) {
110
+ .track
111
+ :global(
112
+ .ui-segment:hover:not([data-state="active"], [aria-current="page"], [aria-pressed="true"], [data-state="open"])
113
+ ) {
114
+ background-color: color-mix(in oklch, var(--color-muted) 55%, transparent);
115
+ }
116
+ }
117
+
118
+ .pill {
119
+ position: absolute;
120
+ top: 2px;
121
+ bottom: 2px;
122
+ left: 0;
123
+ z-index: 0;
124
+ width: 0;
125
+ opacity: 0;
126
+ /* Concentric with the border and the 2px inset. */
127
+ border-radius: calc(var(--radius-lg) - 3px);
128
+ background-color: var(--color-muted);
129
+ box-shadow: inset 0 0 0 1px color-mix(in oklch, var(--color-foreground) 6%, transparent);
130
+ transform: translateX(0);
131
+ /* transitions-dev `16-tabs-sliding`: the fast step, 250ms. */
132
+ transition:
133
+ transform var(--duration-fast) var(--ease-smooth-out),
134
+ width var(--duration-fast) var(--ease-smooth-out),
135
+ opacity var(--duration-quick) var(--ease-smooth-out);
136
+ will-change: transform, width;
137
+ pointer-events: none;
138
+ }
139
+
140
+ /* Before the attachment measures, the active segment draws the pill itself,
141
+ so a server-rendered strip is right before any JS arrives. */
142
+ .track:not([data-pill="on"]) :global(:is([data-state="active"], [aria-current="page"], [aria-pressed="true"])) {
143
+ background-color: var(--color-muted);
144
+ box-shadow: inset 0 0 0 1px color-mix(in oklch, var(--color-foreground) 6%, transparent);
145
+ }
146
+
147
+ /* The underline: no seams, no wash, and the pill becomes a 2px rule on the
148
+ hairline. `--segmented-rule` lets a brand colour it from a class. */
149
+ .track[data-variant="underline"] :global(.ui-segment)::before {
150
+ display: none;
151
+ }
152
+
153
+ @media (hover: hover) {
154
+ .track[data-variant="underline"]
155
+ :global(
156
+ .ui-segment:hover:not([data-state="active"], [aria-current="page"], [aria-pressed="true"], [data-state="open"])
157
+ ) {
158
+ background-color: transparent;
159
+ }
160
+ }
161
+
162
+ .track[data-variant="underline"] .pill {
163
+ top: auto;
164
+ bottom: -1px;
165
+ height: 2px;
166
+ border-radius: 1px;
167
+ background-color: var(--segmented-rule, var(--color-foreground));
168
+ box-shadow: none;
169
+ }
170
+
171
+ .track[data-variant="underline"]:not([data-pill="on"])
172
+ :global(:is([data-state="active"], [aria-current="page"], [aria-pressed="true"])) {
173
+ background-color: transparent;
174
+ box-shadow: inset 0 -2px 0 var(--segmented-rule, var(--color-foreground));
175
+ }
176
+
177
+ @media (prefers-reduced-motion: reduce) {
178
+ .pill {
179
+ transition: opacity var(--duration-quick) var(--ease-smooth-out);
180
+ }
181
+
182
+ .track :global(.ui-segment) {
183
+ transition: none;
184
+ }
185
+ }
186
+ </style>
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from "vite-plus/test";
2
+ import { segmentedTrackClass } from "./segmented.js";
3
+
4
+ describe("segmentedTrackClass", () => {
5
+ // Every segmented control shares one height, whatever its look.
6
+ it("holds one height across variants", () => {
7
+ for (const variant of ["pill", "underline"] as const) {
8
+ const classes = segmentedTrackClass({ variant }).split(" ");
9
+ expect(classes).toContain("h-7");
10
+ expect(classes).toContain("any-pointer-coarse:h-10");
11
+ }
12
+ });
13
+
14
+ it("draws the pill as a bordered well and the underline as a hairline", () => {
15
+ expect(segmentedTrackClass({ variant: "pill" })).toContain("rounded-lg");
16
+ const underline = segmentedTrackClass({ variant: "underline" });
17
+ expect(underline).toContain("border-b");
18
+ expect(underline).not.toContain("rounded-lg");
19
+ });
20
+ });
@@ -0,0 +1,80 @@
1
+ import { tv } from "tailwind-variants";
2
+ import { pageGutterClass } from "../../lib/layout.js";
3
+
4
+ /**
5
+ * The one look for a segmented control: the track and trigger here, what a
6
+ * segment says in `segment.svelte`.
7
+ */
8
+
9
+ // WHY a recipe, not one component: tabs are a `tablist`, filters a `group` of
10
+ // `aria-pressed` toggles, page links a `nav`. Only the ARIA shape may differ.
11
+
12
+ // The look used to drift three ways, one with an at-rest shadow. All three read from here now.
13
+ export const segmentedTrackClass = tv({
14
+ // WHY the height lives on the track: it carries the inset, so an `h-7` trigger
15
+ // drew 30px beside a 28px button. One toolbar row came out at five heights.
16
+
17
+ // WHY no size variant: `sm` drew 24px, and every tab control must share one height.
18
+ base: "h-7 any-pointer-coarse:h-10 shrink-0 items-stretch",
19
+ variants: {
20
+ // `pill`: a bordered well with a travelling highlight. `underline`: bare
21
+ // labels over one hairline, the active one marked by a rule that travels.
22
+ variant: {
23
+ pill: "overflow-hidden rounded-lg border border-border p-0.5",
24
+ underline: "gap-5 border-b border-border",
25
+ },
26
+ // Fill the row rather than sit at its natural width. For a narrow rail.
27
+ fill: { true: "flex w-full", false: "inline-flex w-fit" },
28
+ },
29
+ defaultVariants: { variant: "pill", fill: false },
30
+ });
31
+
32
+ export type SegmentedVariant = "pill" | "underline";
33
+
34
+ export const segmentedTriggerClass = tv({
35
+ base: [
36
+ // `t-press` is the kit's one press: colour, shadow and a pixel of travel.
37
+
38
+ // `relative z-[1]` keeps the label above the travelling pill, or the pill
39
+ // paints over the word it highlights.
40
+
41
+ // `ui-segment` isn't a style. `segmented-track.svelte` matches on it to time
42
+ // the label colour with the pill.
43
+ "ui-segment group/segment t-press relative z-[1] inline-flex items-center justify-center gap-1.5 whitespace-nowrap font-medium select-none",
44
+ "text-muted-foreground hover:text-foreground",
45
+ "disabled:pointer-events-none disabled:opacity-50",
46
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
47
+ // WHY an inset focus ring: the track clips to its rounded corners, so an
48
+ // outside ring was cut in half on the end segments.
49
+ "focus-visible:ring-ring focus-visible:ring-2 focus-visible:ring-inset focus-visible:outline-none",
50
+ // WHY no height on the trigger: an icon makes a segment taller than a word.
51
+ // `h-full` against the track's `items-stretch` keeps every segment the same box.
52
+ "h-full text-xs",
53
+ ],
54
+ variants: {
55
+ // The pill is concentric with the track's border and its 2px inset. The
56
+ // underline has no well to be concentric with, so no padding either: the
57
+ // rule under a label is as wide as the label.
58
+ variant: {
59
+ pill: "rounded-[calc(var(--radius-lg)-3px)] px-2.5 any-pointer-coarse:px-3.5",
60
+ underline: "rounded-sm px-0",
61
+ },
62
+ // WHY `min-w-fit flex-auto`, not equal fractions: fractions cut "Approved" to
63
+ // "Appro…" in a 280px rail. Each segment claims its label and shares the slack.
64
+ fill: { true: "min-w-fit flex-auto", false: "" },
65
+ active: { true: "text-foreground", false: "" },
66
+ },
67
+ defaultVariants: { variant: "pill", fill: false, active: false },
68
+ });
69
+
70
+ /**
71
+ * The row a segmented strip sits in, and the one recipe for a navigation row
72
+ * under the app header. Written per caller it produced two different left
73
+ * edges.
74
+ */
75
+ // WHY no bottom hairline: with a section strip and a page toolbar stacked it
76
+ // drew two rules for one boundary. The gap is the boundary.
77
+ export const segmentedRowClass = `flex shrink-0 flex-wrap items-center gap-x-4 gap-y-2 py-2 ${pageGutterClass}`;
78
+
79
+ /** The active style, for a strip whose state lives in a `data-active` attribute. */
80
+ export const segmentedActiveClass = "data-active:text-foreground";
@@ -0,0 +1,9 @@
1
+ import Root from "./star-rating-input.svelte";
2
+
3
+ export type { StarRatingSize } from "./star-rating-input.svelte";
4
+
5
+ export {
6
+ Root,
7
+ //
8
+ Root as StarRatingInput,
9
+ };
@@ -0,0 +1,263 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * Two sizes, picked by the job: `sm` reads a value back in a row or cell,
4
+ * `lg` is the ask.
5
+ */
6
+ // A middle `md` step used to exist. Nobody could place it, so the same question
7
+ // ended up asked at three sizes.
8
+ export type StarRatingSize = "sm" | "lg";
9
+
10
+ const STAR_SIZE: Record<StarRatingSize, string> = {
11
+ sm: "size-4",
12
+ lg: "size-7",
13
+ };
14
+
15
+ const HIT_SIZE: Record<StarRatingSize, string> = {
16
+ sm: "p-0.5",
17
+ lg: "p-1",
18
+ };
19
+
20
+ /** Pop duration plus the longest stagger, after which the class can come off. */
21
+ const POP_WINDOW_MS = 350 + 4 * 40;
22
+ </script>
23
+
24
+ <script lang="ts">
25
+ import { Star } from "phosphor-svelte";
26
+ import { cn } from "@heyramzi/utils";
27
+
28
+ interface Props {
29
+ /** Current rating value (0 = unrated). */
30
+ value: number;
31
+ /** Maximum rating. Defaults to 5. */
32
+ max?: number;
33
+ /** Visual size of each star. `sm` reads back, `lg` asks. */
34
+ size?: StarRatingSize;
35
+ /** Render as static display, no hover or click. */
36
+ readonly?: boolean;
37
+ /** Disable interaction. */
38
+ disabled?: boolean;
39
+ /** Form field name for native submission. When set, a hidden input is rendered. */
40
+ name?: string;
41
+ /** ARIA label for the radiogroup. */
42
+ ariaLabel?: string;
43
+ /** Per-step descriptive labels (length must equal max). */
44
+ labels?: readonly string[];
45
+ /** Show the label of the currently hovered/selected step beside the stars. */
46
+ showLabel?: boolean;
47
+ class?: string;
48
+ onchange?: (value: number) => void;
49
+ };
50
+
51
+ let {
52
+ value = $bindable(0),
53
+ max = 5,
54
+ size = "sm",
55
+ readonly = false,
56
+ disabled = false,
57
+ name,
58
+ ariaLabel,
59
+ labels,
60
+ showLabel = false,
61
+ class: className,
62
+ onchange,
63
+ }: Props = $props();
64
+
65
+ let hover = $state(0);
66
+ /** Held for the length of the pop, then dropped so the next pick can replay it. */
67
+ let popping = $state(false);
68
+ let popTimer: ReturnType<typeof setTimeout> | undefined;
69
+ let displayed = $derived(hover || value);
70
+ let starSize = $derived(STAR_SIZE[size]);
71
+ let hitSize = $derived(HIT_SIZE[size]);
72
+ let groupAriaLabel = $derived(ariaLabel ?? `Rating, ${max} stars`);
73
+ let labelText = $derived(
74
+ displayed > 0 && labels && labels.length >= displayed ? labels[displayed - 1] : "",
75
+ );
76
+
77
+ function buttonAriaLabel(step: number): string {
78
+ const stepLabel = labels?.[step - 1];
79
+ const base = `${step} of ${max}`;
80
+ return stepLabel ? `${base} (${stepLabel})` : base;
81
+ }
82
+
83
+ function setRating(step: number) {
84
+ if (readonly || disabled) return;
85
+ value = step;
86
+ onchange?.(step);
87
+
88
+ // Drop the class before re-adding it, or a second pick inside the animation
89
+ // window finds it already set and nothing replays.
90
+ popping = false;
91
+ clearTimeout(popTimer);
92
+ requestAnimationFrame(() => {
93
+ popping = true;
94
+ popTimer = setTimeout(() => (popping = false), POP_WINDOW_MS);
95
+ });
96
+ }
97
+
98
+ function handleEnter(step: number) {
99
+ if (readonly || disabled) return;
100
+ hover = step;
101
+ }
102
+
103
+ function handleLeave() {
104
+ if (readonly || disabled) return;
105
+ hover = 0;
106
+ }
107
+ </script>
108
+
109
+ <!-- One star, drawn once. It used to be written twice, once per branch, and the
110
+ two copies had already drifted apart on the glow. -->
111
+ {#snippet star(step: number)}
112
+ <Star
113
+ class={cn("star", starSize, step <= displayed && "is-filled")}
114
+ weight={step <= displayed ? "fill" : "regular"}
115
+ />
116
+ {/snippet}
117
+
118
+ <div class={cn("rating", `rating-${size}`, className)}>
119
+ <div
120
+ class={cn("stars", popping && "is-popping")}
121
+ role={readonly ? "img" : "radiogroup"}
122
+ aria-label={groupAriaLabel}
123
+ onmouseleave={handleLeave}
124
+ >
125
+ {#each Array(max) as _, i (i)}
126
+ {@const step = i + 1}
127
+ {#if readonly}
128
+ <span class={cn("hit", hitSize)}>{@render star(step)}</span>
129
+ {:else}
130
+ <button
131
+ type="button"
132
+ role="radio"
133
+ aria-checked={value === step}
134
+ aria-label={buttonAriaLabel(step)}
135
+ {disabled}
136
+ data-on={step <= value}
137
+ onclick={() => setRating(step)}
138
+ onmouseenter={() => handleEnter(step)}
139
+ onfocus={() => handleEnter(step)}
140
+ onblur={handleLeave}
141
+ style:--pop-delay="{i * 40}ms"
142
+ class={cn("hit hit-button", hitSize, disabled && "is-disabled")}
143
+ >
144
+ {@render star(step)}
145
+ </button>
146
+ {/if}
147
+ {/each}
148
+ </div>
149
+
150
+ {#if showLabel && labelText}
151
+ <span class="rating-caption">{labelText}</span>
152
+ {/if}
153
+
154
+ {#if name}
155
+ <input type="hidden" {name} value={value} />
156
+ {/if}
157
+ </div>
158
+
159
+ <style>
160
+ .rating {
161
+ --like-pop: var(--duration-medium);
162
+ --like-pop-ease: var(--ease-smooth-out);
163
+ --like-fill: var(--duration-quick);
164
+ --like-ease: var(--ease-smooth-out);
165
+
166
+ display: flex;
167
+ align-items: center;
168
+ gap: 0.25rem;
169
+ }
170
+
171
+ .stars {
172
+ display: flex;
173
+ align-items: center;
174
+ }
175
+
176
+ .hit {
177
+ display: inline-flex;
178
+ border-radius: var(--radius-sm);
179
+ }
180
+
181
+ .hit-button {
182
+ transition: transform var(--duration-quick, 150ms) var(--ease-smooth-out, ease);
183
+ }
184
+
185
+ .hit-button:focus-visible {
186
+ outline: none;
187
+ box-shadow: 0 0 0 2px var(--color-ring);
188
+ }
189
+
190
+ @media (hover: hover) and (pointer: fine) {
191
+ .hit-button:not(.is-disabled):hover {
192
+ transform: scale(1.1);
193
+ }
194
+ }
195
+
196
+ .hit-button:not(.is-disabled):active {
197
+ transform: scale(0.95);
198
+ }
199
+
200
+ .is-disabled {
201
+ cursor: not-allowed;
202
+ opacity: 0.6;
203
+ }
204
+
205
+ .rating :global(.star) {
206
+ color: var(--color-border);
207
+ transition: color var(--like-fill) var(--like-ease);
208
+ }
209
+
210
+ .rating :global(.star.is-filled) {
211
+ color: var(--color-warning-400);
212
+ }
213
+
214
+ /* The glow is the size's business, not a prop: it belongs to the ask, where
215
+ the stars are large enough to carry it, and it turned to mud at 16px. */
216
+ .rating-lg :global(.star.is-filled) {
217
+ filter: drop-shadow(0 0 4px color-mix(in oklab, var(--color-warning-400) 45%, transparent));
218
+ }
219
+
220
+ /* transitions.dev like-button pop. Every selected star pops, left to right,
221
+ as one gesture. A class on the group drives it, since rebuilding the row
222
+ would drop keyboard focus. */
223
+ .is-popping .hit-button[data-on="true"] {
224
+ animation: t-like-pop var(--like-pop) var(--like-pop-ease) var(--pop-delay, 0ms);
225
+ }
226
+
227
+ @keyframes t-like-pop {
228
+ 0% {
229
+ transform: scale(1);
230
+ }
231
+ 30% {
232
+ transform: scale(0.82);
233
+ }
234
+ 100% {
235
+ transform: scale(1);
236
+ }
237
+ }
238
+
239
+ .rating-caption {
240
+ font-size: 0.75rem;
241
+ font-weight: 500;
242
+ font-variant-numeric: tabular-nums;
243
+ color: var(--color-muted-foreground);
244
+ }
245
+
246
+ @media (prefers-reduced-motion: reduce) {
247
+ .hit-button,
248
+ .is-popping .hit-button[data-on="true"] {
249
+ animation: none;
250
+ transition: none;
251
+ }
252
+
253
+ .hit-button:not(.is-disabled):active {
254
+ transform: none;
255
+ }
256
+
257
+ @media (hover: hover) and (pointer: fine) {
258
+ .hit-button:not(.is-disabled):hover {
259
+ transform: none;
260
+ }
261
+ }
262
+ }
263
+ </style>
@@ -0,0 +1 @@
1
+ export { default as Svg } from "./svg.svelte";
@@ -0,0 +1,9 @@
1
+ <script lang="ts">
2
+ // Renders a drawing kept in its own .svg file, imported with `?raw`.
3
+ // The markup is ours and static, so rendering it as HTML is safe.
4
+ let { src, class: className = "" }: { src: string; class?: string } = $props();
5
+
6
+ const markup = $derived(className ? src.replace("<svg", `<svg class="${className}"`) : src);
7
+ </script>
8
+
9
+ {@html markup}