@joewinke/jatui 0.1.33 → 0.2.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 (209) hide show
  1. package/CLAUDE.md +22 -0
  2. package/dist/actions/attention.d.ts +24 -0
  3. package/dist/actions/attention.js +94 -0
  4. package/dist/actions/floating.d.ts +53 -0
  5. package/dist/actions/floating.js +158 -0
  6. package/dist/actions/staggerEntrance.d.ts +28 -0
  7. package/dist/actions/staggerEntrance.js +71 -0
  8. package/dist/actions/tooltip.d.ts +28 -0
  9. package/dist/actions/tooltip.js +186 -0
  10. package/dist/assets/sounds/README.md +47 -0
  11. package/dist/assets/sounds/error.d.ts +1 -0
  12. package/dist/assets/sounds/error.js +4 -0
  13. package/dist/assets/sounds/notify.d.ts +1 -0
  14. package/dist/assets/sounds/notify.js +4 -0
  15. package/dist/assets/sounds/pop.d.ts +1 -0
  16. package/dist/assets/sounds/pop.js +4 -0
  17. package/dist/assets/sounds/send.d.ts +1 -0
  18. package/dist/assets/sounds/send.js +4 -0
  19. package/dist/assets/sounds/success.d.ts +1 -0
  20. package/dist/assets/sounds/success.js +4 -0
  21. package/dist/assets/sounds/toggle.d.ts +1 -0
  22. package/dist/assets/sounds/toggle.js +4 -0
  23. package/dist/components/AnimatedNumber.svelte +112 -0
  24. package/dist/components/AnimatedNumber.svelte.d.ts +8 -0
  25. package/dist/components/Badge.svelte +75 -4
  26. package/dist/components/Badge.svelte.d.ts +6 -1
  27. package/dist/components/Button.svelte +196 -21
  28. package/dist/components/Button.svelte.d.ts +10 -1
  29. package/dist/components/Carousel.svelte +265 -0
  30. package/dist/components/Carousel.svelte.d.ts +21 -0
  31. package/dist/components/ChipInput.svelte +63 -3
  32. package/dist/components/Collapse.svelte +70 -14
  33. package/dist/components/Collapse.svelte.d.ts +11 -0
  34. package/dist/components/ColorSelector.svelte +21 -24
  35. package/dist/components/CommandPalette.svelte +475 -0
  36. package/dist/components/CommandPalette.svelte.d.ts +3 -0
  37. package/dist/components/ContextMenu.svelte +9 -73
  38. package/dist/components/CopyButton.svelte +156 -0
  39. package/dist/components/CopyButton.svelte.d.ts +12 -0
  40. package/dist/components/HoldToConfirm.svelte +183 -0
  41. package/dist/components/HoldToConfirm.svelte.d.ts +11 -0
  42. package/dist/components/KeyboardShortcutsOverlay.svelte +54 -8
  43. package/dist/components/KeyboardShortcutsOverlay.svelte.d.ts +3 -0
  44. package/dist/components/PasswordMeter.svelte +87 -0
  45. package/dist/components/PasswordMeter.svelte.d.ts +8 -0
  46. package/dist/components/PinInput.svelte +186 -0
  47. package/dist/components/PinInput.svelte.d.ts +13 -0
  48. package/dist/components/QuantityStepper.svelte +185 -0
  49. package/dist/components/QuantityStepper.svelte.d.ts +12 -0
  50. package/dist/components/Skeleton.svelte +123 -0
  51. package/dist/components/Skeleton.svelte.d.ts +12 -0
  52. package/dist/components/SpeechForm.svelte +37 -0
  53. package/dist/components/StatusBadge.svelte +31 -3
  54. package/dist/components/StatusBadge.svelte.d.ts +0 -5
  55. package/dist/components/Tabs.svelte +270 -0
  56. package/dist/components/Tabs.svelte.d.ts +15 -0
  57. package/dist/components/TextInput.svelte +138 -38
  58. package/dist/components/TextInput.svelte.d.ts +9 -1
  59. package/dist/components/VoicePlayer.svelte +51 -0
  60. package/dist/components/basic/Alert.svelte +36 -0
  61. package/dist/components/basic/Alert.svelte.d.ts +9 -0
  62. package/dist/components/basic/Breadcrumb.svelte +27 -0
  63. package/dist/components/basic/Breadcrumb.svelte.d.ts +11 -0
  64. package/dist/components/basic/ButtonGroup.svelte +15 -0
  65. package/dist/components/basic/ButtonGroup.svelte.d.ts +8 -0
  66. package/dist/components/basic/Checkbox.svelte +85 -0
  67. package/dist/components/basic/Checkbox.svelte.d.ts +15 -0
  68. package/dist/components/basic/Empty.svelte +17 -0
  69. package/dist/components/basic/Empty.svelte.d.ts +8 -0
  70. package/dist/components/basic/Kbd.svelte +16 -0
  71. package/dist/components/basic/Kbd.svelte.d.ts +8 -0
  72. package/dist/components/basic/Pagination.svelte +104 -0
  73. package/dist/components/basic/Pagination.svelte.d.ts +12 -0
  74. package/dist/components/basic/RadioGroup.svelte +71 -0
  75. package/dist/components/basic/RadioGroup.svelte.d.ts +18 -0
  76. package/dist/components/basic/Separator.svelte +19 -0
  77. package/dist/components/basic/Separator.svelte.d.ts +8 -0
  78. package/dist/components/basic/Slider.svelte +72 -0
  79. package/dist/components/basic/Slider.svelte.d.ts +17 -0
  80. package/dist/components/basic/Switch.svelte +88 -0
  81. package/dist/components/basic/Switch.svelte.d.ts +14 -0
  82. package/dist/components/basic/Toggle.svelte +43 -0
  83. package/dist/components/basic/Toggle.svelte.d.ts +14 -0
  84. package/dist/components/graph/GraphCanvas.svelte +103 -0
  85. package/dist/components/graph/GraphCanvas.svelte.d.ts +22 -0
  86. package/dist/components/graph/JatuiNode.svelte +128 -0
  87. package/dist/components/graph/JatuiNode.svelte.d.ts +14 -0
  88. package/dist/components/graph/theme.d.ts +27 -0
  89. package/dist/components/graph/theme.js +136 -0
  90. package/dist/components/messaging/MessageItem.svelte +92 -0
  91. package/dist/components/messaging/MessageItem.svelte.d.ts +5 -0
  92. package/dist/components/messaging/TypingIndicator.svelte +63 -0
  93. package/dist/components/messaging/TypingIndicator.svelte.d.ts +7 -0
  94. package/dist/components/pipeline/PipelineColumn.svelte +44 -3
  95. package/dist/components/progress/ProgressBar.svelte +189 -0
  96. package/dist/components/progress/ProgressBar.svelte.d.ts +17 -0
  97. package/dist/components/progress/ProgressRing.svelte +138 -0
  98. package/dist/components/progress/ProgressRing.svelte.d.ts +12 -0
  99. package/dist/components/progress/Steps.svelte +195 -0
  100. package/dist/components/progress/Steps.svelte.d.ts +14 -0
  101. package/dist/components/shell/NavigationMenu.svelte +422 -0
  102. package/dist/components/shell/NavigationMenu.svelte.d.ts +43 -0
  103. package/dist/components/shell/Sidebar.svelte +276 -0
  104. package/dist/components/shell/Sidebar.svelte.d.ts +32 -0
  105. package/dist/components/shell/SidebarGroup.svelte +180 -0
  106. package/dist/components/shell/SidebarGroup.svelte.d.ts +19 -0
  107. package/dist/components/shell/SidebarItem.svelte +170 -0
  108. package/dist/components/shell/SidebarItem.svelte.d.ts +24 -0
  109. package/dist/components/shell/shell.d.ts +30 -0
  110. package/dist/components/shell/shell.js +65 -0
  111. package/dist/components/table/DataTable.svelte +364 -0
  112. package/dist/components/table/DataTable.svelte.d.ts +4 -0
  113. package/dist/components/table/flexRender.d.ts +31 -0
  114. package/dist/components/table/flexRender.js +32 -0
  115. package/dist/components/table/types.d.ts +28 -0
  116. package/dist/components/table/types.js +1 -0
  117. package/dist/components/toast/ToastHost.svelte +21 -0
  118. package/dist/components/toast/ToastHost.svelte.d.ts +3 -0
  119. package/dist/components/toast/ToastItem.svelte +287 -0
  120. package/dist/components/toast/ToastItem.svelte.d.ts +9 -0
  121. package/dist/index.d.ts +59 -0
  122. package/dist/index.js +67 -0
  123. package/dist/showcase/AnimatedBeam.svelte +183 -0
  124. package/dist/showcase/AnimatedBeam.svelte.d.ts +26 -0
  125. package/dist/showcase/AuroraDrift.svelte +74 -0
  126. package/dist/showcase/AuroraDrift.svelte.d.ts +9 -0
  127. package/dist/showcase/BentoCard.svelte +50 -0
  128. package/dist/showcase/BentoCard.svelte.d.ts +14 -0
  129. package/dist/showcase/BentoGrid.svelte +43 -0
  130. package/dist/showcase/BentoGrid.svelte.d.ts +12 -0
  131. package/dist/showcase/BorderBeam.svelte +79 -0
  132. package/dist/showcase/BorderBeam.svelte.d.ts +12 -0
  133. package/dist/showcase/ClipWipe.svelte +82 -0
  134. package/dist/showcase/ClipWipe.svelte.d.ts +12 -0
  135. package/dist/showcase/Confetti.svelte +71 -0
  136. package/dist/showcase/Confetti.svelte.d.ts +10 -0
  137. package/dist/showcase/CursorSpotlight.svelte +85 -0
  138. package/dist/showcase/CursorSpotlight.svelte.d.ts +9 -0
  139. package/dist/showcase/DeviceMock.svelte +186 -0
  140. package/dist/showcase/DeviceMock.svelte.d.ts +11 -0
  141. package/dist/showcase/Dock.svelte +123 -0
  142. package/dist/showcase/Dock.svelte.d.ts +17 -0
  143. package/dist/showcase/FlipCard.svelte +103 -0
  144. package/dist/showcase/FlipCard.svelte.d.ts +10 -0
  145. package/dist/showcase/FocusReveal.svelte +83 -0
  146. package/dist/showcase/FocusReveal.svelte.d.ts +11 -0
  147. package/dist/showcase/Globe.svelte +202 -0
  148. package/dist/showcase/Globe.svelte.d.ts +16 -0
  149. package/dist/showcase/KenBurns.svelte +105 -0
  150. package/dist/showcase/KenBurns.svelte.d.ts +13 -0
  151. package/dist/showcase/Marquee.svelte +89 -0
  152. package/dist/showcase/Marquee.svelte.d.ts +9 -0
  153. package/dist/showcase/ParallaxTilt.svelte +98 -0
  154. package/dist/showcase/ParallaxTilt.svelte.d.ts +10 -0
  155. package/dist/showcase/ScrambleText.svelte +98 -0
  156. package/dist/showcase/ScrambleText.svelte.d.ts +11 -0
  157. package/dist/showcase/ShineSweep.svelte +81 -0
  158. package/dist/showcase/ShineSweep.svelte.d.ts +9 -0
  159. package/dist/showcase/Terminal.svelte +230 -0
  160. package/dist/showcase/Terminal.svelte.d.ts +18 -0
  161. package/dist/showcase/TextSplitReveal.svelte +146 -0
  162. package/dist/showcase/TextSplitReveal.svelte.d.ts +13 -0
  163. package/dist/showcase/TextWave.svelte +55 -0
  164. package/dist/showcase/TextWave.svelte.d.ts +8 -0
  165. package/dist/showcase/TypewriterText.svelte +130 -0
  166. package/dist/showcase/TypewriterText.svelte.d.ts +9 -0
  167. package/dist/showcase/backdrop/GrainGradient.svelte +126 -0
  168. package/dist/showcase/backdrop/GrainGradient.svelte.d.ts +23 -0
  169. package/dist/showcase/backdrop/MeshGradient.svelte +104 -0
  170. package/dist/showcase/backdrop/MeshGradient.svelte.d.ts +20 -0
  171. package/dist/showcase/backdrop/Metaballs.svelte +116 -0
  172. package/dist/showcase/backdrop/Metaballs.svelte.d.ts +18 -0
  173. package/dist/showcase/backdrop/NeuroNoise.svelte +115 -0
  174. package/dist/showcase/backdrop/NeuroNoise.svelte.d.ts +20 -0
  175. package/dist/showcase/backdrop/ShowcaseBackdrop.svelte +48 -0
  176. package/dist/showcase/backdrop/ShowcaseBackdrop.svelte.d.ts +12 -0
  177. package/dist/showcase/generative/GenerativeBackdrop.svelte +234 -0
  178. package/dist/showcase/generative/GenerativeBackdrop.svelte.d.ts +9 -0
  179. package/dist/showcase/generative/intent.d.ts +7 -0
  180. package/dist/showcase/generative/intent.js +67 -0
  181. package/dist/showcase/generative/manifest.d.ts +9 -0
  182. package/dist/showcase/generative/manifest.js +97 -0
  183. package/dist/showcase/generative/types.d.ts +48 -0
  184. package/dist/showcase/generative/types.js +8 -0
  185. package/dist/showcase/index.d.ts +36 -0
  186. package/dist/showcase/index.js +46 -0
  187. package/dist/showcase/scroll/SmoothScroll.svelte +49 -0
  188. package/dist/showcase/scroll/SmoothScroll.svelte.d.ts +12 -0
  189. package/dist/showcase/scroll/scrollProgress.d.ts +24 -0
  190. package/dist/showcase/scroll/scrollProgress.js +65 -0
  191. package/dist/showcase/scroll/scrollReveal.d.ts +30 -0
  192. package/dist/showcase/scroll/scrollReveal.js +70 -0
  193. package/dist/stores/commandPalette.svelte.d.ts +24 -0
  194. package/dist/stores/commandPalette.svelte.js +82 -0
  195. package/dist/stores/toast.svelte.d.ts +28 -0
  196. package/dist/stores/toast.svelte.js +33 -0
  197. package/dist/styles/motion.css +46 -0
  198. package/dist/styles/tokens.css +31 -0
  199. package/dist/transitions/glide.d.ts +31 -0
  200. package/dist/transitions/glide.js +58 -0
  201. package/dist/utils/sound.d.ts +6 -0
  202. package/dist/utils/sound.js +113 -0
  203. package/dist/utils/splitText.d.ts +25 -0
  204. package/dist/utils/splitText.js +94 -0
  205. package/dist/utils/themeWipe.d.ts +21 -0
  206. package/dist/utils/themeWipe.js +94 -0
  207. package/dist/utils/viewTransition.d.ts +23 -0
  208. package/dist/utils/viewTransition.js +75 -0
  209. package/package.json +50 -3
@@ -0,0 +1,186 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ length?: number;
4
+ value?: string;
5
+ masked?: boolean;
6
+ numeric?: boolean;
7
+ error?: string;
8
+ disabled?: boolean;
9
+ label?: string;
10
+ oncomplete?: (value: string) => void;
11
+ }
12
+
13
+ let {
14
+ length = 6,
15
+ value = $bindable(''),
16
+ masked = false,
17
+ numeric = false,
18
+ error = '',
19
+ disabled = false,
20
+ label = '',
21
+ oncomplete
22
+ }: Props = $props();
23
+
24
+ let cells: string[] = $state(Array(length).fill(''));
25
+ let inputEls: HTMLInputElement[] = $state([]);
26
+ let poppingCells: boolean[] = $state(Array(length).fill(false));
27
+ let shaking = $state(false);
28
+ let hadError = false;
29
+
30
+ $effect(() => {
31
+ const hasError = !!error;
32
+ if (hasError && !hadError) {
33
+ const reduce = typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
34
+ if (!reduce) {
35
+ shaking = false;
36
+ // force reflow
37
+ void document.body?.offsetWidth;
38
+ shaking = true;
39
+ }
40
+ }
41
+ hadError = hasError;
42
+ });
43
+
44
+ function popCell(i: number) {
45
+ const reduce = typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
46
+ if (reduce) return;
47
+ poppingCells[i] = true;
48
+ setTimeout(() => { poppingCells[i] = false; }, 350);
49
+ }
50
+
51
+ function onInput(i: number, e: Event) {
52
+ const el = e.target as HTMLInputElement;
53
+ let char = el.value;
54
+
55
+ if (numeric) char = char.replace(/\D/g, '');
56
+
57
+ if (char.length > 1) {
58
+ // paste — split across cells
59
+ const chars = char.slice(0, length - i).split('');
60
+ chars.forEach((c, offset) => {
61
+ if (i + offset < length) {
62
+ cells[i + offset] = c;
63
+ if (c) popCell(i + offset);
64
+ }
65
+ });
66
+ const nextIdx = Math.min(i + chars.length, length - 1);
67
+ inputEls[nextIdx]?.focus();
68
+ } else {
69
+ cells[i] = char.slice(-1);
70
+ if (cells[i]) {
71
+ popCell(i);
72
+ inputEls[i + 1]?.focus();
73
+ }
74
+ }
75
+
76
+ value = cells.join('');
77
+ if (value.length === length && !value.includes('')) {
78
+ oncomplete?.(value);
79
+ }
80
+ }
81
+
82
+ function onKeydown(i: number, e: KeyboardEvent) {
83
+ if (e.key === 'Backspace') {
84
+ if (!cells[i]) {
85
+ inputEls[i - 1]?.focus();
86
+ } else {
87
+ cells[i] = '';
88
+ value = cells.join('');
89
+ }
90
+ e.preventDefault();
91
+ } else if (e.key === 'ArrowLeft') {
92
+ inputEls[i - 1]?.focus();
93
+ } else if (e.key === 'ArrowRight') {
94
+ inputEls[i + 1]?.focus();
95
+ }
96
+ }
97
+
98
+ function onFocus(el: HTMLInputElement) {
99
+ el.select();
100
+ }
101
+
102
+ const groupId = `pin-${Math.random().toString(36).slice(2, 9)}`;
103
+ </script>
104
+
105
+ <div class="pin-input-root" class:shaking>
106
+ {#if label}
107
+ <div class="label mb-1">
108
+ <span class="text-[0.8125rem] font-medium text-base-content/85">{label}</span>
109
+ </div>
110
+ {/if}
111
+
112
+ <div
113
+ class="flex gap-2"
114
+ role="group"
115
+ aria-label={label || 'PIN input'}
116
+ aria-describedby={error ? `${groupId}-error` : undefined}
117
+ >
118
+ {#each Array(length) as _, i}
119
+ <input
120
+ bind:this={inputEls[i]}
121
+ type={masked ? 'password' : 'text'}
122
+ inputmode={numeric ? 'numeric' : 'text'}
123
+ maxlength={1}
124
+ value={cells[i]}
125
+ {disabled}
126
+ aria-label={`Digit ${i + 1} of ${length}`}
127
+ class="pin-cell input input-bordered h-12 w-11 text-center text-lg font-semibold transition-[border-color] duration-[var(--motion-fast)]"
128
+ class:input-error={!!error}
129
+ class:pin-cell--filled={!!cells[i]}
130
+ class:pin-cell--pop={poppingCells[i]}
131
+ oninput={(e) => onInput(i, e)}
132
+ onkeydown={(e) => onKeydown(i, e)}
133
+ onfocus={(e) => onFocus(e.target as HTMLInputElement)}
134
+ />
135
+ {/each}
136
+ </div>
137
+
138
+ {#if error}
139
+ <div class="label mt-1">
140
+ <span id="{groupId}-error" class="text-[0.8125rem] text-error" role="alert">{error}</span>
141
+ </div>
142
+ {/if}
143
+ </div>
144
+
145
+ <style>
146
+ .pin-cell {
147
+ transform-origin: center;
148
+ transition:
149
+ transform var(--motion-fast) var(--motion-spring),
150
+ border-color var(--motion-fast) var(--motion-glide);
151
+ }
152
+
153
+ .pin-cell--filled {
154
+ border-color: oklch(var(--p));
155
+ }
156
+
157
+ .pin-cell--pop {
158
+ transform: scale(1.14);
159
+ }
160
+
161
+ @keyframes pin-shake {
162
+ 0%, 100% { transform: translateX(0); }
163
+ 15% { transform: translateX(-6px); }
164
+ 30% { transform: translateX(6px); }
165
+ 45% { transform: translateX(-4px); }
166
+ 60% { transform: translateX(4px); }
167
+ 75% { transform: translateX(-2px); }
168
+ 90% { transform: translateX(2px); }
169
+ }
170
+
171
+ .shaking {
172
+ animation: pin-shake var(--motion-move) var(--motion-glide);
173
+ }
174
+
175
+ @media (prefers-reduced-motion: reduce) {
176
+ .pin-cell {
177
+ transition: border-color var(--motion-fast) var(--motion-glide);
178
+ }
179
+ .pin-cell--pop {
180
+ transform: none;
181
+ }
182
+ .shaking {
183
+ animation: none;
184
+ }
185
+ }
186
+ </style>
@@ -0,0 +1,13 @@
1
+ interface Props {
2
+ length?: number;
3
+ value?: string;
4
+ masked?: boolean;
5
+ numeric?: boolean;
6
+ error?: string;
7
+ disabled?: boolean;
8
+ label?: string;
9
+ oncomplete?: (value: string) => void;
10
+ }
11
+ declare const PinInput: import("svelte").Component<Props, {}, "value">;
12
+ type PinInput = ReturnType<typeof PinInput>;
13
+ export default PinInput;
@@ -0,0 +1,185 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ value?: number;
4
+ min?: number;
5
+ max?: number;
6
+ step?: number;
7
+ disabled?: boolean;
8
+ label?: string;
9
+ size?: 'sm' | 'md' | 'lg';
10
+ }
11
+
12
+ let {
13
+ value = $bindable(1),
14
+ min = 0,
15
+ max = Infinity,
16
+ step = 1,
17
+ disabled = false,
18
+ label = '',
19
+ size = 'md'
20
+ }: Props = $props();
21
+
22
+ let bumping = $state(false);
23
+ let direction = $state<'up' | 'down'>('up');
24
+ let holdTimer: ReturnType<typeof setTimeout> | null = null;
25
+ let holdInterval: ReturnType<typeof setInterval> | null = null;
26
+
27
+ const atMin = $derived(value <= min);
28
+ const atMax = $derived(value >= max);
29
+
30
+ const sizeClasses = {
31
+ sm: { btn: 'btn-sm h-7 min-h-0 w-7 text-sm', val: 'min-w-[2ch] text-sm', wrap: 'gap-1' },
32
+ md: { btn: 'h-9 min-h-0 w-9 text-base', val: 'min-w-[3ch] text-base', wrap: 'gap-2' },
33
+ lg: { btn: 'btn-lg h-11 min-h-0 w-11 text-lg', val: 'min-w-[3ch] text-lg', wrap: 'gap-2' }
34
+ };
35
+
36
+ const sc = $derived(sizeClasses[size]);
37
+
38
+ function bump() {
39
+ const reduce = typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
40
+ if (reduce) return;
41
+ bumping = false;
42
+ void document.body?.offsetWidth;
43
+ bumping = true;
44
+ }
45
+
46
+ function change(delta: number) {
47
+ const next = Math.min(max, Math.max(min, value + delta));
48
+ if (next === value) return;
49
+ direction = delta > 0 ? 'up' : 'down';
50
+ value = next;
51
+ bump();
52
+ }
53
+
54
+ function startHold(delta: number) {
55
+ change(delta);
56
+ holdTimer = setTimeout(() => {
57
+ holdInterval = setInterval(() => change(delta), 80);
58
+ }, 400);
59
+ }
60
+
61
+ function stopHold() {
62
+ if (holdTimer) { clearTimeout(holdTimer); holdTimer = null; }
63
+ if (holdInterval) { clearInterval(holdInterval); holdInterval = null; }
64
+ }
65
+
66
+ function onValueInput(e: Event) {
67
+ const raw = (e.target as HTMLInputElement).value;
68
+ const parsed = parseFloat(raw);
69
+ if (!isNaN(parsed)) {
70
+ value = Math.min(max, Math.max(min, parsed));
71
+ bump();
72
+ }
73
+ }
74
+
75
+ const inputId = `qs-${Math.random().toString(36).slice(2, 9)}`;
76
+ </script>
77
+
78
+ <div class="quantity-stepper inline-flex flex-col gap-1">
79
+ {#if label}
80
+ <label for={inputId} class="text-[0.8125rem] font-medium text-base-content/85">{label}</label>
81
+ {/if}
82
+
83
+ <div class="inline-flex items-center {sc.wrap}">
84
+ <button
85
+ type="button"
86
+ class="stepper-btn btn btn-circle btn-outline {sc.btn}"
87
+ aria-label="Decrease"
88
+ disabled={disabled || atMin}
89
+ onpointerdown={() => startHold(-step)}
90
+ onpointerup={stopHold}
91
+ onpointerleave={stopHold}
92
+ onpointercancel={stopHold}
93
+ >
94
+ −
95
+ </button>
96
+
97
+ <input
98
+ id={inputId}
99
+ type="number"
100
+ class="stepper-val input input-ghost {sc.val} text-center font-semibold tabular-nums [appearance:textfield] [&::-webkit-inner-spin-button]:hidden [&::-webkit-outer-spin-button]:hidden"
101
+ class:bumping
102
+ class:bump-up={direction === 'up' && bumping}
103
+ class:bump-down={direction === 'down' && bumping}
104
+ {value}
105
+ {min}
106
+ {max}
107
+ {step}
108
+ {disabled}
109
+ aria-label={label || 'Quantity'}
110
+ oninput={onValueInput}
111
+ onblur={(e) => {
112
+ const parsed = parseFloat((e.target as HTMLInputElement).value);
113
+ if (isNaN(parsed)) value = min;
114
+ }}
115
+ onanimationend={() => { bumping = false; }}
116
+ />
117
+
118
+ <button
119
+ type="button"
120
+ class="stepper-btn btn btn-circle btn-outline {sc.btn}"
121
+ aria-label="Increase"
122
+ disabled={disabled || atMax}
123
+ onpointerdown={() => startHold(step)}
124
+ onpointerup={stopHold}
125
+ onpointerleave={stopHold}
126
+ onpointercancel={stopHold}
127
+ >
128
+ +
129
+ </button>
130
+ </div>
131
+ </div>
132
+
133
+ <style>
134
+ .stepper-btn {
135
+ transition: transform var(--motion-instant) var(--motion-glide);
136
+ user-select: none;
137
+ touch-action: none;
138
+ }
139
+
140
+ .stepper-btn:active:not(:disabled) {
141
+ transform: scale(0.9);
142
+ }
143
+
144
+ .stepper-val {
145
+ display: inline-block;
146
+ transform-origin: center;
147
+ transition: transform var(--motion-fast) var(--motion-spring);
148
+ }
149
+
150
+ @keyframes bump-up {
151
+ 0% { transform: translateY(0) scale(1); }
152
+ 40% { transform: translateY(-4px) scale(1.18); }
153
+ 100% { transform: translateY(0) scale(1); }
154
+ }
155
+
156
+ @keyframes bump-down {
157
+ 0% { transform: translateY(0) scale(1); }
158
+ 40% { transform: translateY(4px) scale(1.18); }
159
+ 100% { transform: translateY(0) scale(1); }
160
+ }
161
+
162
+ .bump-up {
163
+ animation: bump-up var(--motion-enter) var(--motion-spring);
164
+ }
165
+
166
+ .bump-down {
167
+ animation: bump-down var(--motion-enter) var(--motion-spring);
168
+ }
169
+
170
+ @media (prefers-reduced-motion: reduce) {
171
+ .stepper-btn {
172
+ transition: none;
173
+ }
174
+ .stepper-btn:active:not(:disabled) {
175
+ transform: none;
176
+ }
177
+ .stepper-val {
178
+ transition: none;
179
+ }
180
+ .bump-up,
181
+ .bump-down {
182
+ animation: none;
183
+ }
184
+ }
185
+ </style>
@@ -0,0 +1,12 @@
1
+ interface Props {
2
+ value?: number;
3
+ min?: number;
4
+ max?: number;
5
+ step?: number;
6
+ disabled?: boolean;
7
+ label?: string;
8
+ size?: 'sm' | 'md' | 'lg';
9
+ }
10
+ declare const QuantityStepper: import("svelte").Component<Props, {}, "value">;
11
+ type QuantityStepper = ReturnType<typeof QuantityStepper>;
12
+ export default QuantityStepper;
@@ -0,0 +1,123 @@
1
+ <script lang="ts">
2
+ import { fly, fade } from 'svelte/transition';
3
+
4
+ interface Props {
5
+ variant?: 'text' | 'avatar' | 'block';
6
+ loaded?: boolean;
7
+ width?: string;
8
+ height?: string;
9
+ lines?: number;
10
+ class?: string;
11
+ children?: import('svelte').Snippet;
12
+ }
13
+
14
+ let {
15
+ variant = 'text',
16
+ loaded = false,
17
+ width,
18
+ height,
19
+ lines = 3,
20
+ class: className = '',
21
+ children
22
+ }: Props = $props();
23
+ </script>
24
+
25
+ {#if !loaded}
26
+ <div
27
+ class="skeleton-wrap {className}"
28
+ aria-busy="true"
29
+ aria-label="Loading"
30
+ role="status"
31
+ >
32
+ {#if variant === 'avatar'}
33
+ <div class="skel-avatar" style={width ? `width:${width};height:${height ?? width}` : ''}></div>
34
+ {:else if variant === 'block'}
35
+ <div
36
+ class="skel-block"
37
+ style={[width && `width:${width}`, height && `height:${height}`].filter(Boolean).join(';')}
38
+ ></div>
39
+ {:else}
40
+ {#each { length: lines } as _, i}
41
+ <div class="skel-line" class:skel-line--short={i === lines - 1 && lines > 1}></div>
42
+ {/each}
43
+ {/if}
44
+ </div>
45
+ {:else if children}
46
+ <div
47
+ in:fly={{ y: 6, duration: 200, delay: 0 }}
48
+ style="animation-timing-function: var(--motion-glide)"
49
+ >
50
+ {@render children()}
51
+ </div>
52
+ {/if}
53
+
54
+ <style>
55
+ .skeleton-wrap {
56
+ display: flex;
57
+ flex-direction: column;
58
+ gap: 0.5rem;
59
+ }
60
+
61
+ .skel-line {
62
+ height: 0.875rem;
63
+ border-radius: 0.375rem;
64
+ background: linear-gradient(
65
+ 90deg,
66
+ oklch(var(--b2) / 1) 0%,
67
+ oklch(var(--b3) / 1) 25%,
68
+ oklch(var(--b2) / 1) 50%
69
+ );
70
+ background-size: 280% 100%;
71
+ animation: skel-shimmer 1.5s linear infinite;
72
+ }
73
+
74
+ .skel-line--short {
75
+ width: 60%;
76
+ }
77
+
78
+ .skel-avatar {
79
+ width: 3rem;
80
+ height: 3rem;
81
+ border-radius: 9999px;
82
+ background: linear-gradient(
83
+ 90deg,
84
+ oklch(var(--b2) / 1) 0%,
85
+ oklch(var(--b3) / 1) 25%,
86
+ oklch(var(--b2) / 1) 50%
87
+ );
88
+ background-size: 280% 100%;
89
+ animation: skel-shimmer 1.5s linear infinite;
90
+ flex-shrink: 0;
91
+ }
92
+
93
+ .skel-block {
94
+ width: 100%;
95
+ height: 8rem;
96
+ border-radius: 0.5rem;
97
+ background: linear-gradient(
98
+ 90deg,
99
+ oklch(var(--b2) / 1) 0%,
100
+ oklch(var(--b3) / 1) 25%,
101
+ oklch(var(--b2) / 1) 50%
102
+ );
103
+ background-size: 280% 100%;
104
+ animation: skel-shimmer 1.5s linear infinite;
105
+ }
106
+
107
+ @keyframes skel-shimmer {
108
+ 0% {
109
+ background-position: 140% 0;
110
+ }
111
+ 100% {
112
+ background-position: -140% 0;
113
+ }
114
+ }
115
+
116
+ @media (prefers-reduced-motion: reduce) {
117
+ .skel-line,
118
+ .skel-avatar,
119
+ .skel-block {
120
+ animation: none;
121
+ }
122
+ }
123
+ </style>
@@ -0,0 +1,12 @@
1
+ interface Props {
2
+ variant?: 'text' | 'avatar' | 'block';
3
+ loaded?: boolean;
4
+ width?: string;
5
+ height?: string;
6
+ lines?: number;
7
+ class?: string;
8
+ children?: import('svelte').Snippet;
9
+ }
10
+ declare const Skeleton: import("svelte").Component<Props, {}, "">;
11
+ type Skeleton = ReturnType<typeof Skeleton>;
12
+ export default Skeleton;
@@ -36,6 +36,7 @@
36
36
  }>()
37
37
 
38
38
  let remaining = $derived(maxLength - (text?.length || 0))
39
+ // `isCreating` doubles as "recording/processing" state for the breathing orb indicator
39
40
 
40
41
  $effect(() => {
41
42
  return () => {
@@ -91,6 +92,15 @@
91
92
  <p class="text-sm">{infoText}</p>
92
93
  </div>
93
94
  {/if}
95
+
96
+
97
+ <!-- Breathing orb status row — visible while processing -->
98
+ {#if isCreating}
99
+ <div class="flex items-center gap-3 py-1" aria-live="polite">
100
+ <span class="sf-orb" aria-hidden="true"></span>
101
+ <span class="text-sm text-base-content/70 sf-orb-label">Converting your text to speech…</span>
102
+ </div>
103
+ {/if}
94
104
  </div>
95
105
 
96
106
  <div class="flex justify-end gap-2 mt-6">
@@ -112,3 +122,30 @@
112
122
  {/if}
113
123
  </button>
114
124
  </div>
125
+
126
+ <style>
127
+ /* Breathing orb — slow 2s scale/opacity breathe (functional status). */
128
+ .sf-orb {
129
+ display: inline-block;
130
+ width: 10px;
131
+ height: 10px;
132
+ border-radius: 50%;
133
+ background-color: currentColor;
134
+ flex-shrink: 0;
135
+ animation: sf-breathe 2s ease-in-out infinite;
136
+ }
137
+
138
+ @keyframes sf-breathe {
139
+ 0%, 100% { transform: scale(0.85); opacity: 0.55; }
140
+ 50% { transform: scale(1.15); opacity: 1; }
141
+ }
142
+
143
+ @media (prefers-reduced-motion: reduce) {
144
+ .sf-orb {
145
+ animation: none;
146
+ /* Static affordance: solid dot at rest size */
147
+ transform: scale(1);
148
+ opacity: 1;
149
+ }
150
+ }
151
+ </style>
@@ -21,7 +21,9 @@
21
21
  * StatusBadge Component
22
22
  *
23
23
  * Predefined status badge with semantic colors, icons, and dot indicators.
24
+ * Morphs between statuses with a crossfade label + tint transition.
24
25
  */
26
+ import { fade } from 'svelte/transition';
25
27
 
26
28
  interface Props {
27
29
  status: StatusValue;
@@ -108,7 +110,7 @@
108
110
  );
109
111
 
110
112
  const badgeClass = $derived(
111
- ['badge', variantClass, sizeClass, clickable ? 'cursor-pointer hover:opacity-80' : '', className]
113
+ ['badge', variantClass, sizeClass, 'status-badge-morph', clickable ? 'cursor-pointer hover:opacity-80' : '', className]
112
114
  .filter(Boolean)
113
115
  .join(' ')
114
116
  );
@@ -142,7 +144,9 @@
142
144
  {#if showIcon}
143
145
  <span class="mr-1">{config.icon}</span>
144
146
  {/if}
145
- {displayText}
147
+ {#key status}
148
+ <span class="status-label" in:fade={{ duration: 150 }} out:fade={{ duration: 80 }}>{displayText}</span>
149
+ {/key}
146
150
  </span>
147
151
  {:else}
148
152
  <span class={badgeClass} aria-label={ariaLabel || `Status: ${displayText}`}>
@@ -152,6 +156,30 @@
152
156
  {#if showIcon}
153
157
  <span class="mr-1">{config.icon}</span>
154
158
  {/if}
155
- {displayText}
159
+ {#key status}
160
+ <span class="status-label" in:fade={{ duration: 150 }} out:fade={{ duration: 80 }}>{displayText}</span>
161
+ {/key}
156
162
  </span>
157
163
  {/if}
164
+
165
+ <style>
166
+ /* Wrap the badge so position:relative doesn't affect badge layout */
167
+ .status-badge-morph {
168
+ position: relative;
169
+ /* Smooth the background/color tint change when variant class changes */
170
+ transition:
171
+ background-color var(--motion-fast) var(--motion-glide),
172
+ color var(--motion-fast) var(--motion-glide),
173
+ border-color var(--motion-fast) var(--motion-glide);
174
+ }
175
+
176
+ .status-label {
177
+ display: inline;
178
+ }
179
+
180
+ @media (prefers-reduced-motion: reduce) {
181
+ .status-badge-morph {
182
+ transition: none;
183
+ }
184
+ }
185
+ </style>
@@ -1,9 +1,4 @@
1
1
  export type StatusValue = 'active' | 'inactive' | 'pending' | 'completed' | 'cancelled' | 'draft' | 'scheduled' | 'in-progress' | 'overdue' | 'paid' | 'unpaid' | 'partial' | 'assigned' | 'available';
2
- /**
3
- * StatusBadge Component
4
- *
5
- * Predefined status badge with semantic colors, icons, and dot indicators.
6
- */
7
2
  interface Props {
8
3
  status: StatusValue;
9
4
  size?: 'xs' | 'sm' | 'md' | 'lg';