@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
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import AgentAvatar from './agent-avatar.svelte';
3
- import type { Agent } from './types';
3
+ import type { Agent, AgentState } from './types';
4
4
 
5
5
  type Props = {
6
6
  /** The cast to lay out. */
@@ -32,6 +32,12 @@
32
32
 
33
33
  const keyOf = (agent: Agent) => agent.id ?? agent.name;
34
34
 
35
+ /* The card under the pointer or focus wakes up and works; the rest idle.
36
+ A `state` in avatarProps wins. */
37
+ let awake = $state<string | null>(null);
38
+ const stateFor = (id: string) =>
39
+ (avatarProps.state as AgentState | undefined) ?? (awake === id ? 'working' : 'default');
40
+
35
41
  /* Group when any agent asks for one, otherwise a single flat grid. */
36
42
  const sections = $derived.by(() => {
37
43
  const anyGroup = agents.some((agent) => agent.group);
@@ -72,8 +78,12 @@
72
78
  <button
73
79
  type="button"
74
80
  class="agent-cast__card"
75
- aria-current={id === selectedId ? 'true' : undefined}
81
+ aria-pressed={onSelect ? id === selectedId : undefined}
76
82
  onclick={() => onSelect?.(agent)}
83
+ onpointerenter={() => (awake = id)}
84
+ onpointerleave={() => awake === id && (awake = null)}
85
+ onfocus={() => (awake = id)}
86
+ onblur={() => awake === id && (awake = null)}
77
87
  >
78
88
  <span class="agent-cast__slot">
79
89
  <AgentAvatar
@@ -81,7 +91,9 @@
81
91
  role={agent.role}
82
92
  shape={agent.shape ?? 'clover'}
83
93
  color={agent.color}
94
+ aria-hidden
84
95
  {...avatarProps}
96
+ state={stateFor(id)}
85
97
  />
86
98
  </span>
87
99
  <span class="agent-cast__meta">
@@ -133,11 +145,21 @@
133
145
  border-radius: 0.75rem;
134
146
  cursor: pointer;
135
147
  transition:
136
- border-color 0.15s ease,
137
- transform 0.15s ease;
148
+ border-color var(--duration-quick, 150ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)),
149
+ transform var(--duration-quick, 150ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
138
150
  }
139
- .agent-cast__card:hover {
140
- border-color: color-mix(in oklab, var(--color-foreground, #18181b) 25%, transparent);
151
+ @media (prefers-reduced-motion: reduce) {
152
+ .agent-cast__card {
153
+ transition: border-color var(--duration-quick, 150ms) linear;
154
+ }
155
+ .agent-cast__card:active {
156
+ transform: none;
157
+ }
158
+ }
159
+ @media (hover: hover) and (pointer: fine) {
160
+ .agent-cast__card:hover {
161
+ border-color: color-mix(in oklab, var(--color-foreground, #18181b) 25%, transparent);
162
+ }
141
163
  }
142
164
  .agent-cast__card:active {
143
165
  transform: scale(0.985);
@@ -146,13 +168,15 @@
146
168
  outline: 2px solid var(--color-ring, #6366f1);
147
169
  outline-offset: 2px;
148
170
  }
149
- .agent-cast__card[aria-current='true'] {
171
+ .agent-cast__card[aria-pressed='true'] {
150
172
  border-color: var(--color-accent, #6366f1);
151
173
  box-shadow: 0 0 0 1px var(--color-accent, #6366f1);
152
174
  }
175
+ /* Room above the head, so a hop or a hat never meets the card's edge. */
153
176
  .agent-cast__slot {
154
177
  display: block;
155
178
  width: 100%;
179
+ padding: 0.5rem 0.5rem 0;
156
180
  }
157
181
  .agent-cast__meta {
158
182
  display: flex;
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import AgentAvatar from './agent-avatar.svelte';
3
3
  import AgentCast from './agent-cast.svelte';
4
- import type { Agent } from './types';
4
+ import type { Agent, AgentState } from './types';
5
5
 
6
6
  const cast: Agent[] = [
7
7
  { id: 'ada', name: 'Ada', role: 'Researcher', shape: 'clover', color: '#41C4FF', group: 'Research' },
@@ -14,7 +14,14 @@
14
14
  { id: 'hal', name: 'Hal', role: 'Watcher', shape: 'star', group: 'Craft' },
15
15
  ];
16
16
 
17
+ const states: { value: AgentState; label: string }[] = [
18
+ { value: 'default', label: 'Idle' },
19
+ { value: 'working', label: 'Working' },
20
+ { value: 'sleeping', label: 'Sleeping' },
21
+ ];
22
+
17
23
  let selected = $state<string | null>('ada');
24
+ let leadState = $state<AgentState>('working');
18
25
  let lead = $derived(cast.find((agent) => agent.id === selected) ?? cast[0]);
19
26
  </script>
20
27
 
@@ -26,14 +33,25 @@
26
33
  role={lead.role}
27
34
  shape={lead.shape}
28
35
  color={lead.color}
29
- state="working"
30
- theme="light"
36
+ state={leadState}
31
37
  />
32
38
  </div>
33
39
  <div class="demo__intro">
34
40
  <p class="demo__eyebrow">AgentCast</p>
35
41
  <h1 class="demo__title">{lead.name}</h1>
36
42
  <p class="demo__role">{lead.role}</p>
43
+ <div class="demo__states" role="radiogroup" aria-label="State">
44
+ {#each states as s (s.value)}
45
+ <button
46
+ type="button"
47
+ role="radio"
48
+ aria-checked={leadState === s.value}
49
+ onclick={() => (leadState = s.value)}
50
+ >
51
+ {s.label}
52
+ </button>
53
+ {/each}
54
+ </div>
37
55
  </div>
38
56
  </header>
39
57
 
@@ -45,9 +63,8 @@
45
63
  max-width: 56rem;
46
64
  margin: 0 auto;
47
65
  padding: 2rem 1.5rem 4rem;
48
- font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
49
- color: #18181b;
50
- background: #fafafa;
66
+ color: var(--color-foreground, #18181b);
67
+ background: var(--color-background, #fafafa);
51
68
  }
52
69
  .demo__hero {
53
70
  display: flex;
@@ -64,7 +81,7 @@
64
81
  font-size: 0.75rem;
65
82
  letter-spacing: 0.14em;
66
83
  text-transform: uppercase;
67
- color: #71717a;
84
+ color: var(--color-muted-foreground, #71717a);
68
85
  }
69
86
  .demo__title {
70
87
  margin: 0.25rem 0 0;
@@ -73,6 +90,36 @@
73
90
  }
74
91
  .demo__role {
75
92
  margin: 0.25rem 0 0;
76
- color: #52525b;
93
+ color: var(--color-muted-foreground, #52525b);
94
+ }
95
+ .demo__states {
96
+ display: inline-flex;
97
+ gap: 0.25rem;
98
+ margin-top: 0.875rem;
99
+ padding: 0.1875rem;
100
+ border-radius: 0.625rem;
101
+ background: color-mix(in oklab, var(--color-foreground, #18181b) 6%, transparent);
102
+ }
103
+ .demo__states button {
104
+ padding: 0.3125rem 0.75rem;
105
+ font: inherit;
106
+ font-size: 0.8125rem;
107
+ color: var(--color-muted-foreground, #71717a);
108
+ background: transparent;
109
+ border: 0;
110
+ border-radius: 0.4375rem;
111
+ cursor: pointer;
112
+ transition:
113
+ color var(--duration-quick, 150ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)),
114
+ background-color var(--duration-quick, 150ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
115
+ }
116
+ .demo__states button[aria-checked='true'] {
117
+ color: var(--color-foreground, #18181b);
118
+ background: var(--color-card, #fff);
119
+ box-shadow: var(--elev-hairline, 0 0 0 1px rgb(0 0 0 / 0.06));
120
+ }
121
+ .demo__states button:focus-visible {
122
+ outline: 2px solid var(--color-ring, #6366f1);
123
+ outline-offset: 1px;
77
124
  }
78
125
  </style>
@@ -0,0 +1,58 @@
1
+ /* Pure helpers for the avatar: numbers, theme, lights, idle scheduling. */
2
+ import { stockLights } from './vendor/plastic';
3
+
4
+ export const clamp = (v: number, lo: number, hi: number) =>
5
+ Math.min(hi, Math.max(lo, Number.isFinite(v) ? v : 1));
6
+ export const q = (v: number, lo: number, hi: number) => Math.round(clamp(v, lo, hi) * 20) / 20;
7
+ export const clampOpt = (v: number | undefined, d: number, lo: number, hi: number) =>
8
+ clamp(v ?? d, lo, hi);
9
+ export const deviceDpr = () =>
10
+ Math.min(2, (typeof devicePixelRatio === 'number' && devicePixelRatio) || 1);
11
+
12
+ export function hashSeed(id: string): number {
13
+ let h = 2166136261;
14
+ for (let i = 0; i < id.length; i++) h = Math.imul(h ^ id.charCodeAt(i), 16777619);
15
+ return ((h >>> 0) % 1000) / 1000;
16
+ }
17
+
18
+ export const reducedMotion = (): boolean =>
19
+ typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
20
+
21
+ export function resolveTheme(theme: 'auto' | 'dark' | 'light', el: HTMLElement | null): 'dark' | 'light' {
22
+ if (theme !== 'auto') return theme;
23
+ const host = el?.closest('[data-theme], .dark, .light') as HTMLElement | null;
24
+ if (host) {
25
+ const v = host.getAttribute('data-theme');
26
+ if (v === 'dark' || v === 'light') return v;
27
+ if (host.classList.contains('dark')) return 'dark';
28
+ if (host.classList.contains('light')) return 'light';
29
+ }
30
+ return typeof matchMedia === 'function' && matchMedia('(prefers-color-scheme: light)').matches
31
+ ? 'light'
32
+ : 'dark';
33
+ }
34
+
35
+ export function lightsFor(lightAt: number, backLight?: number, lightFront?: number) {
36
+ const a = (lightAt * Math.PI) / 180;
37
+ const b = ((backLight ?? 0) * Math.PI) / 180;
38
+ const lights = stockLights(Math.sin(a), -Math.cos(a));
39
+ if (backLight !== undefined) [lights.bx, lights.by] = [Math.sin(b), -Math.cos(b)];
40
+ if (lightFront !== undefined) lights.front = (Math.min(85, Math.max(0, lightFront)) * Math.PI) / 180;
41
+ return lights;
42
+ }
43
+
44
+ export function warmIdle(fn: () => void): () => void {
45
+ if (typeof requestIdleCallback === 'function') {
46
+ const id = requestIdleCallback(fn);
47
+ return () => cancelIdleCallback(id);
48
+ }
49
+ const id = setTimeout(fn, 1);
50
+ return () => clearTimeout(id);
51
+ }
52
+
53
+ /* Full pull up close, fading to nothing by 3 head widths. */
54
+ export function pointerStrength(d: number) {
55
+ if (d < 1) return 1;
56
+ if (d > 3) return 0;
57
+ return 1 - (d - 1) / 2;
58
+ }
@@ -0,0 +1,70 @@
1
+ /* What every avatar on the page shares: the batching shown-gate, so a row of
2
+ them comes in together, the path caches, and one theme watcher. */
3
+ import { bakesPending } from './vendor/plastic';
4
+
5
+ export const SHOW_WITHIN = 4000;
6
+ export const waiters = new Map<object, { ready: () => boolean; show: () => void }>();
7
+
8
+ export function showReady() {
9
+ if (bakesPending()) return;
10
+ for (const w of waiters.values()) if (!w.ready()) return;
11
+ const all = [...waiters.values()];
12
+ waiters.clear();
13
+ for (const w of all) w.show();
14
+ }
15
+
16
+ const pathCache = new Map<string, Path2D>();
17
+ export function bodyPath(d: string): Path2D {
18
+ let p = pathCache.get(d);
19
+ if (!p) {
20
+ p = new Path2D(d);
21
+ pathCache.set(d, p);
22
+ }
23
+ return p;
24
+ }
25
+
26
+ const partsCache = new Map<string, Path2D[]>();
27
+ export function partPaths(d: string): Path2D[] {
28
+ let p = partsCache.get(d);
29
+ if (!p) {
30
+ p = d
31
+ .split(/(?=M)/)
32
+ .filter((s) => s.trim())
33
+ .map((s) => new Path2D(s));
34
+ partsCache.set(d, p);
35
+ }
36
+ return p;
37
+ }
38
+
39
+ let uidSeq = 0;
40
+ export const nextUid = () => `ba-${++uidSeq}`;
41
+
42
+ /* One observer for the whole page: a `.dark` or `data-theme` flip on
43
+ <html> or <body>, or the OS scheme changing, repaints every auto avatar. */
44
+ const themeListeners = new Set<() => void>();
45
+ let themeWatch: (() => void) | null = null;
46
+ export function onThemeChange(fn: () => void): () => void {
47
+ themeListeners.add(fn);
48
+ if (!themeWatch && typeof MutationObserver === 'function' && typeof document !== 'undefined') {
49
+ const fire = () => {
50
+ for (const l of themeListeners) l();
51
+ };
52
+ const mo = new MutationObserver(fire);
53
+ const opts = { attributes: true, attributeFilter: ['class', 'data-theme'] };
54
+ mo.observe(document.documentElement, opts);
55
+ if (document.body) mo.observe(document.body, opts);
56
+ const mq = typeof matchMedia === 'function' ? matchMedia('(prefers-color-scheme: dark)') : null;
57
+ mq?.addEventListener('change', fire);
58
+ themeWatch = () => {
59
+ mo.disconnect();
60
+ mq?.removeEventListener('change', fire);
61
+ };
62
+ }
63
+ return () => {
64
+ themeListeners.delete(fn);
65
+ if (themeListeners.size === 0 && themeWatch) {
66
+ themeWatch();
67
+ themeWatch = null;
68
+ }
69
+ };
70
+ }
@@ -61,7 +61,7 @@ export type AgentAvatarProps = Omit<
61
61
  shape?: AgentShape;
62
62
  /** Face kind. Default `eyes`. */
63
63
  face?: BotAvatarFace;
64
- /** Face ink. Default `#1E1A33`. */
64
+ /** Face ink. Default: dark on a light body, light on a dark one. */
65
65
  ink?: string;
66
66
  /** Rendered size in px. Omit to fill 75% of the slot. */
67
67
  size?: number;
@@ -69,4 +69,6 @@ export type AgentAvatarProps = Omit<
69
69
  style?: string;
70
70
  onclick?: (event: MouseEvent) => void;
71
71
  'aria-label'?: string;
72
+ /** Hide the canvas from assistive tech when a parent already names the agent. */
73
+ 'aria-hidden'?: boolean | 'true' | 'false';
72
74
  };
@@ -0,0 +1,86 @@
1
+ <script lang="ts" module>
2
+ import type { ButtonProps } from "../../shadcn/button/index.js";
3
+
4
+ export type AiStatus = "idle" | "processing" | "done";
5
+
6
+ export interface AiButtonProps {
7
+ status?: AiStatus;
8
+ label?: string;
9
+ processingLabel?: string;
10
+ variant?: ButtonProps["variant"];
11
+ size?: ButtonProps["size"];
12
+ disabled?: boolean;
13
+ onclick?: (e: MouseEvent) => void;
14
+ class?: string;
15
+ iconOnly?: boolean;
16
+ }
17
+ </script>
18
+
19
+ <script lang="ts">
20
+ // =========================
21
+ // IMPORTS
22
+ // =========================
23
+ import { cn } from "@heyramzi/utils";
24
+ import { Sparkle } from "phosphor-svelte";
25
+ import { Spinner } from "../../shadcn/spinner/index.js";
26
+ import { Button } from "../../shadcn/button/index.js";
27
+
28
+ // =========================
29
+ // PROPS
30
+ // =========================
31
+ let {
32
+ status = "idle",
33
+ label = "Generate",
34
+ processingLabel = "Processing...",
35
+ variant = "outline",
36
+ size = "sm",
37
+ disabled = false,
38
+ onclick,
39
+ class: className,
40
+ iconOnly = false,
41
+ }: AiButtonProps = $props();
42
+ </script>
43
+
44
+ <!-- ===========================
45
+ MARKUP
46
+ =========================== -->
47
+ {#if iconOnly}
48
+ <Button
49
+ {variant}
50
+ size="sm"
51
+ icon
52
+ disabled={disabled || status === "processing"}
53
+ onclick={onclick}
54
+ class={cn(
55
+ "relative",
56
+ status === "processing" && "pointer-events-none",
57
+ className,
58
+ )}
59
+ >
60
+ {#if status === "processing"}
61
+ <Spinner class="size-4" />
62
+ {:else}
63
+ <Sparkle class="size-4" weight="fill" />
64
+ {/if}
65
+ </Button>
66
+ {:else}
67
+ <Button
68
+ {variant}
69
+ {size}
70
+ disabled={disabled || status === "processing"}
71
+ onclick={onclick}
72
+ class={cn(
73
+ "gap-1.5",
74
+ status === "processing" && "pointer-events-none",
75
+ className,
76
+ )}
77
+ >
78
+ {#if status === "processing"}
79
+ <Spinner class="size-3.5" />
80
+ <span>{processingLabel}</span>
81
+ {:else}
82
+ <Sparkle class="size-3.5" weight="fill" />
83
+ <span>{label}</span>
84
+ {/if}
85
+ </Button>
86
+ {/if}
@@ -0,0 +1,4 @@
1
+ import AiButton from "./AiButton.svelte";
2
+ import type { AiButtonProps, AiStatus } from "./AiButton.svelte";
3
+
4
+ export { AiButton, type AiButtonProps, type AiStatus };
@@ -23,15 +23,15 @@
23
23
  ...rest
24
24
  }: ConfirmationProps = $props();
25
25
 
26
- // WHY: setContext runs once on mount but the value is referenced reactively via the proxy Svelte sets up on objects passed to setContext.
27
-
28
- // State changes rerun any $derived in child components.
29
- const ctx = $state({ kind, executing });
30
- $effect(() => {
31
- ctx.kind = kind;
32
- ctx.executing = executing;
26
+ // WHY getters: setContext runs once on mount, so the parts read the props through these and rerun their $derived when a prop changes.
27
+ setConfirmationContext({
28
+ get kind() {
29
+ return kind;
30
+ },
31
+ get executing() {
32
+ return executing;
33
+ },
33
34
  });
34
- setConfirmationContext(ctx);
35
35
  </script>
36
36
 
37
37
  {#if kind !== "idle"}
@@ -5,8 +5,8 @@ const CONFIRMATION_CONTEXT_KEY = Symbol("confirmation-context");
5
5
  export type ConfirmationState = "idle" | "requested" | "accepted" | "rejected" | "failed";
6
6
 
7
7
  export interface ConfirmationContextValue {
8
- kind: ConfirmationState;
9
- executing: boolean;
8
+ readonly kind: ConfirmationState;
9
+ readonly executing: boolean;
10
10
  }
11
11
 
12
12
  export function setConfirmationContext(value: ConfirmationContextValue) {