@heyramzi/ui 6.1.0 → 8.0.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 (332) hide show
  1. package/COMPONENTS.md +32 -40
  2. package/README.md +10 -4
  3. package/package.json +31 -29
  4. package/scripts/sync-shadcn.mjs +29 -16
  5. package/scripts/sync-transitions.mjs +11 -11
  6. package/src/ai/ai-button/AiButton.svelte +86 -0
  7. package/src/ai/ai-button/index.ts +4 -0
  8. package/src/ai/confirmation/Confirmation.svelte +8 -8
  9. package/src/ai/confirmation/confirmation-context.svelte.ts +2 -2
  10. package/src/ai/generation-progress/GenerationProgress.svelte +354 -0
  11. package/src/ai/generation-progress/index.ts +7 -0
  12. package/src/ai/index.ts +16 -24
  13. package/src/ai/prompt-input/PromptInput.svelte +19 -1
  14. package/src/ai/prompt-input/PromptInputSubmit.svelte +9 -3
  15. package/src/ai/reasoning/Reasoning.svelte +11 -8
  16. package/src/ambient.d.ts +4 -0
  17. package/src/animations/README.md +17 -17
  18. package/src/animations/index.css +3 -14
  19. package/src/animations/upstream-coverage.test.ts +1 -1
  20. package/src/components/copy/clipboard.test.ts +206 -0
  21. package/src/components/copy/clipboard.ts +49 -0
  22. package/src/components/copy/copied.svelte.ts +39 -0
  23. package/src/components/copy/copy-button.svelte +175 -0
  24. package/src/components/copy/copy-button.test.ts +110 -0
  25. package/src/components/copy/index.ts +3 -0
  26. package/src/components/data-table/data-table.test.ts +134 -0
  27. package/src/components/data-table/data-table.ts +77 -0
  28. package/src/components/data-table/index.ts +20 -0
  29. package/src/components/data-table/infinite-scroll.svelte +69 -0
  30. package/src/components/data-table/pagination.svelte +62 -0
  31. package/src/components/data-table/row-arrow.svelte +28 -0
  32. package/src/components/data-table/row-context-menu.svelte +106 -0
  33. package/src/components/data-table/row-context-menu.test.ts +18 -0
  34. package/src/components/data-table/sortable-head.svelte +56 -0
  35. package/src/components/data-table/table-skeleton.svelte +83 -0
  36. package/src/components/hold-to-confirm/hold-to-confirm.svelte +232 -0
  37. package/src/components/hold-to-confirm/index.ts +1 -0
  38. package/src/components/icon/icon.svelte +23 -0
  39. package/src/components/icon/index.ts +1 -0
  40. package/src/components/language-flag/index.ts +1 -0
  41. package/src/components/language-flag/language-flag.svelte +60 -0
  42. package/src/components/list-page/detail-empty.svelte +50 -0
  43. package/src/components/list-page/detail-header.svelte +107 -0
  44. package/src/components/list-page/detail-panel.svelte +90 -0
  45. package/src/components/list-page/detail-row.svelte +36 -0
  46. package/src/components/list-page/detail-stack.svelte +33 -0
  47. package/src/components/list-page/empty-state.svelte +91 -0
  48. package/src/components/list-page/filter-item.svelte +49 -0
  49. package/src/components/list-page/filter-popover.svelte +88 -0
  50. package/src/components/list-page/filter-section.ts +12 -0
  51. package/src/components/list-page/grouped-list.svelte +116 -0
  52. package/src/components/list-page/index.ts +28 -0
  53. package/src/components/list-page/list-page-shell.svelte +195 -0
  54. package/src/components/list-page/list-table.ts +19 -0
  55. package/src/components/list-page/master-detail.svelte +206 -0
  56. package/src/components/list-page/master-detail.test.ts +37 -0
  57. package/src/components/list-page/notice-bar.svelte +68 -0
  58. package/src/components/list-page/prev-next.svelte +104 -0
  59. package/src/components/list-page/queue-rail.ts +25 -0
  60. package/src/components/list-page/rail-body.svelte +29 -0
  61. package/src/components/list-page/rail-empty.svelte +51 -0
  62. package/src/components/list-page/rail-head.svelte +49 -0
  63. package/src/components/list-page/rail-row.svelte +163 -0
  64. package/src/components/list-page/section-host.ts +24 -0
  65. package/src/components/list-page/section-label.svelte +39 -0
  66. package/src/components/list-page/step.test.ts +21 -0
  67. package/src/components/list-page/step.ts +10 -0
  68. package/src/components/list-page/toolbar-search.svelte +63 -0
  69. package/src/components/list-page/toolbar-trigger.ts +23 -0
  70. package/src/components/list-page/types.ts +1 -0
  71. package/src/components/locale-switch/index.ts +1 -0
  72. package/src/components/locale-switch/locale-switch.svelte +59 -0
  73. package/src/components/mark/index.ts +1 -0
  74. package/src/components/mark/mark.svelte +70 -0
  75. package/src/components/open-in/index.ts +6 -0
  76. package/src/components/open-in/open-in.svelte +106 -0
  77. package/src/components/picker/index.ts +4 -0
  78. package/src/components/picker/mark-picker.svelte +84 -0
  79. package/src/components/picker/picker.svelte +143 -0
  80. package/src/components/picker/trigger.ts +42 -0
  81. package/src/components/picker/types.ts +26 -0
  82. package/src/components/segmented/animated-tabs.svelte +175 -0
  83. package/src/components/segmented/collapse.test.ts +49 -0
  84. package/src/components/segmented/collapse.ts +39 -0
  85. package/src/components/segmented/index.ts +41 -0
  86. package/src/components/segmented/section-runs.svelte +79 -0
  87. package/src/components/segmented/segment.svelte +106 -0
  88. package/src/components/segmented/segmented-filter.svelte +125 -0
  89. package/src/components/segmented/segmented-nav.svelte +82 -0
  90. package/src/components/segmented/segmented-overflow.svelte +85 -0
  91. package/src/components/segmented/segmented-track.svelte +181 -0
  92. package/src/components/segmented/segmented.test.ts +20 -0
  93. package/src/components/segmented/segmented.ts +80 -0
  94. package/src/components/star-rating-input/index.ts +9 -0
  95. package/src/components/star-rating-input/star-rating-input.svelte +256 -0
  96. package/src/components/svg/index.ts +1 -0
  97. package/src/components/svg/svg.svelte +9 -0
  98. package/src/effects/border-beam/BorderBeam.svelte +1 -1
  99. package/src/effects/image-comparison/ImageComparison.css +42 -8
  100. package/src/effects/image-comparison/ImageComparison.svelte +138 -21
  101. package/src/effects/image-comparison/compare.test.ts +17 -0
  102. package/src/effects/image-comparison/compare.ts +5 -0
  103. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +338 -0
  104. package/src/effects/magic-card/MagicCard.css +47 -0
  105. package/src/effects/magic-card/MagicCard.svelte +57 -0
  106. package/src/effects/marquee/Marquee.css +27 -1
  107. package/src/effects/marquee/Marquee.svelte +67 -3
  108. package/src/effects/reveal/index.ts +2 -1
  109. package/src/effects/reveal/observe.test.ts +60 -0
  110. package/src/effects/reveal/observe.ts +33 -1
  111. package/src/effects/reveal/types.ts +7 -0
  112. package/src/effects/theme-toggle/ThemeToggle.css +199 -6
  113. package/src/effects/theme-toggle/ThemeToggle.svelte +51 -26
  114. package/src/gallery.test.ts +52 -0
  115. package/src/index.ts +5 -1
  116. package/src/lib/panel.ts +7 -1
  117. package/src/lib/sliding-pill.ts +68 -0
  118. package/src/lib/theme-transition.ts +41 -13
  119. package/src/lib/tone.ts +48 -0
  120. package/src/shadcn/accordion/accordion-trigger.svelte +15 -6
  121. package/src/shadcn/alert/alert.svelte +3 -1
  122. package/src/shadcn/badge/badge.svelte +8 -7
  123. package/src/shadcn/breadcrumb/breadcrumb-ellipsis.svelte +2 -2
  124. package/src/shadcn/breadcrumb/breadcrumb-separator.svelte +2 -2
  125. package/src/shadcn/calendar/calendar-month-select.svelte +2 -2
  126. package/src/shadcn/calendar/calendar-next-button.svelte +2 -2
  127. package/src/shadcn/calendar/calendar-prev-button.svelte +2 -2
  128. package/src/shadcn/calendar/calendar-year-select.svelte +2 -2
  129. package/src/shadcn/calendar/calendar.svelte +6 -0
  130. package/src/shadcn/code-block/code-block-actions.svelte +23 -0
  131. package/src/shadcn/code-block/code-block-content.svelte +55 -0
  132. package/src/shadcn/code-block/code-block-copy-button.svelte +10 -0
  133. package/src/shadcn/code-block/code-block-footer.svelte +20 -0
  134. package/src/shadcn/code-block/code-block-header.svelte +23 -0
  135. package/src/shadcn/code-block/code-block-line.svelte +71 -0
  136. package/src/shadcn/code-block/code-block-title.svelte +23 -0
  137. package/src/shadcn/code-block/code-block.svelte +47 -0
  138. package/src/shadcn/code-block/index.ts +42 -0
  139. package/src/shadcn/command/index.ts +3 -0
  140. package/src/shadcn/field/index.ts +3 -0
  141. package/src/shadcn/field/simple-field.svelte +71 -0
  142. package/src/shadcn/field/simple-field.test.ts +47 -0
  143. package/src/shadcn/hooks/highlighter.ts +132 -0
  144. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +98 -0
  145. package/src/shadcn/kbd/index.ts +10 -0
  146. package/src/shadcn/kbd/kbd-group.svelte +20 -0
  147. package/src/shadcn/kbd/kbd.svelte +23 -0
  148. package/src/shadcn/message-scroller/index.ts +28 -0
  149. package/src/shadcn/message-scroller/message-scroller-button.svelte +52 -0
  150. package/src/shadcn/message-scroller/message-scroller-content.svelte +17 -0
  151. package/src/shadcn/message-scroller/message-scroller-item.svelte +22 -0
  152. package/src/shadcn/message-scroller/message-scroller-provider.svelte +7 -0
  153. package/src/shadcn/message-scroller/message-scroller-viewport.svelte +20 -0
  154. package/src/shadcn/message-scroller/message-scroller.svelte +20 -0
  155. package/src/shadcn/radio-group/radio-group-item.svelte +3 -2
  156. package/src/shadcn/sheet/sheet-content.svelte +1 -0
  157. package/src/shadcn/sheet/sheet.css +48 -0
  158. package/src/shadcn/sonner/sonner.svelte +7 -10
  159. package/src/shadcn/tabs/tabs-list.svelte +13 -2
  160. package/src/shadcn/tabs/tabs-trigger.svelte +1 -1
  161. package/src/shadcn/toggle-group/toggle-group.svelte +10 -3
  162. package/src/shadcn/trace/index.ts +29 -0
  163. package/src/shadcn/trace/trace-collapsible-content.svelte +29 -0
  164. package/src/shadcn/trace/trace-collapsible-trigger.svelte +29 -0
  165. package/src/shadcn/trace/trace-collapsible.svelte +20 -0
  166. package/src/shadcn/trace/trace-description.svelte +20 -0
  167. package/src/shadcn/trace/trace-icon.svelte +24 -0
  168. package/src/shadcn/trace/trace-line.svelte +31 -0
  169. package/src/shadcn/trace/trace-title.svelte +20 -0
  170. package/src/shadcn/trace/trace.svelte +23 -0
  171. package/src/ai/composer/Composer.css +0 -108
  172. package/src/ai/composer/Composer.svelte +0 -111
  173. package/src/ai/composer/index.ts +0 -10
  174. package/src/ai/conversation/Conversation.svelte +0 -28
  175. package/src/ai/conversation/ConversationContent.svelte +0 -35
  176. package/src/ai/conversation/ConversationEmptyState.svelte +0 -46
  177. package/src/ai/conversation/ConversationScrollButton.svelte +0 -51
  178. package/src/ai/conversation/index.ts +0 -33
  179. package/src/ai/conversation/stick-to-bottom-context.svelte.ts +0 -131
  180. package/src/animations/accordion.css +0 -58
  181. package/src/animations/icon-swap.css +0 -43
  182. package/src/animations/notification-badge.css +0 -81
  183. package/src/animations/panel.css +0 -86
  184. package/src/animations/reasoning-stream.css +0 -49
  185. package/src/animations/skeleton-reveal.css +0 -97
  186. package/src/animations/streaming-text.css +0 -31
  187. package/src/animations/tabs-sliding.css +0 -83
  188. package/src/animations/texts-reveal.css +0 -70
  189. package/src/animations/tooltip.css +0 -72
  190. package/src/animations/view-transition-reveal.ts +0 -74
  191. package/src/charts/animated-value.css +0 -7
  192. package/src/charts/animated-value.svelte +0 -110
  193. package/src/charts/area-chart.css +0 -29
  194. package/src/charts/area-chart.svelte +0 -195
  195. package/src/charts/bar-chart.css +0 -119
  196. package/src/charts/bar-chart.svelte +0 -269
  197. package/src/charts/charts.css +0 -201
  198. package/src/charts/crosshair.svelte +0 -35
  199. package/src/charts/donut-chart.css +0 -56
  200. package/src/charts/donut-chart.svelte +0 -158
  201. package/src/charts/edge-fade.svelte +0 -43
  202. package/src/charts/edge-fade.ts +0 -16
  203. package/src/charts/figure.css +0 -100
  204. package/src/charts/figure.svelte +0 -122
  205. package/src/charts/gauge-chart.css +0 -94
  206. package/src/charts/gauge-chart.svelte +0 -303
  207. package/src/charts/hover-band.test.ts +0 -30
  208. package/src/charts/hover-band.ts +0 -21
  209. package/src/charts/hover-focus.svelte +0 -85
  210. package/src/charts/index.ts +0 -30
  211. package/src/charts/legend.css +0 -131
  212. package/src/charts/legend.svelte +0 -112
  213. package/src/charts/line-chart.css +0 -26
  214. package/src/charts/line-chart.svelte +0 -256
  215. package/src/charts/motion.ts +0 -19
  216. package/src/charts/number-chart.css +0 -168
  217. package/src/charts/number-chart.svelte +0 -177
  218. package/src/charts/primitives/chart-container.svelte +0 -57
  219. package/src/charts/primitives/chart-style.svelte +0 -36
  220. package/src/charts/primitives/chart-tooltip.svelte +0 -161
  221. package/src/charts/primitives/chart-utils.ts +0 -54
  222. package/src/charts/primitives/index.ts +0 -11
  223. package/src/charts/reveal.svelte +0 -61
  224. package/src/charts/ring-chart.css +0 -96
  225. package/src/charts/ring-chart.svelte +0 -158
  226. package/src/charts/sparkline.css +0 -45
  227. package/src/charts/sparkline.svelte +0 -140
  228. package/src/charts/stat-card.css +0 -103
  229. package/src/charts/stat-card.svelte +0 -267
  230. package/src/charts/time-series-chart.css +0 -28
  231. package/src/charts/time-series-chart.svelte +0 -179
  232. package/src/charts/trend-pill.css +0 -38
  233. package/src/charts/trend-pill.svelte +0 -48
  234. package/src/lib/hooks/is-mobile.svelte.ts +0 -11
  235. package/src/lib/utils.ts +0 -45
  236. package/src/showcase/AiSection.svelte +0 -157
  237. package/src/showcase/AnimationsSection.svelte +0 -1658
  238. package/src/showcase/ChartsSection.svelte +0 -458
  239. package/src/showcase/EffectsSection.svelte +0 -152
  240. package/src/showcase/ShadcnSection.svelte +0 -27
  241. package/src/showcase/ShowcaseImport.svelte +0 -126
  242. package/src/showcase/ShowcaseList.svelte +0 -132
  243. package/src/showcase/ShowcaseNav.svelte +0 -247
  244. package/src/showcase/ShowcaseSection.svelte +0 -112
  245. package/src/showcase/ShowcaseShell.svelte +0 -55
  246. package/src/showcase/ShowcaseStage.svelte +0 -342
  247. package/src/showcase/ShowcaseVariant.svelte +0 -109
  248. package/src/showcase/assets/image-reveal/art-1.jpg +0 -0
  249. package/src/showcase/assets/image-reveal/art-2.jpg +0 -0
  250. package/src/showcase/assets/image-reveal/art-3.jpg +0 -0
  251. package/src/showcase/check.svg +0 -1
  252. package/src/showcase/chevron-arms.svg +0 -2
  253. package/src/showcase/chevron-down.svg +0 -1
  254. package/src/showcase/copy.svg +0 -2
  255. package/src/showcase/demos/ai/confirmation.svelte +0 -59
  256. package/src/showcase/demos/ai/conversation.svelte +0 -59
  257. package/src/showcase/demos/ai/loading-state.svelte +0 -19
  258. package/src/showcase/demos/ai/prompt-input.svelte +0 -57
  259. package/src/showcase/demos/ai/reasoning.svelte +0 -19
  260. package/src/showcase/demos/ai/thinking-dots.svelte +0 -15
  261. package/src/showcase/demos/ai/voice-waveform.svelte +0 -24
  262. package/src/showcase/demos/effects/animated-beam.svelte +0 -50
  263. package/src/showcase/demos/effects/animated-group.svelte +0 -47
  264. package/src/showcase/demos/effects/border-beam-default.svelte +0 -20
  265. package/src/showcase/demos/effects/border-beam-line.svelte +0 -20
  266. package/src/showcase/demos/effects/border-beam-mono.svelte +0 -20
  267. package/src/showcase/demos/effects/border-beam-sunset.svelte +0 -20
  268. package/src/showcase/demos/effects/image-comparison.svelte +0 -25
  269. package/src/showcase/demos/effects/image-reveal.svelte +0 -49
  270. package/src/showcase/demos/effects/in-view.svelte +0 -41
  271. package/src/showcase/demos/effects/integrations-hub.svelte +0 -82
  272. package/src/showcase/demos/effects/magnetic.svelte +0 -31
  273. package/src/showcase/demos/effects/marquee.svelte +0 -33
  274. package/src/showcase/demos/effects/progressive-blur.svelte +0 -47
  275. package/src/showcase/demos/effects/spotlight.svelte +0 -40
  276. package/src/showcase/demos/effects/text-effect.svelte +0 -55
  277. package/src/showcase/demos/shadcn/accordion.svelte +0 -45
  278. package/src/showcase/demos/shadcn/alert-dialog.svelte +0 -23
  279. package/src/showcase/demos/shadcn/alert.svelte +0 -30
  280. package/src/showcase/demos/shadcn/attachment.svelte +0 -71
  281. package/src/showcase/demos/shadcn/avatar.svelte +0 -30
  282. package/src/showcase/demos/shadcn/badge.svelte +0 -26
  283. package/src/showcase/demos/shadcn/breadcrumb.svelte +0 -34
  284. package/src/showcase/demos/shadcn/bubble.svelte +0 -36
  285. package/src/showcase/demos/shadcn/button.svelte +0 -11
  286. package/src/showcase/demos/shadcn/calendar.svelte +0 -8
  287. package/src/showcase/demos/shadcn/card.svelte +0 -39
  288. package/src/showcase/demos/shadcn/checkbox.svelte +0 -39
  289. package/src/showcase/demos/shadcn/collapsible.svelte +0 -20
  290. package/src/showcase/demos/shadcn/command.svelte +0 -48
  291. package/src/showcase/demos/shadcn/context-menu.svelte +0 -54
  292. package/src/showcase/demos/shadcn/demo-harness.svelte +0 -12
  293. package/src/showcase/demos/shadcn/demos.test.ts +0 -20
  294. package/src/showcase/demos/shadcn/dialog.svelte +0 -38
  295. package/src/showcase/demos/shadcn/dropdown-menu.svelte +0 -59
  296. package/src/showcase/demos/shadcn/empty.svelte +0 -29
  297. package/src/showcase/demos/shadcn/field.svelte +0 -115
  298. package/src/showcase/demos/shadcn/hover-card.svelte +0 -32
  299. package/src/showcase/demos/shadcn/index.ts +0 -358
  300. package/src/showcase/demos/shadcn/input-otp.svelte +0 -19
  301. package/src/showcase/demos/shadcn/input.svelte +0 -5
  302. package/src/showcase/demos/shadcn/label.svelte +0 -11
  303. package/src/showcase/demos/shadcn/marker.svelte +0 -30
  304. package/src/showcase/demos/shadcn/message.svelte +0 -75
  305. package/src/showcase/demos/shadcn/popover.svelte +0 -36
  306. package/src/showcase/demos/shadcn/progress.svelte +0 -13
  307. package/src/showcase/demos/shadcn/radio-group.svelte +0 -19
  308. package/src/showcase/demos/shadcn/scroll-area.svelte +0 -18
  309. package/src/showcase/demos/shadcn/select.svelte +0 -29
  310. package/src/showcase/demos/shadcn/separator.svelte +0 -12
  311. package/src/showcase/demos/shadcn/sheet.svelte +0 -48
  312. package/src/showcase/demos/shadcn/skeleton.svelte +0 -11
  313. package/src/showcase/demos/shadcn/slider.svelte +0 -7
  314. package/src/showcase/demos/shadcn/sonner.svelte +0 -18
  315. package/src/showcase/demos/shadcn/spinner.svelte +0 -11
  316. package/src/showcase/demos/shadcn/switch.svelte +0 -9
  317. package/src/showcase/demos/shadcn/table.svelte +0 -76
  318. package/src/showcase/demos/shadcn/tabs.svelte +0 -62
  319. package/src/showcase/demos/shadcn/textarea.svelte +0 -5
  320. package/src/showcase/demos/shadcn/toggle-group.svelte +0 -18
  321. package/src/showcase/demos/shadcn/toggle.svelte +0 -14
  322. package/src/showcase/demos/shadcn/tooltip.svelte +0 -13
  323. package/src/showcase/heart.svg +0 -1
  324. package/src/showcase/index.ts +0 -38
  325. package/src/showcase/kit.test.ts +0 -72
  326. package/src/showcase/kit.ts +0 -17
  327. package/src/showcase/plus.svg +0 -1
  328. package/src/showcase/showcase-shell.css +0 -91
  329. package/src/showcase/stacked-section.css +0 -15
  330. package/src/showcase/success-check.svg +0 -1
  331. package/src/showcase/types.ts +0 -29
  332. package/src/svg-raw.d.ts +0 -7
@@ -2,15 +2,20 @@
2
2
  import { CaretDown } from "phosphor-svelte";
3
3
  import { Accordion as AccordionPrimitive } from "bits-ui";
4
4
  import { cn, type WithoutChild } from "@heyramzi/utils";
5
+ import type { Snippet } from "svelte";
5
6
 
6
7
  let {
7
8
  ref = $bindable(null),
8
9
  class: className,
9
10
  level = 3,
11
+ indicator,
10
12
  children,
11
13
  ...restProps
12
14
  }: WithoutChild<AccordionPrimitive.TriggerProps> & {
13
15
  level?: AccordionPrimitive.HeaderProps["level"];
16
+ /** Replaces the caret, e.g. a plus that turns into a minus. Style it off
17
+ * `group-aria-expanded/accordion-trigger:`. */
18
+ indicator?: Snippet;
14
19
  } = $props();
15
20
  </script>
16
21
 
@@ -25,11 +30,15 @@
25
30
  {...restProps}
26
31
  >
27
32
  {@render children?.()}
28
- <!-- One caret that turns over, not two that swap. A transform and not a
29
- `d:` path morph, which is Chromium-only. -->
30
- <CaretDown
31
- data-slot="accordion-trigger-icon"
32
- class="cn-accordion-trigger-icon pointer-events-none shrink-0 transition-transform duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)] group-aria-expanded/accordion-trigger:rotate-180 motion-reduce:transition-none"
33
- />
33
+ {#if indicator}
34
+ {@render indicator()}
35
+ {:else}
36
+ <!-- One caret that turns over, not two that swap. A transform and not a
37
+ `d:` path morph, which is Chromium-only. -->
38
+ <CaretDown
39
+ data-slot="accordion-trigger-icon"
40
+ class="cn-accordion-trigger-icon pointer-events-none shrink-0 transition-transform duration-[var(--duration-fast)] ease-[var(--ease-smooth-out)] group-aria-expanded/accordion-trigger:rotate-180 motion-reduce:transition-none"
41
+ />
42
+ {/if}
34
43
  </AccordionPrimitive.Trigger>
35
44
  </AccordionPrimitive.Header>
@@ -26,6 +26,8 @@
26
26
  ref = $bindable(null),
27
27
  class: className,
28
28
  variant = "default",
29
+ // A caller whose message isn't urgent passes `status`, so a screen reader doesn't interrupt.
30
+ role = "alert",
29
31
  children,
30
32
  ...restProps
31
33
  }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
@@ -38,7 +40,7 @@
38
40
  data-slot="alert"
39
41
  class={cn(alertVariants({ variant }), className)}
40
42
  {...restProps}
41
- role="alert"
43
+ {role}
42
44
  >
43
45
  {@render children?.()}
44
46
  </div>
@@ -4,16 +4,13 @@
4
4
  export const badgeVariants = tv({
5
5
  base: "h-5 gap-1 rounded-full border border-transparent px-2 py-0.5 text-xs font-medium has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive group/badge inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap transition-[background-color,border-color,color,box-shadow] focus-visible:ring-[3px] [&>svg]:pointer-events-none",
6
6
  variants: {
7
- // Three weights a label can carry: filled, tinted, outlined. Severity is
8
- // `StatusBadge` reading `TONE_CLASS`, not a badge colour. `destructive`,
9
- // `warning`, `ghost` and `link` went on 4 Sep 2026.
7
+ // Three weights a label can carry: filled, tinted, outlined. Severity is the
8
+ // `tone` prop, which reads `TONE_CLASS`, not a variant.
10
9
  variant: {
11
10
  default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
12
11
  secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
13
12
  outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
14
- // Kept when the three weights landed: severity is `StatusBadge` reading its tone, but a consumer outside this workspace still needs one badge that says "this is wrong" without a status
15
-
16
- // scale behind it.
13
+ // For a consumer that wants one "this is wrong" badge without a tone prop.
17
14
  destructive: "bg-destructive/10 text-destructive [a]:hover:bg-destructive/20",
18
15
  },
19
16
  },
@@ -28,16 +25,20 @@
28
25
  <script lang="ts">
29
26
  import type { HTMLAnchorAttributes } from "svelte/elements";
30
27
  import { cn, type WithElementRef } from "@heyramzi/utils";
28
+ import { TONE_CLASS, type Tone } from "../../lib/tone.js";
31
29
 
32
30
  let {
33
31
  ref = $bindable(null),
34
32
  href,
35
33
  class: className,
36
34
  variant = "default",
35
+ tone,
37
36
  children,
38
37
  ...restProps
39
38
  }: WithElementRef<HTMLAnchorAttributes> & {
40
39
  variant?: BadgeVariant;
40
+ /** The severity scale. Draws an outlined chip in the tone's colours and ignores `variant`. */
41
+ tone?: Tone;
41
42
  } = $props();
42
43
  </script>
43
44
 
@@ -46,7 +47,7 @@
46
47
  bind:this={ref}
47
48
  data-slot="badge"
48
49
  {href}
49
- class={cn(badgeVariants({ variant }), className)}
50
+ class={cn(badgeVariants({ variant: tone ? "outline" : variant }), tone && TONE_CLASS[tone], className)}
50
51
  {...restProps}
51
52
  >
52
53
  {@render children?.()}
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
2
+ import { DotsThree } from "phosphor-svelte";
3
3
  import type { HTMLAttributes } from "svelte/elements";
4
4
  import { cn, type WithElementRef, type WithoutChildren } from "@heyramzi/utils";
5
5
 
@@ -18,6 +18,6 @@
18
18
  class={cn("flex size-9 items-center justify-center", className)}
19
19
  {...restProps}
20
20
  >
21
- <EllipsisIcon class="size-4" />
21
+ <DotsThree class="size-4" />
22
22
  <span class="sr-only">More</span>
23
23
  </span>
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
2
+ import { CaretRight } from "phosphor-svelte";
3
3
  import { cn, type WithElementRef } from "@heyramzi/utils";
4
4
  import type { HTMLLiAttributes } from "svelte/elements";
5
5
 
@@ -22,6 +22,6 @@
22
22
  {#if children}
23
23
  {@render children?.()}
24
24
  {:else}
25
- <ChevronRightIcon />
25
+ <CaretRight />
26
26
  {/if}
27
27
  </li>
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { Calendar as CalendarPrimitive } from "bits-ui";
3
3
  import { cn, type WithoutChildrenOrChild } from "@heyramzi/utils";
4
- import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
4
+ import { CaretDown } from "phosphor-svelte";
5
5
 
6
6
  let {
7
7
  ref = $bindable(null),
@@ -37,7 +37,7 @@
37
37
  aria-hidden="true"
38
38
  >
39
39
  {monthItems.find((item) => item.value === value)?.label || selectedMonthItem.label}
40
- <ChevronDownIcon class="size-4" />
40
+ <CaretDown class="size-4" />
41
41
  </span>
42
42
  {/snippet}
43
43
  </CalendarPrimitive.MonthSelect>
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { Calendar as CalendarPrimitive } from "bits-ui";
3
- import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
3
+ import { CaretRight } from "phosphor-svelte";
4
4
  import { buttonVariants, type ButtonVariant } from "../button/index.js";
5
5
  import { cn } from "@heyramzi/utils";
6
6
 
@@ -16,7 +16,7 @@
16
16
  </script>
17
17
 
18
18
  {#snippet Fallback()}
19
- <ChevronRightIcon class="size-4" />
19
+ <CaretRight class="size-4" />
20
20
  {/snippet}
21
21
 
22
22
  <CalendarPrimitive.NextButton
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { Calendar as CalendarPrimitive } from "bits-ui";
3
- import ChevronLeftIcon from "@lucide/svelte/icons/chevron-left";
3
+ import { CaretLeft } from "phosphor-svelte";
4
4
  import { buttonVariants, type ButtonVariant } from "../button/index.js";
5
5
  import { cn } from "@heyramzi/utils";
6
6
 
@@ -16,7 +16,7 @@
16
16
  </script>
17
17
 
18
18
  {#snippet Fallback()}
19
- <ChevronLeftIcon class="size-4" />
19
+ <CaretLeft class="size-4" />
20
20
  {/snippet}
21
21
 
22
22
  <CalendarPrimitive.PrevButton
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { Calendar as CalendarPrimitive } from "bits-ui";
3
3
  import { cn, type WithoutChildrenOrChild } from "@heyramzi/utils";
4
- import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
4
+ import { CaretDown } from "phosphor-svelte";
5
5
 
6
6
  let {
7
7
  ref = $bindable(null),
@@ -36,7 +36,7 @@
36
36
  aria-hidden="true"
37
37
  >
38
38
  {yearItems.find((item) => item.value === value)?.label || selectedYearItem.label}
39
- <ChevronDownIcon class="size-4" />
39
+ <CaretDown class="size-4" />
40
40
  </span>
41
41
  {/snippet}
42
42
  </CalendarPrimitive.YearSelect>
@@ -24,6 +24,10 @@
24
24
  yearFormat = "numeric",
25
25
  day,
26
26
  disableDaysOutsideMonth = false,
27
+ // Monday first and six rows always: a five-week month would otherwise resize
28
+ // the popover as the reader pages through the year.
29
+ weekStartsOn = 1,
30
+ fixedWeeks = true,
27
31
  ...restProps
28
32
  }: WithoutChildrenOrChild<CalendarPrimitive.RootProps> & {
29
33
  buttonVariant?: ButtonVariant;
@@ -52,6 +56,8 @@ get along, so we shut typescript up by casting `value` to `never`.
52
56
  bind:placeholder
53
57
  {weekdayFormat}
54
58
  {disableDaysOutsideMonth}
59
+ {weekStartsOn}
60
+ {fixedWeeks}
55
61
  class={cn(
56
62
  "bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
57
63
  className,
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "@heyramzi/utils";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
11
+ </script>
12
+
13
+ <div
14
+ bind:this={ref}
15
+ data-slot="code-block-actions"
16
+ class={cn(
17
+ "-my-1 -me-1.5 gap-1 [[data-slot=code-block]>&]:m-0 [[data-slot=code-block]>&]:top-3 [[data-slot=code-block]>&]:end-3 group-data-[size=sm]/code-block:[[data-slot=code-block]>&]:top-2 group-data-[size=sm]/code-block:[[data-slot=code-block]>&]:end-2 [[data-slot=code-block]>&]:*:[button]:bg-background [[data-slot=code-block]>&]:*:[button]:ring-1 [[data-slot=code-block]>&]:*:[button]:ring-foreground/10 ms-auto flex shrink-0 items-center [[data-slot=code-block]>&]:absolute [[data-slot=code-block]>&]:z-10 [[data-slot=code-block]>&]:opacity-0 [[data-slot=code-block]>&]:transition-opacity [[data-slot=code-block]>&]:group-focus-within/code-block:opacity-100 [[data-slot=code-block]>&]:group-hover/code-block:opacity-100 [[data-slot=code-block]>&]:pointer-coarse:opacity-100",
18
+ className
19
+ )}
20
+ {...restProps}
21
+ >
22
+ {@render children?.()}
23
+ </div>
@@ -0,0 +1,55 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "@heyramzi/utils";
3
+ import CodeBlockLine from "./code-block-line.svelte";
4
+ import type { HTMLAttributes } from "svelte/elements";
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ style,
10
+ code,
11
+ wrap = false,
12
+ lineNumbers = false,
13
+ startLine = 1,
14
+ children,
15
+ ...restProps
16
+ }: WithElementRef<HTMLAttributes<HTMLPreElement>> & {
17
+ /** Plain text to render. Split into lines when `lineNumbers` is set. */
18
+ code?: string;
19
+ wrap?: boolean;
20
+ lineNumbers?: boolean;
21
+ startLine?: number;
22
+ } = $props();
23
+
24
+ // A `code` string is split into lines so each one can be numbered.
25
+ const lines = $derived(
26
+ code !== undefined && lineNumbers ? code.replace(/\n$/, "").split("\n") : null
27
+ );
28
+ </script>
29
+
30
+ {#snippet content()}
31
+ <code
32
+ data-slot="code-block-code"class=" grid min-h-lh min-w-max content-start in-data-numbered:grid-cols-[auto_1fr] in-data-wrap:min-w-0">
33
+ {#if lines}
34
+ {#each lines as line, index (index)}
35
+ <CodeBlockLine code={line} />
36
+ {/each}
37
+ {:else if code !== undefined}
38
+ {code}
39
+ {:else}
40
+ {@render children?.()}
41
+ {/if}
42
+ </code>
43
+ {/snippet}
44
+
45
+ <!-- Whitespace inside a <pre> is rendered, so its content is a snippet defined outside it, where Svelte trims whitespace as usual. -->
46
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
47
+ <pre
48
+ bind:this={ref}
49
+ data-slot="code-block-content"data-wrap={wrap || undefined}
50
+ data-numbered={lineNumbers || undefined}
51
+ dir="ltr"tabindex={0}
52
+ style="counter-reset: line {startLine - 1};{style ?? ''}"class={cn("px-4 py-4 leading-relaxed group-has-data-[slot=code-block-header]/code-block:pt-2 group-has-data-[slot=code-block-footer]/code-block:pb-2 group-data-[size=sm]/code-block:px-3 group-data-[size=sm]/code-block:py-3 group-data-[size=sm]/code-block:group-has-data-[slot=code-block-header]/code-block:pt-1.5 min-h-0 min-w-0 scroll-fade-x scrollbar-thin overflow-auto text-start font-mono outline-none data-wrap:scroll-fade-y data-wrap:wrap-break-word data-wrap:whitespace-pre-wrap",
53
+ className
54
+ )}
55
+ {...restProps}>{@render content()}</pre>
@@ -0,0 +1,10 @@
1
+ <script lang="ts">
2
+ import type { ComponentProps } from "svelte";
3
+ import CopyButton from "../../components/copy/copy-button.svelte";
4
+
5
+ // WHY this wraps the shared control: the code block's own copy button drew a
6
+ // second icon swap and a second clipboard write. `md` is the 28px icon Button.
7
+ let { size = "md", ...restProps }: ComponentProps<typeof CopyButton> = $props();
8
+ </script>
9
+
10
+ <CopyButton data-slot="code-block-copy-button" {size} {...restProps} />
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "@heyramzi/utils";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
11
+ </script>
12
+
13
+ <div
14
+ bind:this={ref}
15
+ data-slot="code-block-footer"
16
+ class={cn("gap-2 px-4 pb-4 text-muted-foreground group-data-[size=sm]/code-block:px-3 group-data-[size=sm]/code-block:pb-3 flex min-w-0 shrink-0 items-center", className)}
17
+ {...restProps}
18
+ >
19
+ {@render children?.()}
20
+ </div>
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "@heyramzi/utils";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
11
+ </script>
12
+
13
+ <div
14
+ bind:this={ref}
15
+ data-slot="code-block-header"
16
+ class={cn(
17
+ "gap-2 px-4 pt-4 text-muted-foreground group-data-[variant=destructive]/code-block:text-destructive group-data-[size=sm]/code-block:px-3 group-data-[size=sm]/code-block:pt-3 flex min-w-0 shrink-0 items-center *:has-[>[data-slot=code-block-title]]:min-w-0",
18
+ className
19
+ )}
20
+ {...restProps}
21
+ >
22
+ {@render children?.()}
23
+ </div>
@@ -0,0 +1,71 @@
1
+ <script lang="ts" module>
2
+ import { tv, type VariantProps } from "tailwind-variants";
3
+
4
+ export const codeBlockLineVariants = tv({
5
+ base: "-mx-4 px-4 in-data-numbered:gap-x-3 before:min-w-6 before:text-end before:text-muted-foreground after:start-1 after:text-muted-foreground group-data-[size=sm]/code-block:-mx-3 group-data-[size=sm]/code-block:px-3 relative block min-h-lh after:absolute after:top-0 after:select-none in-data-numbered:col-span-2 in-data-numbered:grid in-data-numbered:grid-cols-subgrid in-data-numbered:[counter-increment:line] in-data-numbered:before:content-[counter(line)] in-data-numbered:before:select-none in-data-numbered:supports-[content:counter(line)_/_'']:before:content-[counter(line)_/_'']",
6
+ variants: {
7
+ variant: {
8
+ default: "",
9
+ added: "bg-muted after:content-['+']",
10
+ removed: "bg-destructive/10 after:text-destructive after:content-['−']",
11
+ highlighted: "bg-muted",
12
+ },
13
+ },
14
+ defaultVariants: {
15
+ variant: "default",
16
+ },
17
+ });
18
+
19
+ export type CodeBlockLineVariant = VariantProps<typeof codeBlockLineVariants>["variant"];
20
+
21
+ // One highlighted token, such as one returned by `useHighlightedCode`.
22
+ export type CodeBlockLineToken = {
23
+ content: string;
24
+ style?: string;
25
+ class?: string;
26
+ };
27
+ </script>
28
+
29
+ <script lang="ts">
30
+ import { cn, type WithElementRef } from "@heyramzi/utils";
31
+ import type { HTMLAttributes } from "svelte/elements";
32
+
33
+ let {
34
+ ref = $bindable(null),
35
+ class: className,
36
+ variant = "default",
37
+ code,
38
+ children,
39
+ ...restProps
40
+ }: WithElementRef<HTMLAttributes<HTMLSpanElement>> & {
41
+ variant?: CodeBlockLineVariant;
42
+ /** The line as plain text, or as highlighted tokens. When it is set, `children` is ignored. */
43
+ code?: string | CodeBlockLineToken[];
44
+ } = $props();
45
+
46
+ const newline = "\n";
47
+ </script>
48
+
49
+ <!-- An empty line holds a newline so copying a selection keeps blank lines. -->
50
+ <span
51
+ bind:this={ref}
52
+ data-slot="code-block-line"data-variant={variant}
53
+ class={cn(codeBlockLineVariants({ variant }), className)}
54
+ {...restProps}
55
+ >
56
+ <span data-slot="code-block-line-content"class="min-w-0">
57
+ {#if typeof code ==="string"}
58
+ {code || newline}
59
+ {:else if code?.length}
60
+ {#each code as token, index (index)}
61
+ <span style={token.style} class={token.class}>{token.content}</span>
62
+ {/each}
63
+ {:else if code}
64
+ {newline}
65
+ {:else if children}
66
+ {@render children()}
67
+ {:else}
68
+ {newline}
69
+ {/if}
70
+ </span>
71
+ </span>
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "@heyramzi/utils";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
11
+ </script>
12
+
13
+ <div
14
+ bind:this={ref}
15
+ data-slot="code-block-title"
16
+ class={cn(
17
+ "font-sans text-sm font-medium text-foreground group-data-[variant=destructive]/code-block:text-destructive *:[svg]:me-1.5 [&_svg:not([class*='size-'])]:size-4 group-data-[size=sm]/code-block:text-xs group-data-[size=sm]/code-block:[&_svg:not([class*='size-'])]:size-3.5 min-w-0 truncate [&_svg]:pointer-events-none [&_svg]:inline-block [&_svg]:align-middle",
18
+ className
19
+ )}
20
+ {...restProps}
21
+ >
22
+ {@render children?.()}
23
+ </div>
@@ -0,0 +1,47 @@
1
+ <script lang="ts" module>
2
+ import { tv, type VariantProps } from "tailwind-variants";
3
+
4
+ export const codeBlockVariants = tv({
5
+ base: "rounded-lg text-sm data-[size=sm]:text-xs has-[>[data-slot=code-block-content]:focus-visible]:ring-3 has-[>[data-slot=code-block-content]:focus-visible]:ring-ring/50 group/code-block relative flex w-full min-w-0 flex-col overflow-hidden",
6
+ variants: {
7
+ variant: {
8
+ default: "border border-input text-foreground dark:bg-input/30 has-[>[data-slot=code-block-content]:focus-visible]:border-ring",
9
+ destructive: "border border-destructive/30 text-destructive dark:border-destructive/40",
10
+ },
11
+ },
12
+ defaultVariants: {
13
+ variant: "default",
14
+ },
15
+ });
16
+
17
+ export type CodeBlockVariant = VariantProps<typeof codeBlockVariants>["variant"];
18
+ export type CodeBlockSize = "default" | "sm";
19
+ </script>
20
+
21
+ <script lang="ts">
22
+ import { cn, type WithElementRef } from "@heyramzi/utils";
23
+ import type { HTMLAttributes } from "svelte/elements";
24
+
25
+ let {
26
+ ref = $bindable(null),
27
+ class: className,
28
+ variant = "default",
29
+ size = "default",
30
+ children,
31
+ ...restProps
32
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
33
+ variant?: CodeBlockVariant;
34
+ size?: CodeBlockSize;
35
+ } = $props();
36
+ </script>
37
+
38
+ <div
39
+ bind:this={ref}
40
+ data-slot="code-block"
41
+ data-variant={variant}
42
+ data-size={size}
43
+ class={cn(codeBlockVariants({ variant }), className)}
44
+ {...restProps}
45
+ >
46
+ {@render children?.()}
47
+ </div>
@@ -0,0 +1,42 @@
1
+ import Actions from "./code-block-actions.svelte";
2
+ import Content from "./code-block-content.svelte";
3
+ import CopyButton from "./code-block-copy-button.svelte";
4
+ import Footer from "./code-block-footer.svelte";
5
+ import Header from "./code-block-header.svelte";
6
+ import Title from "./code-block-title.svelte";
7
+ import Line, {
8
+ codeBlockLineVariants,
9
+ type CodeBlockLineToken,
10
+ type CodeBlockLineVariant,
11
+ } from "./code-block-line.svelte";
12
+ import Root, {
13
+ codeBlockVariants,
14
+ type CodeBlockSize,
15
+ type CodeBlockVariant,
16
+ } from "./code-block.svelte";
17
+
18
+ export {
19
+ Root,
20
+ Header,
21
+ Title,
22
+ Actions,
23
+ CopyButton,
24
+ Content,
25
+ Line,
26
+ Footer,
27
+ codeBlockVariants,
28
+ codeBlockLineVariants,
29
+ type CodeBlockVariant,
30
+ type CodeBlockSize,
31
+ type CodeBlockLineVariant,
32
+ type CodeBlockLineToken,
33
+ //
34
+ Root as CodeBlock,
35
+ Header as CodeBlockHeader,
36
+ Title as CodeBlockTitle,
37
+ Actions as CodeBlockActions,
38
+ CopyButton as CodeBlockCopyButton,
39
+ Content as CodeBlockContent,
40
+ Line as CodeBlockLine,
41
+ Footer as CodeBlockFooter,
42
+ };
@@ -1,3 +1,4 @@
1
+ import { computeCommandScore } from "bits-ui";
1
2
  import Root from "./command.svelte";
2
3
  import Loading from "./command-loading.svelte";
3
4
  import Dialog from "./command-dialog.svelte";
@@ -10,7 +11,9 @@ import Separator from "./command-separator.svelte";
10
11
  import Shortcut from "./command-shortcut.svelte";
11
12
  import LinkItem from "./command-link-item.svelte";
12
13
 
14
+ // The scorer Command uses by default, for a custom `filter` that wants to rank its own words.
13
15
  export {
16
+ computeCommandScore,
14
17
  Root,
15
18
  Dialog,
16
19
  Empty,
@@ -8,6 +8,7 @@ import Title from "./field-title.svelte";
8
8
  import Description from "./field-description.svelte";
9
9
  import Separator from "./field-separator.svelte";
10
10
  import Error from "./field-error.svelte";
11
+ import Simple from "./simple-field.svelte";
11
12
 
12
13
  export {
13
14
  Field,
@@ -20,6 +21,7 @@ export {
20
21
  Description,
21
22
  Separator,
22
23
  Error,
24
+ Simple,
23
25
  //
24
26
  Set as FieldSet,
25
27
  Legend as FieldLegend,
@@ -30,4 +32,5 @@ export {
30
32
  Description as FieldDescription,
31
33
  Separator as FieldSeparator,
32
34
  Error as FieldError,
35
+ Simple as SimpleField,
33
36
  };
@@ -0,0 +1,71 @@
1
+ <script lang="ts">
2
+ import { cn } from "@heyramzi/utils";
3
+ import type { Snippet } from "svelte";
4
+ import type { Attachment } from "svelte/attachments";
5
+ import Field from "./field.svelte";
6
+ import FieldLabel from "./field-label.svelte";
7
+ import FieldDescription from "./field-description.svelte";
8
+ import FieldError from "./field-error.svelte";
9
+
10
+ interface Props {
11
+ label?: string;
12
+ description?: string;
13
+ error?: string;
14
+ /** Id of the control the label binds to. Also picks the control that gets the message link. */
15
+ for?: string;
16
+ /** "default" is text-sm foreground; "muted" is a text-xs micro-label. */
17
+ labelVariant?: "default" | "muted";
18
+ class?: string;
19
+ children: Snippet;
20
+ }
21
+
22
+ let {
23
+ label,
24
+ description,
25
+ error,
26
+ for: htmlFor,
27
+ labelVariant = "default",
28
+ class: className,
29
+ children,
30
+ }: Props = $props();
31
+
32
+ const uid = $props.id();
33
+ const messageId = `${uid}-message`;
34
+ let message = $derived(error ?? description);
35
+
36
+ // WHY an attachment: the control is the caller's snippet, so the wrapper can't
37
+ // set its props. Wiring the DOM here means no call site can forget the link.
38
+ const CONTROL = "input, textarea, select, [role=combobox], [role=switch], [role=checkbox]";
39
+ const link: Attachment<HTMLElement> = (node) => {
40
+ if (!message) return;
41
+ const control =
42
+ (htmlFor ? node.querySelector<HTMLElement>(`[id="${CSS.escape(htmlFor)}"]`) : null) ??
43
+ node.querySelector<HTMLElement>(CONTROL);
44
+ if (!control) return;
45
+ const before = control.getAttribute("aria-describedby");
46
+ control.setAttribute("aria-describedby", [before, messageId].filter(Boolean).join(" "));
47
+ if (error) control.setAttribute("aria-invalid", "true");
48
+ return () => {
49
+ if (before === null) control.removeAttribute("aria-describedby");
50
+ else control.setAttribute("aria-describedby", before);
51
+ if (error) control.removeAttribute("aria-invalid");
52
+ };
53
+ };
54
+ </script>
55
+
56
+ <Field class={cn("gap-2", className)} data-invalid={error ? "true" : undefined} {@attach link}>
57
+ {#if label}
58
+ <FieldLabel
59
+ for={htmlFor}
60
+ class={labelVariant === "muted" ? "text-muted-foreground text-xs" : undefined}
61
+ >
62
+ {label}
63
+ </FieldLabel>
64
+ {/if}
65
+ {@render children()}
66
+ {#if error}
67
+ <FieldError id={messageId} class="text-xs">{error}</FieldError>
68
+ {:else if description}
69
+ <FieldDescription id={messageId} class="text-xs">{description}</FieldDescription>
70
+ {/if}
71
+ </Field>