@heyramzi/ui 7.0.0 → 8.1.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 (259) hide show
  1. package/COMPONENTS.md +28 -17
  2. package/README.md +6 -2
  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/agents/agent-avatar.svelte +67 -114
  7. package/src/agents/agent-cast.svelte +31 -7
  8. package/src/agents/demo.svelte +55 -8
  9. package/src/agents/helpers.ts +58 -0
  10. package/src/agents/page-state.ts +70 -0
  11. package/src/agents/types.ts +3 -1
  12. package/src/ai/ai-button/AiButton.svelte +86 -0
  13. package/src/ai/ai-button/index.ts +4 -0
  14. package/src/ai/confirmation/Confirmation.svelte +8 -8
  15. package/src/ai/confirmation/confirmation-context.svelte.ts +2 -2
  16. package/src/ai/generation-progress/GenerationProgress.svelte +354 -0
  17. package/src/ai/generation-progress/index.ts +7 -0
  18. package/src/ai/index.ts +16 -6
  19. package/src/ai/prompt-input/PromptInput.svelte +19 -1
  20. package/src/ai/prompt-input/PromptInputSubmit.svelte +9 -3
  21. package/src/ai/reasoning/Reasoning.svelte +11 -8
  22. package/src/ambient.d.ts +4 -0
  23. package/src/animations/README.md +14 -14
  24. package/src/animations/index.css +3 -14
  25. package/src/animations/upstream-coverage.test.ts +1 -1
  26. package/src/components/copy/clipboard.test.ts +206 -0
  27. package/src/components/copy/clipboard.ts +49 -0
  28. package/src/components/copy/copied.svelte.ts +39 -0
  29. package/src/components/copy/copy-button.svelte +175 -0
  30. package/src/components/copy/copy-button.test.ts +110 -0
  31. package/src/components/copy/index.ts +3 -0
  32. package/src/components/data-table/data-table.test.ts +134 -0
  33. package/src/components/data-table/data-table.ts +77 -0
  34. package/src/components/data-table/index.ts +20 -0
  35. package/src/components/data-table/infinite-scroll.svelte +69 -0
  36. package/src/components/data-table/pagination.svelte +62 -0
  37. package/src/components/data-table/row-arrow.svelte +28 -0
  38. package/src/components/data-table/row-context-menu.svelte +106 -0
  39. package/src/components/data-table/row-context-menu.test.ts +18 -0
  40. package/src/components/data-table/sortable-head.svelte +56 -0
  41. package/src/components/data-table/table-skeleton.svelte +83 -0
  42. package/src/components/hold-to-confirm/hold-to-confirm.svelte +232 -0
  43. package/src/components/hold-to-confirm/index.ts +1 -0
  44. package/src/components/icon/icon.svelte +23 -0
  45. package/src/components/icon/index.ts +1 -0
  46. package/src/components/language-flag/index.ts +1 -0
  47. package/src/components/language-flag/language-flag.svelte +60 -0
  48. package/src/components/list-page/detail-empty.svelte +50 -0
  49. package/src/components/list-page/detail-header.svelte +107 -0
  50. package/src/components/list-page/detail-panel.svelte +90 -0
  51. package/src/components/list-page/detail-row.svelte +36 -0
  52. package/src/components/list-page/detail-stack.svelte +33 -0
  53. package/src/components/list-page/empty-state.svelte +91 -0
  54. package/src/components/list-page/filter-item.svelte +49 -0
  55. package/src/components/list-page/filter-popover.svelte +88 -0
  56. package/src/components/list-page/filter-section.ts +12 -0
  57. package/src/components/list-page/grouped-list.svelte +116 -0
  58. package/src/components/list-page/index.ts +28 -0
  59. package/src/components/list-page/list-page-shell.svelte +195 -0
  60. package/src/components/list-page/list-table.ts +19 -0
  61. package/src/components/list-page/master-detail.svelte +206 -0
  62. package/src/components/list-page/master-detail.test.ts +37 -0
  63. package/src/components/list-page/notice-bar.svelte +68 -0
  64. package/src/components/list-page/prev-next.svelte +104 -0
  65. package/src/components/list-page/queue-rail.ts +25 -0
  66. package/src/components/list-page/rail-body.svelte +29 -0
  67. package/src/components/list-page/rail-empty.svelte +51 -0
  68. package/src/components/list-page/rail-head.svelte +49 -0
  69. package/src/components/list-page/rail-row.svelte +163 -0
  70. package/src/components/list-page/section-host.ts +24 -0
  71. package/src/components/list-page/section-label.svelte +39 -0
  72. package/src/components/list-page/step.test.ts +21 -0
  73. package/src/components/list-page/step.ts +10 -0
  74. package/src/components/list-page/toolbar-search.svelte +63 -0
  75. package/src/components/list-page/toolbar-trigger.ts +23 -0
  76. package/src/components/list-page/types.ts +1 -0
  77. package/src/components/locale-switch/index.ts +1 -0
  78. package/src/components/locale-switch/locale-switch.svelte +59 -0
  79. package/src/components/mark/index.ts +1 -0
  80. package/src/components/mark/mark.svelte +70 -0
  81. package/src/components/open-in/index.ts +6 -0
  82. package/src/components/open-in/open-in.svelte +106 -0
  83. package/src/components/picker/index.ts +4 -0
  84. package/src/components/picker/mark-picker.svelte +84 -0
  85. package/src/components/picker/picker.svelte +143 -0
  86. package/src/components/picker/trigger.ts +42 -0
  87. package/src/components/picker/types.ts +26 -0
  88. package/src/components/segmented/animated-tabs.svelte +175 -0
  89. package/src/components/segmented/collapse.test.ts +49 -0
  90. package/src/components/segmented/collapse.ts +39 -0
  91. package/src/components/segmented/index.ts +41 -0
  92. package/src/components/segmented/section-runs.svelte +79 -0
  93. package/src/components/segmented/segment.svelte +106 -0
  94. package/src/components/segmented/segmented-filter.svelte +125 -0
  95. package/src/components/segmented/segmented-nav.svelte +82 -0
  96. package/src/components/segmented/segmented-overflow.svelte +85 -0
  97. package/src/components/segmented/segmented-track.svelte +181 -0
  98. package/src/components/segmented/segmented.test.ts +20 -0
  99. package/src/components/segmented/segmented.ts +80 -0
  100. package/src/components/star-rating-input/index.ts +9 -0
  101. package/src/components/star-rating-input/star-rating-input.svelte +256 -0
  102. package/src/components/svg/index.ts +1 -0
  103. package/src/components/svg/svg.svelte +9 -0
  104. package/src/effects/border-beam/BorderBeam.svelte +1 -1
  105. package/src/effects/image-comparison/ImageComparison.css +42 -8
  106. package/src/effects/image-comparison/ImageComparison.svelte +138 -21
  107. package/src/effects/image-comparison/compare.test.ts +17 -0
  108. package/src/effects/image-comparison/compare.ts +5 -0
  109. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +338 -0
  110. package/src/effects/magic-card/MagicCard.css +47 -0
  111. package/src/effects/magic-card/MagicCard.svelte +57 -0
  112. package/src/effects/marquee/Marquee.css +27 -1
  113. package/src/effects/marquee/Marquee.svelte +67 -3
  114. package/src/effects/reveal/index.ts +2 -1
  115. package/src/effects/reveal/observe.test.ts +60 -0
  116. package/src/effects/reveal/observe.ts +33 -1
  117. package/src/effects/reveal/types.ts +7 -0
  118. package/src/effects/theme-toggle/ThemeToggle.css +199 -6
  119. package/src/effects/theme-toggle/ThemeToggle.svelte +51 -26
  120. package/src/gallery.test.ts +52 -0
  121. package/src/index.ts +5 -1
  122. package/src/lib/panel.ts +7 -1
  123. package/src/lib/theme-transition.ts +37 -11
  124. package/src/lib/tone.ts +48 -0
  125. package/src/shadcn/accordion/accordion-trigger.svelte +15 -6
  126. package/src/shadcn/alert/alert.svelte +3 -1
  127. package/src/shadcn/badge/badge.svelte +8 -7
  128. package/src/shadcn/breadcrumb/breadcrumb-ellipsis.svelte +2 -2
  129. package/src/shadcn/breadcrumb/breadcrumb-separator.svelte +2 -2
  130. package/src/shadcn/calendar/calendar-month-select.svelte +2 -2
  131. package/src/shadcn/calendar/calendar-next-button.svelte +2 -2
  132. package/src/shadcn/calendar/calendar-prev-button.svelte +2 -2
  133. package/src/shadcn/calendar/calendar-year-select.svelte +2 -2
  134. package/src/shadcn/calendar/calendar.svelte +6 -0
  135. package/src/shadcn/code-block/code-block-copy-button.svelte +6 -55
  136. package/src/shadcn/command/index.ts +3 -0
  137. package/src/shadcn/field/index.ts +3 -0
  138. package/src/shadcn/field/simple-field.svelte +71 -0
  139. package/src/shadcn/field/simple-field.test.ts +47 -0
  140. package/src/shadcn/message-scroller/message-scroller-button.svelte +2 -2
  141. package/src/shadcn/radio-group/radio-group-item.svelte +3 -2
  142. package/src/shadcn/sheet/sheet-content.svelte +1 -0
  143. package/src/shadcn/sheet/sheet.css +48 -0
  144. package/src/shadcn/sonner/sonner.svelte +7 -10
  145. package/src/shadcn/toggle-group/toggle-group.svelte +10 -3
  146. package/src/shadcn/trace/trace-collapsible-trigger.svelte +2 -2
  147. package/src/ai/composer/Composer.css +0 -108
  148. package/src/ai/composer/Composer.svelte +0 -111
  149. package/src/ai/composer/index.ts +0 -10
  150. package/src/animations/accordion.css +0 -58
  151. package/src/animations/icon-swap.css +0 -43
  152. package/src/animations/notification-badge.css +0 -81
  153. package/src/animations/panel.css +0 -86
  154. package/src/animations/reasoning-stream.css +0 -49
  155. package/src/animations/skeleton-reveal.css +0 -97
  156. package/src/animations/streaming-text.css +0 -31
  157. package/src/animations/tabs-sliding.css +0 -83
  158. package/src/animations/texts-reveal.css +0 -70
  159. package/src/animations/tooltip.css +0 -72
  160. package/src/animations/view-transition-reveal.ts +0 -74
  161. package/src/lib/utils.ts +0 -45
  162. package/src/showcase/AiSection.svelte +0 -150
  163. package/src/showcase/AnimationsSection.svelte +0 -1658
  164. package/src/showcase/EffectsSection.svelte +0 -152
  165. package/src/showcase/ShadcnSection.svelte +0 -27
  166. package/src/showcase/ShowcaseImport.svelte +0 -126
  167. package/src/showcase/ShowcaseList.svelte +0 -132
  168. package/src/showcase/ShowcaseNav.svelte +0 -247
  169. package/src/showcase/ShowcaseSection.svelte +0 -112
  170. package/src/showcase/ShowcaseShell.svelte +0 -55
  171. package/src/showcase/ShowcaseStage.svelte +0 -342
  172. package/src/showcase/ShowcaseVariant.svelte +0 -109
  173. package/src/showcase/assets/image-reveal/art-1.jpg +0 -0
  174. package/src/showcase/assets/image-reveal/art-2.jpg +0 -0
  175. package/src/showcase/assets/image-reveal/art-3.jpg +0 -0
  176. package/src/showcase/check.svg +0 -1
  177. package/src/showcase/chevron-arms.svg +0 -2
  178. package/src/showcase/chevron-down.svg +0 -1
  179. package/src/showcase/copy.svg +0 -2
  180. package/src/showcase/demos/ai/confirmation.svelte +0 -59
  181. package/src/showcase/demos/ai/loading-state.svelte +0 -19
  182. package/src/showcase/demos/ai/prompt-input.svelte +0 -57
  183. package/src/showcase/demos/ai/reasoning.svelte +0 -19
  184. package/src/showcase/demos/ai/thinking-dots.svelte +0 -15
  185. package/src/showcase/demos/ai/voice-waveform.svelte +0 -24
  186. package/src/showcase/demos/effects/animated-beam.svelte +0 -50
  187. package/src/showcase/demos/effects/animated-group.svelte +0 -47
  188. package/src/showcase/demos/effects/border-beam-default.svelte +0 -20
  189. package/src/showcase/demos/effects/border-beam-line.svelte +0 -20
  190. package/src/showcase/demos/effects/border-beam-mono.svelte +0 -20
  191. package/src/showcase/demos/effects/border-beam-sunset.svelte +0 -20
  192. package/src/showcase/demos/effects/image-comparison.svelte +0 -25
  193. package/src/showcase/demos/effects/image-reveal.svelte +0 -49
  194. package/src/showcase/demos/effects/in-view.svelte +0 -41
  195. package/src/showcase/demos/effects/integrations-hub.svelte +0 -82
  196. package/src/showcase/demos/effects/magnetic.svelte +0 -31
  197. package/src/showcase/demos/effects/marquee.svelte +0 -33
  198. package/src/showcase/demos/effects/progressive-blur.svelte +0 -47
  199. package/src/showcase/demos/effects/spotlight.svelte +0 -40
  200. package/src/showcase/demos/effects/text-effect.svelte +0 -55
  201. package/src/showcase/demos/shadcn/accordion.svelte +0 -45
  202. package/src/showcase/demos/shadcn/alert-dialog.svelte +0 -23
  203. package/src/showcase/demos/shadcn/alert.svelte +0 -30
  204. package/src/showcase/demos/shadcn/attachment.svelte +0 -71
  205. package/src/showcase/demos/shadcn/avatar.svelte +0 -30
  206. package/src/showcase/demos/shadcn/badge.svelte +0 -26
  207. package/src/showcase/demos/shadcn/breadcrumb.svelte +0 -34
  208. package/src/showcase/demos/shadcn/bubble.svelte +0 -36
  209. package/src/showcase/demos/shadcn/button.svelte +0 -11
  210. package/src/showcase/demos/shadcn/calendar.svelte +0 -8
  211. package/src/showcase/demos/shadcn/card.svelte +0 -39
  212. package/src/showcase/demos/shadcn/checkbox.svelte +0 -39
  213. package/src/showcase/demos/shadcn/code-block.svelte +0 -80
  214. package/src/showcase/demos/shadcn/collapsible.svelte +0 -20
  215. package/src/showcase/demos/shadcn/command.svelte +0 -48
  216. package/src/showcase/demos/shadcn/context-menu.svelte +0 -54
  217. package/src/showcase/demos/shadcn/demo-harness.svelte +0 -12
  218. package/src/showcase/demos/shadcn/demos.test.ts +0 -20
  219. package/src/showcase/demos/shadcn/dialog.svelte +0 -38
  220. package/src/showcase/demos/shadcn/dropdown-menu.svelte +0 -59
  221. package/src/showcase/demos/shadcn/empty.svelte +0 -29
  222. package/src/showcase/demos/shadcn/field.svelte +0 -115
  223. package/src/showcase/demos/shadcn/hover-card.svelte +0 -32
  224. package/src/showcase/demos/shadcn/index.ts +0 -382
  225. package/src/showcase/demos/shadcn/input-otp.svelte +0 -19
  226. package/src/showcase/demos/shadcn/input.svelte +0 -5
  227. package/src/showcase/demos/shadcn/label.svelte +0 -11
  228. package/src/showcase/demos/shadcn/marker.svelte +0 -30
  229. package/src/showcase/demos/shadcn/message-scroller.svelte +0 -44
  230. package/src/showcase/demos/shadcn/message.svelte +0 -75
  231. package/src/showcase/demos/shadcn/popover.svelte +0 -36
  232. package/src/showcase/demos/shadcn/progress.svelte +0 -13
  233. package/src/showcase/demos/shadcn/radio-group.svelte +0 -19
  234. package/src/showcase/demos/shadcn/scroll-area.svelte +0 -18
  235. package/src/showcase/demos/shadcn/select.svelte +0 -29
  236. package/src/showcase/demos/shadcn/separator.svelte +0 -12
  237. package/src/showcase/demos/shadcn/sheet.svelte +0 -48
  238. package/src/showcase/demos/shadcn/skeleton.svelte +0 -11
  239. package/src/showcase/demos/shadcn/slider.svelte +0 -7
  240. package/src/showcase/demos/shadcn/sonner.svelte +0 -18
  241. package/src/showcase/demos/shadcn/spinner.svelte +0 -11
  242. package/src/showcase/demos/shadcn/switch.svelte +0 -9
  243. package/src/showcase/demos/shadcn/table.svelte +0 -76
  244. package/src/showcase/demos/shadcn/tabs.svelte +0 -62
  245. package/src/showcase/demos/shadcn/textarea.svelte +0 -5
  246. package/src/showcase/demos/shadcn/toggle-group.svelte +0 -18
  247. package/src/showcase/demos/shadcn/toggle.svelte +0 -14
  248. package/src/showcase/demos/shadcn/tooltip.svelte +0 -13
  249. package/src/showcase/demos/shadcn/trace.svelte +0 -51
  250. package/src/showcase/heart.svg +0 -1
  251. package/src/showcase/index.ts +0 -38
  252. package/src/showcase/kit.test.ts +0 -72
  253. package/src/showcase/kit.ts +0 -16
  254. package/src/showcase/plus.svg +0 -1
  255. package/src/showcase/showcase-shell.css +0 -91
  256. package/src/showcase/stacked-section.css +0 -15
  257. package/src/showcase/success-check.svg +0 -1
  258. package/src/showcase/types.ts +0 -29
  259. package/src/svg-raw.d.ts +0 -7
@@ -0,0 +1,354 @@
1
+ <script lang="ts" module>
2
+ export interface GenerationSteps {
3
+ completed: number;
4
+ total: number;
5
+ currentStep?: string;
6
+ }
7
+
8
+ // No `variant` prop: bar, card and inline drew the same four facts three ways.
9
+ export interface GenerationProgressProps {
10
+ status: "idle" | "generating" | "failed";
11
+ progress?: GenerationSteps | null;
12
+ label?: string;
13
+ showElapsed?: boolean;
14
+ class?: string;
15
+ }
16
+ </script>
17
+
18
+ <script lang="ts">
19
+ // =========================
20
+ // IMPORTS
21
+ // =========================
22
+ import { cn } from "@heyramzi/utils";
23
+
24
+ // =========================
25
+ // PROPS
26
+ // =========================
27
+ let {
28
+ status,
29
+ progress = null,
30
+ label,
31
+ showElapsed = false,
32
+ class: className,
33
+ }: GenerationProgressProps = $props();
34
+
35
+ // =========================
36
+ // STATE
37
+ // =========================
38
+ let elapsed = $state(0);
39
+
40
+ // =========================
41
+ // DERIVED
42
+ // =========================
43
+ let heading = $derived(label || "Generating");
44
+ let percent = $derived(progress && progress.total > 0 ? (progress.completed / progress.total) * 100 : 0);
45
+
46
+ // =========================
47
+ // EFFECTS
48
+ // =========================
49
+ $effect(() => {
50
+ if (!showElapsed || status !== "generating") {
51
+ elapsed = 0;
52
+ return;
53
+ }
54
+
55
+ const interval = setInterval(() => {
56
+ elapsed += 1;
57
+ }, 1000);
58
+
59
+ return () => clearInterval(interval);
60
+ });
61
+ </script>
62
+
63
+ <!-- ===========================
64
+ MARKUP
65
+ =========================== -->
66
+ {#if status === "failed"}
67
+ <div class={cn("gen gen-failed", className)}>
68
+ <div class="gen-head">
69
+ <span class="gen-dot"></span>
70
+ <span class="gen-label">Generation failed</span>
71
+ </div>
72
+ </div>
73
+ {:else if status === "generating"}
74
+ <div class={cn("gen", className)}>
75
+ <div class="gen-head">
76
+ <span class="gen-dot"></span>
77
+ <!-- data-text carries the same string for the shimmer's ::before layer. -->
78
+ <span class="gen-label t-shimmer" data-text={heading}>{heading}</span>
79
+ <span class="gen-meta">
80
+ {#if progress}
81
+ <span class="gen-count">
82
+ <!-- #key is the reflow: Svelte drops the node and builds a new one,
83
+ which is what replays the pop-in on every step. -->
84
+ {#key progress.completed}
85
+ <span class="t-digit-group is-animating"><span class="t-digit">{progress.completed}</span></span>
86
+ {/key}<span class="gen-total">/{progress.total}</span>
87
+ </span>
88
+ {/if}
89
+ {#if showElapsed}
90
+ <span class="gen-elapsed">{elapsed}s</span>
91
+ {/if}
92
+ </span>
93
+ </div>
94
+
95
+ <div class="gen-track" data-indeterminate={progress ? undefined : true}>
96
+ <div class="gen-fill" style:transform={progress ? `scaleX(${percent / 100})` : undefined}></div>
97
+ </div>
98
+
99
+ {#if progress?.currentStep}
100
+ {#key progress.currentStep}
101
+ <p class="gen-step">{progress.currentStep}</p>
102
+ {/key}
103
+ {/if}
104
+ </div>
105
+ {/if}
106
+
107
+ <!-- ===========================
108
+ STYLE
109
+ =========================== -->
110
+ <style>
111
+ /* ── Surface ─────────────────────────────────────────── */
112
+ .gen {
113
+ --shimmer-dur: 2200ms;
114
+ --shimmer-base: var(--color-brand-indigo);
115
+ /* The band is darker than the base on a pale tint and lighter on ink.
116
+ Otherwise the sweep looks like the label dropping out. */
117
+ --shimmer-highlight: color-mix(in oklab, black 45%, var(--color-brand-indigo));
118
+ --shimmer-band: 400%;
119
+ --shimmer-ease: linear;
120
+ --digit-dur: var(--duration-very-slow);
121
+ --digit-distance: var(--distance-base);
122
+ --digit-blur: var(--blur-small);
123
+ --digit-ease: var(--ease-smooth-out);
124
+ --digit-dir-x: 0;
125
+ --digit-dir-y: 1;
126
+
127
+ padding: 0.625rem 0.875rem;
128
+ border-radius: var(--radius-lg);
129
+ background: color-mix(in oklab, var(--color-brand-indigo) 10%, transparent);
130
+ }
131
+
132
+ :global(.dark) .gen {
133
+ --shimmer-highlight: color-mix(in oklab, white 70%, var(--color-brand-indigo));
134
+ }
135
+
136
+ .gen-failed {
137
+ --shimmer-base: var(--color-destructive);
138
+ background: color-mix(in oklab, var(--color-destructive) 10%, transparent);
139
+ }
140
+
141
+ .gen-head {
142
+ display: flex;
143
+ align-items: center;
144
+ justify-content: space-between;
145
+ gap: 0.5rem;
146
+ }
147
+
148
+ .gen-dot {
149
+ flex: none;
150
+ width: 0.375rem;
151
+ height: 0.375rem;
152
+ border-radius: 9999px;
153
+ background: var(--color-brand-indigo);
154
+ animation: gen-pulse 1.8s var(--ease-in-out, ease-in-out) infinite;
155
+ }
156
+
157
+ .gen-failed .gen-dot {
158
+ background: var(--color-destructive);
159
+ animation: none;
160
+ }
161
+
162
+ .gen-label {
163
+ margin-inline-end: auto;
164
+ font-size: 0.75rem;
165
+ font-weight: 500;
166
+ line-height: 1.4;
167
+ }
168
+
169
+ /* terra-600, not --color-destructive: that token is tuned for fills, and at
170
+ L=0.6 small text on the pale tint fails. Dark mode uses the lighter step. */
171
+ .gen-failed .gen-label {
172
+ color: var(--color-terra-600);
173
+ }
174
+
175
+ :global(.dark) .gen-failed .gen-label {
176
+ color: var(--color-terra-400);
177
+ }
178
+
179
+ .gen-meta {
180
+ display: flex;
181
+ align-items: baseline;
182
+ gap: 0.375rem;
183
+ font-size: 0.75rem;
184
+ font-weight: 500;
185
+ font-variant-numeric: tabular-nums;
186
+ color: var(--color-brand-indigo);
187
+ }
188
+
189
+ .gen-count {
190
+ display: inline-flex;
191
+ align-items: baseline;
192
+ }
193
+
194
+ .gen-total,
195
+ .gen-elapsed {
196
+ opacity: 0.6;
197
+ }
198
+
199
+ /* ── Track ───────────────────────────────────────────── */
200
+ .gen-track {
201
+ position: relative;
202
+ overflow: hidden;
203
+ margin-top: 0.5rem;
204
+ height: 0.25rem;
205
+ border-radius: 9999px;
206
+ background: color-mix(in oklab, var(--color-brand-indigo) 22%, transparent);
207
+ }
208
+
209
+ .gen-fill {
210
+ width: 100%;
211
+ height: 100%;
212
+ transform-origin: left;
213
+ border-radius: 9999px;
214
+ background: var(--color-brand-indigo);
215
+ transition: transform var(--duration-slow, 400ms) var(--ease-smooth-out, ease);
216
+ }
217
+
218
+ /* A job with no step count still has to look like it is running, so the fill
219
+ becomes a band that sweeps instead of a length that grows. */
220
+ .gen-track[data-indeterminate] .gen-fill {
221
+ width: 38%;
222
+ transition: none;
223
+ animation: gen-sweep 1.5s var(--ease-in-out, ease-in-out) infinite;
224
+ }
225
+
226
+ .gen-step {
227
+ margin: 0.375rem 0 0;
228
+ overflow: hidden;
229
+ font-size: 0.75rem;
230
+ line-height: 1.5;
231
+ white-space: nowrap;
232
+ text-overflow: ellipsis;
233
+ /* Mix toward the foreground, not transparent: a 70% indigo wash on its
234
+ own track measured 2.9:1. This stays quieter than the label and AA. */
235
+ color: color-mix(in oklab, var(--color-brand-indigo) 70%, var(--color-foreground));
236
+ animation: gen-step-in var(--duration-quick, 150ms) var(--ease-in-out, ease-in-out) both;
237
+ }
238
+
239
+ @keyframes gen-pulse {
240
+ 0%,
241
+ 100% {
242
+ opacity: 1;
243
+ }
244
+ 50% {
245
+ opacity: 0.35;
246
+ }
247
+ }
248
+
249
+ @keyframes gen-sweep {
250
+ 0% {
251
+ translate: -110% 0;
252
+ }
253
+ 100% {
254
+ translate: 290% 0;
255
+ }
256
+ }
257
+
258
+ @keyframes gen-step-in {
259
+ from {
260
+ opacity: 0;
261
+ transform: translateY(var(--distance-micro, 4px));
262
+ filter: blur(var(--blur-small, 2px));
263
+ }
264
+ to {
265
+ opacity: 1;
266
+ transform: none;
267
+ filter: blur(0);
268
+ }
269
+ }
270
+
271
+ /* ── transitions.dev: shimmer text ───────────────────── */
272
+ .t-shimmer {
273
+ position: relative;
274
+ display: inline-block;
275
+ color: var(--shimmer-base);
276
+ }
277
+ .t-shimmer::before {
278
+ content: attr(data-text);
279
+ position: absolute;
280
+ inset: 0;
281
+ pointer-events: none;
282
+ background-image: linear-gradient(
283
+ 90deg,
284
+ transparent 0%,
285
+ transparent 40%,
286
+ var(--shimmer-highlight) 50%,
287
+ transparent 60%,
288
+ transparent 100%
289
+ );
290
+ background-size: var(--shimmer-band) 100%;
291
+ background-repeat: no-repeat;
292
+ -webkit-background-clip: text;
293
+ background-clip: text;
294
+ color: transparent;
295
+ -webkit-text-fill-color: transparent;
296
+ animation: t-shimmer var(--shimmer-dur) var(--shimmer-ease) infinite;
297
+ }
298
+ @keyframes t-shimmer {
299
+ 0% {
300
+ background-position: 100% 0;
301
+ }
302
+ 100% {
303
+ background-position: 0% 0;
304
+ }
305
+ }
306
+
307
+ /* ── transitions.dev: number pop-in ──────────────────── */
308
+ @keyframes t-digit-pop-in {
309
+ 0% {
310
+ transform: translate(
311
+ calc(var(--digit-distance) * var(--digit-dir-x)),
312
+ calc(var(--digit-distance) * var(--digit-dir-y))
313
+ );
314
+ opacity: 0;
315
+ filter: blur(var(--digit-blur));
316
+ }
317
+ 100% {
318
+ transform: translate(0, 0);
319
+ opacity: 1;
320
+ filter: blur(0);
321
+ }
322
+ }
323
+
324
+ .t-digit-group {
325
+ display: inline-flex;
326
+ align-items: baseline;
327
+ }
328
+ .t-digit {
329
+ display: inline-block;
330
+ will-change: transform, opacity, filter;
331
+ }
332
+ .t-digit-group.is-animating .t-digit {
333
+ animation: t-digit-pop-in var(--digit-dur) var(--digit-ease) both;
334
+ }
335
+
336
+ @media (prefers-reduced-motion: reduce) {
337
+ .t-shimmer::before {
338
+ animation: none !important;
339
+ }
340
+ .t-digit-group.is-animating .t-digit,
341
+ .gen-dot,
342
+ .gen-step {
343
+ animation: none;
344
+ }
345
+ .gen-track[data-indeterminate] .gen-fill {
346
+ width: 100%;
347
+ animation: none;
348
+ opacity: 0.5;
349
+ }
350
+ .gen-fill {
351
+ transition: none;
352
+ }
353
+ }
354
+ </style>
@@ -0,0 +1,7 @@
1
+ import GenerationProgress from "./GenerationProgress.svelte";
2
+ import type {
3
+ GenerationProgressProps,
4
+ GenerationSteps,
5
+ } from "./GenerationProgress.svelte";
6
+
7
+ export { GenerationProgress, type GenerationProgressProps, type GenerationSteps };
package/src/ai/index.ts CHANGED
@@ -1,9 +1,3 @@
1
- // =========================
2
- // COMPOSER
3
- // =========================
4
-
5
- export { Composer, type ComposerProps } from "./composer/index.js";
6
-
7
1
  // =========================
8
2
  // TYPING DOTS
9
3
  // =========================
@@ -99,3 +93,19 @@ export {
99
93
  // =========================
100
94
 
101
95
  export { PixelLoader, type PixelLoaderProps } from "./loading-state/index.js";
96
+
97
+ // =========================
98
+ // AI BUTTON
99
+ // =========================
100
+
101
+ export { AiButton, type AiButtonProps, type AiStatus } from "./ai-button/index.js";
102
+
103
+ // =========================
104
+ // GENERATION PROGRESS
105
+ // =========================
106
+
107
+ export {
108
+ GenerationProgress,
109
+ type GenerationProgressProps,
110
+ type GenerationSteps,
111
+ } from "./generation-progress/index.js";
@@ -5,14 +5,26 @@
5
5
  export interface PromptInputProps extends Omit<HTMLFormAttributes, "onsubmit"> {
6
6
  onSubmit?: (value: string, event: SubmitEvent) => void | Promise<void>;
7
7
  class?: string;
8
+ /** A line under the input, for a send that failed. Cleared by passing null. */
9
+ error?: string | null;
10
+ /** Colour of the focus ring. Falls back to `--ring`. */
11
+ accent?: string;
8
12
  children?: Snippet;
9
13
  }
10
14
  </script>
11
15
 
12
16
  <script lang="ts">
13
17
  import { cn } from "@heyramzi/utils";
18
+ import { fade } from "svelte/transition";
14
19
 
15
- let { class: className, onSubmit, children, ...rest }: PromptInputProps = $props();
20
+ let {
21
+ class: className,
22
+ onSubmit,
23
+ error = null,
24
+ accent,
25
+ children,
26
+ ...rest
27
+ }: PromptInputProps = $props();
16
28
 
17
29
  async function handleSubmit(event: SubmitEvent) {
18
30
  event.preventDefault();
@@ -31,8 +43,14 @@
31
43
  "focus-within:ring-1 focus-within:ring-[color:var(--ring,currentColor)]",
32
44
  className,
33
45
  )}
46
+ style:--ring={accent}
34
47
  onsubmit={handleSubmit}
35
48
  {...rest}
36
49
  >
37
50
  {@render children?.()}
51
+ {#if error}
52
+ <p role="alert" class="text-destructive px-3 pb-2 text-xs" in:fade={{ duration: 150 }}>
53
+ {error}
54
+ </p>
55
+ {/if}
38
56
  </form>
@@ -8,6 +8,10 @@
8
8
  status?: PromptInputStatus;
9
9
  onStop?: () => void;
10
10
  onclick?: (event: MouseEvent) => void;
11
+ /** Accessible name while idle. Translate at the call site. */
12
+ submitLabel?: string;
13
+ /** Accessible name while generating, when a click stops the run. */
14
+ stopLabel?: string;
11
15
  class?: string;
12
16
  variant?: ButtonVariant;
13
17
  size?: ButtonSize;
@@ -18,7 +22,7 @@
18
22
  <script lang="ts">
19
23
  import { cn } from "@heyramzi/utils";
20
24
  import { Button } from "../../shadcn/button/index.js";
21
- import LoaderIcon from "phosphor-svelte/lib/CircleNotch";
25
+ import { Spinner } from "../../shadcn/spinner/index.js";
22
26
  import SendIcon from "phosphor-svelte/lib/PaperPlaneRight";
23
27
  import SquareIcon from "phosphor-svelte/lib/Square";
24
28
  import XIcon from "phosphor-svelte/lib/X";
@@ -27,6 +31,8 @@
27
31
  status = "ready",
28
32
  onStop,
29
33
  onclick,
34
+ submitLabel = "Submit",
35
+ stopLabel = "Stop",
30
36
  variant = "default",
31
37
  size = "default",
32
38
  icon = true,
@@ -56,13 +62,13 @@
56
62
  type={buttonType}
57
63
  data-prompt-input-submit
58
64
  onclick={handleClick}
59
- aria-label={isGenerating ? "Stop" : "Submit"}
65
+ aria-label={isGenerating ? stopLabel : submitLabel}
60
66
  class={cn("gap-1.5", className)}
61
67
  >
62
68
  {#if children}
63
69
  {@render children()}
64
70
  {:else if status === "submitted"}
65
- <LoaderIcon class="size-4 animate-spin" />
71
+ <Spinner />
66
72
  {:else if status === "streaming"}
67
73
  <SquareIcon class="size-4" />
68
74
  {:else if status === "error"}
@@ -10,6 +10,7 @@
10
10
  </script>
11
11
 
12
12
  <script lang="ts">
13
+ import { untrack } from "svelte";
13
14
  import { cn } from "@heyramzi/utils";
14
15
  import { Collapsible } from "../../shadcn/collapsible/index.js";
15
16
  import { setReasoningContext } from "./reasoning-context.svelte.js";
@@ -23,18 +24,20 @@
23
24
  ...rest
24
25
  }: ReasoningProps = $props();
25
26
 
26
- // WHY: child parts read streaming/duration through this proxy; the $effect keeps
27
- // it synced to props so $derived in the trigger reruns on every change.
28
- const ctx = $state({ isStreaming, duration });
29
- $effect(() => {
30
- ctx.isStreaming = isStreaming;
31
- ctx.duration = duration;
27
+ // WHY: child parts read streaming/duration through these getters, so $derived in
28
+ // the trigger reruns whenever the props change.
29
+ setReasoningContext({
30
+ get isStreaming() {
31
+ return isStreaming;
32
+ },
33
+ get duration() {
34
+ return duration;
35
+ },
32
36
  });
33
- setReasoningContext(ctx);
34
37
 
35
38
  // WHY: auto-open on stream start and auto-close on stream end, but only on the
36
39
  // transition itself so a user who toggles mid-stream is left alone.
37
- let prev = isStreaming;
40
+ let prev = untrack(() => isStreaming);
38
41
  $effect(() => {
39
42
  if (isStreaming && !prev) open = true;
40
43
  else if (!isStreaming && prev) open = false;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * A stylesheet imported for its side effect, bundled by the consumer's Vite.
3
+ */
4
+ declare module "*.css";
@@ -17,37 +17,27 @@ The script writes pristine upstream copies to `.upstream/transitions/` and hashe
17
17
 
18
18
  | File | Selector | Used by |
19
19
  | --- | --- | --- |
20
- | `accordion.css` | `.t-acc` | standalone |
21
20
  | `avatar-group-hover.css` | `.t-avatar-group`, `.t-avatar` | standalone |
22
21
  | `card-tilt.css` | `.t-tilt`, `.t-tilt-card`, `.t-tilt-glare` | standalone |
23
22
  | `digit-pop-in.css` | `.t-digit` | standalone |
24
- | `dropdown.css` | `.t-dropdown` | (not yet wired) |
25
- | `icon-swap.css` | `.t-icon-swap` | standalone |
23
+ | `dropdown.css` | `.t-dropdown` | `shadcn/tooltip`, `dropdown-menu`, `popover`, `context-menu` and `select` content |
26
24
  | `input-clear-dissolve.css` | `.t-clear`, `.t-clear-mirror`, `.t-clear-glow` | standalone |
27
25
  | `input-shake.css` | `.t-input-wrap`, `.t-input`, `.t-error-msg` | (none, form layer moved to upsys) |
28
26
  | `learn-more-hover.css` | `.t-learn`, `.t-learn-chevron` | standalone |
29
27
  | `like-button.css` | `.t-like`, `.t-like-particles` | standalone |
30
28
  | `modal.css` | `.t-modal`, `.t-modal-backdrop` | `shadcn/dialog`, `shadcn/alert-dialog` |
31
- | `notification-badge.css` | `.t-badge` | standalone |
32
29
  | `page-side-by-side.css` | `.t-page` | standalone |
33
- | `panel.css` | `.t-panel` | (not yet wired) |
34
30
  | `plus-menu-morph.css` | `.t-morph`, `.t-morph-plus`, `.t-morph-menu` | standalone |
35
- | `reasoning-stream.css` | `.t-reason`, `.t-reason-viewport` | standalone |
36
31
  | `resize.css` | `.t-resize` | standalone |
37
32
  | `shimmer-text.css` | `.t-shimmer` | `thinking-states.css` |
38
- | `skeleton-reveal.css` | `.t-skel` | standalone |
39
33
  | `spinning-counter.css` | `.t-reel`, `.t-reel-col` | standalone |
40
- | `streaming-text.css` | `.t-stream-w` | standalone |
41
34
  | `success-check.css` | `.t-success-check` | standalone |
42
- | `tabs-sliding.css` | `.t-tabs`, `.t-tab`, `.t-tabs-pill` | standalone |
43
35
  | `text-swap.css` + `text-swap.ts` | `.t-text-swap` | standalone |
44
- | `texts-reveal.css` | `.t-stagger`, `.t-stagger-line` | standalone |
45
36
  | `thinking-states.css` | `.t-think`, `.t-think-text` | standalone |
46
- | `tooltip.css` | `.t-tt-wrap`, `.t-tt`, `.t-tt-trigger` | standalone |
47
37
 
48
38
  `index.css` is the barrel — imports all of the above.
49
39
 
50
- ## Five upstream transitions that are not files here
40
+ ## Fifteen upstream transitions that are not files here
51
41
 
52
42
  One behaviour, one implementation. Where a component already did the job, the upstream recipe was merged into it or dropped, rather than shipped as a second way to do the same thing.
53
43
 
@@ -58,8 +48,18 @@ One behaviour, one implementation. Where a component already did the job, the up
58
48
  | `toast-open-close` | `shadcn/sonner` — svelte-sonner owns toast motion. |
59
49
  | `banner-stacking` | `shadcn/sonner` — stacking and hover spread are what it does. |
60
50
  | `matrix-dot-loader` | `ai/loading-state` — `PixelLoader` is the dot-grid loader. |
61
-
62
- Two more pairs look like duplicates and are not: `spinning-counter` rolls a number between values, `digit-pop-in` updates one in place; `streaming-text` resolves a paragraph word by word, `ai/response` only blinks a caret while `.is-streaming`.
51
+ | `tabs-sliding` | `shadcn/tabs/tabs-list.svelte` with `lib/sliding-pill.ts` — the pill slides under the active tab. |
52
+ | `tooltip-open-close` | `shadcn/tooltip/tooltip-content.svelte` — wired through `.t-dropdown`. |
53
+ | `accordion` | `shadcn/accordion/accordion-content.svelte` — the expand and collapse keyframes. |
54
+ | `skeleton-loader-and-reveal` | `shadcn/skeleton/skeleton.svelte` — the pulse. |
55
+ | `notification-badge` | `shadcn/badge/badge.svelte` — the badge. |
56
+ | `reasoning-stream` | `ai/reasoning/ReasoningContent.svelte` — the reasoning body. |
57
+ | `streaming-text` | `effects/reveal/TextEffect.svelte` — word by word with a blur. |
58
+ | `texts-reveal` | `effects/reveal/TextEffect.svelte` — the staggered rise. |
59
+ | `panel-reveal` | `shadcn/sheet/sheet.css` — the sheet slide, imported by the sheet itself. |
60
+ | `icon-swap` | `effects/theme-toggle/ThemeToggle.svelte` — the sun, moon and monitor icons cross-fade. |
61
+
62
+ One more pair looks like a duplicate and is not: `spinning-counter` rolls a number between values, `digit-pop-in` updates one in place.
63
63
 
64
64
  ## How to consume
65
65
 
@@ -1,34 +1,23 @@
1
1
  /* Transitions.dev — bundle */
2
2
  /* Source: https://github.com/Jakubantalik/transitions.dev */
3
- /* Refresh the vendored upstream with `pnpm sync:transitions`. Two free
4
- transitions are not here because a component already owns them: the switch
5
- thumb lives in shadcn/switch/switch.css and the checkbox draw in
6
- shadcn/checkbox/checkbox.css. See README.md. */
3
+ /* Refresh the vendored upstream with `pnpm sync:transitions`. Fourteen free
4
+ transitions are not here because a component already owns them. See
5
+ README.md. */
7
6
 
8
7
  @import "./modal.css";
9
- @import "./icon-swap.css";
10
- @import "./panel.css";
11
8
  @import "./dropdown.css";
12
9
  @import "./text-swap.css";
13
10
  @import "./resize.css";
14
11
  @import "./digit-pop-in.css";
15
- @import "./notification-badge.css";
16
12
  @import "./page-side-by-side.css";
17
13
  @import "./input-shake.css";
18
14
  @import "./success-check.css";
19
15
  @import "./avatar-group-hover.css";
20
- @import "./skeleton-reveal.css";
21
16
  @import "./shimmer-text.css";
22
- @import "./tabs-sliding.css";
23
- @import "./tooltip.css";
24
- @import "./texts-reveal.css";
25
17
  @import "./input-clear-dissolve.css";
26
- @import "./accordion.css";
27
18
  @import "./learn-more-hover.css";
28
19
  @import "./card-tilt.css";
29
20
  @import "./plus-menu-morph.css";
30
21
  @import "./like-button.css";
31
22
  @import "./spinning-counter.css";
32
23
  @import "./thinking-states.css";
33
- @import "./reasoning-stream.css";
34
- @import "./streaming-text.css";
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  /*
14
- * The homes are not all in this folder: five live on the component that already did the job. See README.md.
14
+ * The homes are not all in this folder: fifteen live on the component that already did the job. See README.md.
15
15
  */
16
16
 
17
17
  import { existsSync, readdirSync, readFileSync } from "node:fs";