@heyramzi/ui 7.0.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 (253) hide show
  1. package/COMPONENTS.md +28 -17
  2. package/README.md +5 -1
  3. package/package.json +25 -15
  4. package/scripts/sync-shadcn.mjs +17 -13
  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 -6
  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 +14 -14
  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/theme-transition.ts +37 -11
  118. package/src/lib/tone.ts +48 -0
  119. package/src/shadcn/accordion/accordion-trigger.svelte +15 -6
  120. package/src/shadcn/alert/alert.svelte +3 -1
  121. package/src/shadcn/badge/badge.svelte +8 -7
  122. package/src/shadcn/breadcrumb/breadcrumb-ellipsis.svelte +2 -2
  123. package/src/shadcn/breadcrumb/breadcrumb-separator.svelte +2 -2
  124. package/src/shadcn/calendar/calendar-month-select.svelte +2 -2
  125. package/src/shadcn/calendar/calendar-next-button.svelte +2 -2
  126. package/src/shadcn/calendar/calendar-prev-button.svelte +2 -2
  127. package/src/shadcn/calendar/calendar-year-select.svelte +2 -2
  128. package/src/shadcn/calendar/calendar.svelte +6 -0
  129. package/src/shadcn/code-block/code-block-copy-button.svelte +6 -55
  130. package/src/shadcn/command/index.ts +3 -0
  131. package/src/shadcn/field/index.ts +3 -0
  132. package/src/shadcn/field/simple-field.svelte +71 -0
  133. package/src/shadcn/field/simple-field.test.ts +47 -0
  134. package/src/shadcn/message-scroller/message-scroller-button.svelte +2 -2
  135. package/src/shadcn/radio-group/radio-group-item.svelte +3 -2
  136. package/src/shadcn/sheet/sheet-content.svelte +1 -0
  137. package/src/shadcn/sheet/sheet.css +48 -0
  138. package/src/shadcn/sonner/sonner.svelte +7 -10
  139. package/src/shadcn/toggle-group/toggle-group.svelte +10 -3
  140. package/src/shadcn/trace/trace-collapsible-trigger.svelte +2 -2
  141. package/src/ai/composer/Composer.css +0 -108
  142. package/src/ai/composer/Composer.svelte +0 -111
  143. package/src/ai/composer/index.ts +0 -10
  144. package/src/animations/accordion.css +0 -58
  145. package/src/animations/icon-swap.css +0 -43
  146. package/src/animations/notification-badge.css +0 -81
  147. package/src/animations/panel.css +0 -86
  148. package/src/animations/reasoning-stream.css +0 -49
  149. package/src/animations/skeleton-reveal.css +0 -97
  150. package/src/animations/streaming-text.css +0 -31
  151. package/src/animations/tabs-sliding.css +0 -83
  152. package/src/animations/texts-reveal.css +0 -70
  153. package/src/animations/tooltip.css +0 -72
  154. package/src/animations/view-transition-reveal.ts +0 -74
  155. package/src/lib/utils.ts +0 -45
  156. package/src/showcase/AiSection.svelte +0 -150
  157. package/src/showcase/AnimationsSection.svelte +0 -1658
  158. package/src/showcase/EffectsSection.svelte +0 -152
  159. package/src/showcase/ShadcnSection.svelte +0 -27
  160. package/src/showcase/ShowcaseImport.svelte +0 -126
  161. package/src/showcase/ShowcaseList.svelte +0 -132
  162. package/src/showcase/ShowcaseNav.svelte +0 -247
  163. package/src/showcase/ShowcaseSection.svelte +0 -112
  164. package/src/showcase/ShowcaseShell.svelte +0 -55
  165. package/src/showcase/ShowcaseStage.svelte +0 -342
  166. package/src/showcase/ShowcaseVariant.svelte +0 -109
  167. package/src/showcase/assets/image-reveal/art-1.jpg +0 -0
  168. package/src/showcase/assets/image-reveal/art-2.jpg +0 -0
  169. package/src/showcase/assets/image-reveal/art-3.jpg +0 -0
  170. package/src/showcase/check.svg +0 -1
  171. package/src/showcase/chevron-arms.svg +0 -2
  172. package/src/showcase/chevron-down.svg +0 -1
  173. package/src/showcase/copy.svg +0 -2
  174. package/src/showcase/demos/ai/confirmation.svelte +0 -59
  175. package/src/showcase/demos/ai/loading-state.svelte +0 -19
  176. package/src/showcase/demos/ai/prompt-input.svelte +0 -57
  177. package/src/showcase/demos/ai/reasoning.svelte +0 -19
  178. package/src/showcase/demos/ai/thinking-dots.svelte +0 -15
  179. package/src/showcase/demos/ai/voice-waveform.svelte +0 -24
  180. package/src/showcase/demos/effects/animated-beam.svelte +0 -50
  181. package/src/showcase/demos/effects/animated-group.svelte +0 -47
  182. package/src/showcase/demos/effects/border-beam-default.svelte +0 -20
  183. package/src/showcase/demos/effects/border-beam-line.svelte +0 -20
  184. package/src/showcase/demos/effects/border-beam-mono.svelte +0 -20
  185. package/src/showcase/demos/effects/border-beam-sunset.svelte +0 -20
  186. package/src/showcase/demos/effects/image-comparison.svelte +0 -25
  187. package/src/showcase/demos/effects/image-reveal.svelte +0 -49
  188. package/src/showcase/demos/effects/in-view.svelte +0 -41
  189. package/src/showcase/demos/effects/integrations-hub.svelte +0 -82
  190. package/src/showcase/demos/effects/magnetic.svelte +0 -31
  191. package/src/showcase/demos/effects/marquee.svelte +0 -33
  192. package/src/showcase/demos/effects/progressive-blur.svelte +0 -47
  193. package/src/showcase/demos/effects/spotlight.svelte +0 -40
  194. package/src/showcase/demos/effects/text-effect.svelte +0 -55
  195. package/src/showcase/demos/shadcn/accordion.svelte +0 -45
  196. package/src/showcase/demos/shadcn/alert-dialog.svelte +0 -23
  197. package/src/showcase/demos/shadcn/alert.svelte +0 -30
  198. package/src/showcase/demos/shadcn/attachment.svelte +0 -71
  199. package/src/showcase/demos/shadcn/avatar.svelte +0 -30
  200. package/src/showcase/demos/shadcn/badge.svelte +0 -26
  201. package/src/showcase/demos/shadcn/breadcrumb.svelte +0 -34
  202. package/src/showcase/demos/shadcn/bubble.svelte +0 -36
  203. package/src/showcase/demos/shadcn/button.svelte +0 -11
  204. package/src/showcase/demos/shadcn/calendar.svelte +0 -8
  205. package/src/showcase/demos/shadcn/card.svelte +0 -39
  206. package/src/showcase/demos/shadcn/checkbox.svelte +0 -39
  207. package/src/showcase/demos/shadcn/code-block.svelte +0 -80
  208. package/src/showcase/demos/shadcn/collapsible.svelte +0 -20
  209. package/src/showcase/demos/shadcn/command.svelte +0 -48
  210. package/src/showcase/demos/shadcn/context-menu.svelte +0 -54
  211. package/src/showcase/demos/shadcn/demo-harness.svelte +0 -12
  212. package/src/showcase/demos/shadcn/demos.test.ts +0 -20
  213. package/src/showcase/demos/shadcn/dialog.svelte +0 -38
  214. package/src/showcase/demos/shadcn/dropdown-menu.svelte +0 -59
  215. package/src/showcase/demos/shadcn/empty.svelte +0 -29
  216. package/src/showcase/demos/shadcn/field.svelte +0 -115
  217. package/src/showcase/demos/shadcn/hover-card.svelte +0 -32
  218. package/src/showcase/demos/shadcn/index.ts +0 -382
  219. package/src/showcase/demos/shadcn/input-otp.svelte +0 -19
  220. package/src/showcase/demos/shadcn/input.svelte +0 -5
  221. package/src/showcase/demos/shadcn/label.svelte +0 -11
  222. package/src/showcase/demos/shadcn/marker.svelte +0 -30
  223. package/src/showcase/demos/shadcn/message-scroller.svelte +0 -44
  224. package/src/showcase/demos/shadcn/message.svelte +0 -75
  225. package/src/showcase/demos/shadcn/popover.svelte +0 -36
  226. package/src/showcase/demos/shadcn/progress.svelte +0 -13
  227. package/src/showcase/demos/shadcn/radio-group.svelte +0 -19
  228. package/src/showcase/demos/shadcn/scroll-area.svelte +0 -18
  229. package/src/showcase/demos/shadcn/select.svelte +0 -29
  230. package/src/showcase/demos/shadcn/separator.svelte +0 -12
  231. package/src/showcase/demos/shadcn/sheet.svelte +0 -48
  232. package/src/showcase/demos/shadcn/skeleton.svelte +0 -11
  233. package/src/showcase/demos/shadcn/slider.svelte +0 -7
  234. package/src/showcase/demos/shadcn/sonner.svelte +0 -18
  235. package/src/showcase/demos/shadcn/spinner.svelte +0 -11
  236. package/src/showcase/demos/shadcn/switch.svelte +0 -9
  237. package/src/showcase/demos/shadcn/table.svelte +0 -76
  238. package/src/showcase/demos/shadcn/tabs.svelte +0 -62
  239. package/src/showcase/demos/shadcn/textarea.svelte +0 -5
  240. package/src/showcase/demos/shadcn/toggle-group.svelte +0 -18
  241. package/src/showcase/demos/shadcn/toggle.svelte +0 -14
  242. package/src/showcase/demos/shadcn/tooltip.svelte +0 -13
  243. package/src/showcase/demos/shadcn/trace.svelte +0 -51
  244. package/src/showcase/heart.svg +0 -1
  245. package/src/showcase/index.ts +0 -38
  246. package/src/showcase/kit.test.ts +0 -72
  247. package/src/showcase/kit.ts +0 -16
  248. package/src/showcase/plus.svg +0 -1
  249. package/src/showcase/showcase-shell.css +0 -91
  250. package/src/showcase/stacked-section.css +0 -15
  251. package/src/showcase/success-check.svg +0 -1
  252. package/src/showcase/types.ts +0 -29
  253. package/src/svg-raw.d.ts +0 -7
@@ -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,
@@ -1,59 +1,10 @@
1
1
  <script lang="ts">
2
- import { onDestroy } from "svelte";
3
- import CheckIcon from "@lucide/svelte/icons/check";
4
- import CopyIcon from "@lucide/svelte/icons/copy";
5
- import { Button, type ButtonProps } from "../button/index.js";
6
- import { cn } from "@heyramzi/utils";
2
+ import type { ComponentProps } from "svelte";
3
+ import CopyButton from "../../components/copy/copy-button.svelte";
7
4
 
8
- let {
9
- ref = $bindable(null),
10
- class: className,
11
- value,
12
- variant = "ghost",
13
- size = "sm",
14
- onclick,
15
- ...restProps
16
- }: Omit<ButtonProps, "onclick"> & {
17
- value: string;
18
- onclick?: (e: MouseEvent) => void;
19
- } = $props();
20
-
21
- let copied = $state(false);
22
- let timeout: ReturnType<typeof setTimeout> | undefined;
23
-
24
- function copy() {
25
- navigator.clipboard
26
- .writeText(value)
27
- .then(() => {
28
- copied = true;
29
- clearTimeout(timeout);
30
- timeout = setTimeout(() => (copied = false), 2000);
31
- })
32
- .catch(() => {});
33
- }
34
-
35
- onDestroy(() => clearTimeout(timeout));
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();
36
8
  </script>
37
9
 
38
- <Button
39
- bind:ref
40
- data-slot="code-block-copy-button"
41
- data-copied={copied || undefined}
42
- {variant}
43
- {size}
44
- icon
45
- aria-label={copied ? "Copied" : "Copy"}
46
- class={cn(className)}
47
- onclick={(e) => {
48
- onclick?.(e);
49
- copy();
50
- }}
51
- {...restProps}
52
- >
53
- {#if copied}
54
- <CheckIcon />
55
- {:else}
56
- <CopyIcon />
57
- {/if}
58
- </Button>
59
- <span aria-live="polite" class="sr-only">{copied ? "Copied" : ""}</span>
10
+ <CopyButton data-slot="code-block-copy-button" {size} {...restProps} />
@@ -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>
@@ -0,0 +1,47 @@
1
+ import { createRawSnippet, flushSync, mount, unmount } from "svelte";
2
+ import { describe, expect, it } from "vite-plus/test";
3
+ import SimpleField from "./simple-field.svelte";
4
+
5
+ const input = createRawSnippet(() => ({ render: () => `<input id="email" />` }));
6
+
7
+ function render(props: { error?: string; description?: string }) {
8
+ const target = document.createElement("div");
9
+ document.body.appendChild(target);
10
+ const app = mount(SimpleField, { target, props: { label: "Email", for: "email", children: input, ...props } });
11
+ flushSync();
12
+ return {
13
+ target,
14
+ done: () => {
15
+ void unmount(app);
16
+ target.remove();
17
+ },
18
+ };
19
+ }
20
+
21
+ describe("SimpleField", () => {
22
+ it("links the error to its input and marks it invalid", () => {
23
+ const { target, done } = render({ error: "Enter a valid email" });
24
+ const control = target.querySelector("input")!;
25
+ const message = target.querySelector("[data-slot=field-error]")!;
26
+ expect(message.textContent).toContain("Enter a valid email");
27
+ expect(message.getAttribute("role")).toBe("alert");
28
+ expect(control.getAttribute("aria-describedby")).toBe(message.id);
29
+ expect(control.getAttribute("aria-invalid")).toBe("true");
30
+ done();
31
+ });
32
+
33
+ it("links the description when there is no error", () => {
34
+ const { target, done } = render({ description: "We never share it" });
35
+ const control = target.querySelector("input")!;
36
+ const message = target.querySelector("[data-slot=field-description]")!;
37
+ expect(control.getAttribute("aria-describedby")).toBe(message.id);
38
+ expect(control.hasAttribute("aria-invalid")).toBe(false);
39
+ done();
40
+ });
41
+
42
+ it("leaves the input alone when there is nothing to say", () => {
43
+ const { target, done } = render({});
44
+ expect(target.querySelector("input")!.hasAttribute("aria-describedby")).toBe(false);
45
+ done();
46
+ });
47
+ });
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { MessageScroller as MessageScrollerPrimitive } from "@shadcn-svelte/primitives/message-scroller";
3
- import ArrowDownIcon from "@lucide/svelte/icons/arrow-down";
3
+ import { ArrowDown } from "phosphor-svelte";
4
4
  import { Button } from "../button/index.js";
5
5
  import type { ButtonSize, ButtonVariant } from "../button/index.js";
6
6
  import { cn } from "@heyramzi/utils";
@@ -41,7 +41,7 @@
41
41
  {#if children}
42
42
  {@render children()}
43
43
  {:else}
44
- <ArrowDownIcon />
44
+ <ArrowDown />
45
45
  <span class="sr-only">
46
46
  {direction === "end" ? "Scroll to end" : "Scroll to start"}
47
47
  </span>
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
3
- import CircleIcon from "@lucide/svelte/icons/circle";
3
+ import { Circle } from "phosphor-svelte";
4
4
  import { cn, type WithoutChildrenOrChild } from "@heyramzi/utils";
5
5
 
6
6
  let {
@@ -22,7 +22,8 @@
22
22
  {#snippet children({ checked })}
23
23
  <div data-slot="radio-group-indicator" class="relative flex items-center justify-center">
24
24
  {#if checked}
25
- <CircleIcon
25
+ <Circle
26
+ weight="fill"
26
27
  class="fill-primary absolute start-1/2 top-1/2 size-2 -translate-x-1/2 -translate-y-1/2"
27
28
  />
28
29
  {/if}
@@ -4,6 +4,7 @@
4
4
  </script>
5
5
 
6
6
  <script lang="ts">
7
+ import "./sheet.css";
7
8
  import { Dialog as SheetPrimitive } from "bits-ui";
8
9
  import type { Snippet } from "svelte";
9
10
  import SheetPortal from "./sheet-portal.svelte";
@@ -0,0 +1,48 @@
1
+ /* The sheet slide: transitions.dev panel reveal, owned by the sheet. */
2
+
3
+ :root {
4
+ --sheet-open-dur: 300ms;
5
+ --sheet-close-dur: 250ms;
6
+ --sheet-translate: 100%;
7
+ --sheet-ease: cubic-bezier(0.22, 1, 0.36, 1);
8
+ }
9
+
10
+ .t-sheet {
11
+ opacity: 0;
12
+ pointer-events: none;
13
+ transition:
14
+ transform var(--sheet-close-dur) var(--sheet-ease),
15
+ opacity var(--sheet-close-dur) var(--sheet-ease);
16
+ will-change: transform, opacity;
17
+ }
18
+
19
+ .t-sheet[data-side="right"] {
20
+ transform: translateX(var(--sheet-translate));
21
+ }
22
+
23
+ .t-sheet[data-side="left"] {
24
+ transform: translateX(calc(-1 * var(--sheet-translate)));
25
+ }
26
+
27
+ .t-sheet[data-side="top"] {
28
+ transform: translateY(calc(-1 * var(--sheet-translate)));
29
+ }
30
+
31
+ .t-sheet[data-side="bottom"] {
32
+ transform: translateY(var(--sheet-translate));
33
+ }
34
+
35
+ .t-sheet[data-state="open"] {
36
+ transform: translate(0, 0);
37
+ opacity: 1;
38
+ pointer-events: auto;
39
+ transition:
40
+ transform var(--sheet-open-dur) var(--sheet-ease),
41
+ opacity var(--sheet-open-dur) var(--sheet-ease);
42
+ }
43
+
44
+ @media (prefers-reduced-motion: reduce) {
45
+ .t-sheet {
46
+ transition: none !important;
47
+ }
48
+ }
@@ -1,10 +1,7 @@
1
1
  <script lang="ts">
2
- import CircleCheckIcon from "@lucide/svelte/icons/circle-check";
3
- import InfoIcon from "@lucide/svelte/icons/info";
4
- import Loader2Icon from "@lucide/svelte/icons/loader-2";
5
- import OctagonXIcon from "@lucide/svelte/icons/octagon-x";
6
- import TriangleAlertIcon from "@lucide/svelte/icons/triangle-alert";
2
+ import { CheckCircle, Info, Warning, XCircle } from "phosphor-svelte";
7
3
 
4
+ import { Spinner } from "../spinner/index.js";
8
5
  import { Toaster as Sonner, type ToasterProps as SonnerProps } from "svelte-sonner";
9
6
  import { mode } from "mode-watcher";
10
7
 
@@ -17,18 +14,18 @@
17
14
  style="--normal-bg: var(--color-popover); --normal-text: var(--color-popover-foreground); --normal-border: var(--color-border);"
18
15
  {...restProps}
19
16
  >{#snippet loadingIcon()}
20
- <Loader2Icon class="size-4 animate-spin" />
17
+ <Spinner />
21
18
  {/snippet}
22
19
  {#snippet successIcon()}
23
- <CircleCheckIcon class="size-4" />
20
+ <CheckCircle class="size-4" />
24
21
  {/snippet}
25
22
  {#snippet errorIcon()}
26
- <OctagonXIcon class="size-4" />
23
+ <XCircle class="size-4" />
27
24
  {/snippet}
28
25
  {#snippet infoIcon()}
29
- <InfoIcon class="size-4" />
26
+ <Info class="size-4" />
30
27
  {/snippet}
31
28
  {#snippet warningIcon()}
32
- <TriangleAlertIcon class="size-4" />
29
+ <Warning class="size-4" />
33
30
  {/snippet}
34
31
  </Sonner>
@@ -32,10 +32,17 @@
32
32
  ...restProps
33
33
  }: ToggleGroupPrimitive.RootProps & ToggleVariants & { spacing?: number } = $props();
34
34
 
35
+ // WHY: getters, so an item reads the group's current props, not the ones it mounted with.
35
36
  setToggleGroupCtx({
36
- variant,
37
- size,
38
- spacing,
37
+ get variant() {
38
+ return variant;
39
+ },
40
+ get size() {
41
+ return size;
42
+ },
43
+ get spacing() {
44
+ return spacing;
45
+ },
39
46
  });
40
47
  </script>
41
48
 
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import * as Collapsible from "../collapsible/index.js";
3
- import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
3
+ import { CaretRight } from "phosphor-svelte";
4
4
  import { cn, type WithoutChild } from "@heyramzi/utils";
5
5
  import TraceIcon from "./trace-icon.svelte";
6
6
  import type { Collapsible as CollapsiblePrimitive } from "bits-ui";
@@ -24,6 +24,6 @@
24
24
  >
25
25
  {@render children?.()}
26
26
  <TraceIcon class="transition-transform group-aria-expanded/trace-collapsible-trigger:rotate-90">
27
- <ChevronRightIcon />
27
+ <CaretRight />
28
28
  </TraceIcon>
29
29
  </Collapsible.Trigger>
@@ -1,108 +0,0 @@
1
- .chat-composer {
2
- border-top: 1px solid color-mix(in srgb, var(--border, rgba(0, 0, 0, 0.1)) 40%, transparent);
3
- padding: 0.625rem 0.75rem;
4
- }
5
-
6
- .chat-composer-row {
7
- display: flex;
8
- align-items: flex-end;
9
- gap: 0.375rem;
10
- }
11
-
12
- .chat-composer-input {
13
- /* WHY: grows with content; min/max bounds can be widened via `inputClass`. */
14
- field-sizing: content;
15
- max-height: 6rem;
16
- flex: 1;
17
- resize: none;
18
- border: 0;
19
- border-radius: 1rem;
20
- background: color-mix(in srgb, var(--muted, rgba(0, 0, 0, 0.06)) 40%, transparent);
21
- padding: 0.5rem 0.875rem;
22
- font-size: 0.875rem;
23
- color: inherit;
24
- transition: background-color 120ms ease;
25
- }
26
-
27
- .chat-composer-input::placeholder {
28
- color: var(--muted-foreground, rgba(0, 0, 0, 0.45));
29
- }
30
-
31
- .chat-composer-input:focus {
32
- outline: none;
33
- background: color-mix(in srgb, var(--muted, rgba(0, 0, 0, 0.06)) 60%, transparent);
34
- /* WHY: a soft ring in the conversation's accent, which is per-surface. */
35
- box-shadow: 0 0 0 2px
36
- color-mix(in srgb, var(--chat-accent, var(--primary, #6647f0)) 28%, transparent);
37
- }
38
-
39
- .chat-composer-input:disabled {
40
- opacity: 0.4;
41
- }
42
-
43
- .chat-composer-send {
44
- display: flex;
45
- height: 2.25rem;
46
- width: 2.25rem;
47
- flex-shrink: 0;
48
- align-items: center;
49
- justify-content: center;
50
- border: 0;
51
- border-radius: 9999px;
52
- background: var(--chat-accent, var(--primary, #6647f0));
53
- color: var(--chat-accent-foreground, #fff);
54
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
55
- cursor: pointer;
56
- transition:
57
- transform 150ms ease,
58
- box-shadow 150ms ease;
59
- }
60
-
61
- .chat-composer-send:hover:not(:disabled) {
62
- transform: translateY(-1px);
63
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12);
64
- }
65
-
66
- .chat-composer-send:active:not(:disabled) {
67
- transform: translateY(0) scale(0.95);
68
- }
69
-
70
- .chat-composer-send:disabled {
71
- cursor: not-allowed;
72
- opacity: 0.2;
73
- box-shadow: none;
74
- }
75
-
76
- .chat-composer-icon {
77
- height: 0.875rem;
78
- width: 0.875rem;
79
- }
80
-
81
- .chat-composer-spin {
82
- animation: chat-composer-spin 800ms linear infinite;
83
- }
84
-
85
- @keyframes chat-composer-spin {
86
- to {
87
- transform: rotate(360deg);
88
- }
89
- }
90
-
91
- .chat-composer-error {
92
- margin-top: 0.25rem;
93
- padding: 0 0.25rem;
94
- font-size: 0.75rem;
95
- color: var(--destructive, #dc2626);
96
- }
97
-
98
- @media (prefers-reduced-motion: reduce) {
99
- .chat-composer-send {
100
- transition: background-color 150ms ease;
101
- }
102
- .chat-composer-send:hover:not(:disabled) {
103
- transform: none;
104
- }
105
- .chat-composer-spin {
106
- animation: none;
107
- }
108
- }