@joewinke/jatui 0.1.31 → 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 (239) 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/collapse.js +29 -15
  5. package/dist/actions/floating.d.ts +53 -0
  6. package/dist/actions/floating.js +158 -0
  7. package/dist/actions/listNav.js +137 -108
  8. package/dist/actions/railNav.js +89 -72
  9. package/dist/actions/staggerEntrance.d.ts +28 -0
  10. package/dist/actions/staggerEntrance.js +71 -0
  11. package/dist/actions/tooltip.d.ts +28 -0
  12. package/dist/actions/tooltip.js +186 -0
  13. package/dist/assets/sounds/README.md +47 -0
  14. package/dist/assets/sounds/error.d.ts +1 -0
  15. package/dist/assets/sounds/error.js +4 -0
  16. package/dist/assets/sounds/notify.d.ts +1 -0
  17. package/dist/assets/sounds/notify.js +4 -0
  18. package/dist/assets/sounds/pop.d.ts +1 -0
  19. package/dist/assets/sounds/pop.js +4 -0
  20. package/dist/assets/sounds/send.d.ts +1 -0
  21. package/dist/assets/sounds/send.js +4 -0
  22. package/dist/assets/sounds/success.d.ts +1 -0
  23. package/dist/assets/sounds/success.js +4 -0
  24. package/dist/assets/sounds/toggle.d.ts +1 -0
  25. package/dist/assets/sounds/toggle.js +4 -0
  26. package/dist/components/AnimatedNumber.svelte +112 -0
  27. package/dist/components/AnimatedNumber.svelte.d.ts +8 -0
  28. package/dist/components/Badge.svelte +75 -4
  29. package/dist/components/Badge.svelte.d.ts +6 -1
  30. package/dist/components/Button.svelte +196 -21
  31. package/dist/components/Button.svelte.d.ts +10 -1
  32. package/dist/components/Carousel.svelte +265 -0
  33. package/dist/components/Carousel.svelte.d.ts +21 -0
  34. package/dist/components/ChipInput.svelte +63 -3
  35. package/dist/components/Collapse.svelte +70 -14
  36. package/dist/components/Collapse.svelte.d.ts +11 -0
  37. package/dist/components/ColorSelector.svelte +21 -24
  38. package/dist/components/CommandPalette.svelte +475 -0
  39. package/dist/components/CommandPalette.svelte.d.ts +3 -0
  40. package/dist/components/ContextMenu.svelte +9 -73
  41. package/dist/components/CopyButton.svelte +156 -0
  42. package/dist/components/CopyButton.svelte.d.ts +12 -0
  43. package/dist/components/HoldToConfirm.svelte +183 -0
  44. package/dist/components/HoldToConfirm.svelte.d.ts +11 -0
  45. package/dist/components/KeyboardShortcutsOverlay.svelte +54 -8
  46. package/dist/components/KeyboardShortcutsOverlay.svelte.d.ts +3 -0
  47. package/dist/components/PasswordMeter.svelte +87 -0
  48. package/dist/components/PasswordMeter.svelte.d.ts +8 -0
  49. package/dist/components/PinInput.svelte +186 -0
  50. package/dist/components/PinInput.svelte.d.ts +13 -0
  51. package/dist/components/QuantityStepper.svelte +185 -0
  52. package/dist/components/QuantityStepper.svelte.d.ts +12 -0
  53. package/dist/components/Skeleton.svelte +123 -0
  54. package/dist/components/Skeleton.svelte.d.ts +12 -0
  55. package/dist/components/SpeechForm.svelte +37 -0
  56. package/dist/components/StatusBadge.svelte +31 -3
  57. package/dist/components/StatusBadge.svelte.d.ts +0 -5
  58. package/dist/components/Tabs.svelte +270 -0
  59. package/dist/components/Tabs.svelte.d.ts +15 -0
  60. package/dist/components/TextInput.svelte +138 -38
  61. package/dist/components/TextInput.svelte.d.ts +9 -1
  62. package/dist/components/VoicePlayer.svelte +51 -0
  63. package/dist/components/basic/Alert.svelte +36 -0
  64. package/dist/components/basic/Alert.svelte.d.ts +9 -0
  65. package/dist/components/basic/Breadcrumb.svelte +27 -0
  66. package/dist/components/basic/Breadcrumb.svelte.d.ts +11 -0
  67. package/dist/components/basic/ButtonGroup.svelte +15 -0
  68. package/dist/components/basic/ButtonGroup.svelte.d.ts +8 -0
  69. package/dist/components/basic/Checkbox.svelte +85 -0
  70. package/dist/components/basic/Checkbox.svelte.d.ts +15 -0
  71. package/dist/components/basic/Empty.svelte +17 -0
  72. package/dist/components/basic/Empty.svelte.d.ts +8 -0
  73. package/dist/components/basic/Kbd.svelte +16 -0
  74. package/dist/components/basic/Kbd.svelte.d.ts +8 -0
  75. package/dist/components/basic/Pagination.svelte +104 -0
  76. package/dist/components/basic/Pagination.svelte.d.ts +12 -0
  77. package/dist/components/basic/RadioGroup.svelte +71 -0
  78. package/dist/components/basic/RadioGroup.svelte.d.ts +18 -0
  79. package/dist/components/basic/Separator.svelte +19 -0
  80. package/dist/components/basic/Separator.svelte.d.ts +8 -0
  81. package/dist/components/basic/Slider.svelte +72 -0
  82. package/dist/components/basic/Slider.svelte.d.ts +17 -0
  83. package/dist/components/basic/Switch.svelte +88 -0
  84. package/dist/components/basic/Switch.svelte.d.ts +14 -0
  85. package/dist/components/basic/Toggle.svelte +43 -0
  86. package/dist/components/basic/Toggle.svelte.d.ts +14 -0
  87. package/dist/components/graph/GraphCanvas.svelte +103 -0
  88. package/dist/components/graph/GraphCanvas.svelte.d.ts +22 -0
  89. package/dist/components/graph/JatuiNode.svelte +128 -0
  90. package/dist/components/graph/JatuiNode.svelte.d.ts +14 -0
  91. package/dist/components/graph/theme.d.ts +27 -0
  92. package/dist/components/graph/theme.js +136 -0
  93. package/dist/components/markup.js +62 -59
  94. package/dist/components/messaging/MessageItem.svelte +92 -0
  95. package/dist/components/messaging/MessageItem.svelte.d.ts +5 -0
  96. package/dist/components/messaging/TypingIndicator.svelte +63 -0
  97. package/dist/components/messaging/TypingIndicator.svelte.d.ts +7 -0
  98. package/dist/components/pipeline/PipelineColumn.svelte +44 -3
  99. package/dist/components/progress/ProgressBar.svelte +189 -0
  100. package/dist/components/progress/ProgressBar.svelte.d.ts +17 -0
  101. package/dist/components/progress/ProgressRing.svelte +138 -0
  102. package/dist/components/progress/ProgressRing.svelte.d.ts +12 -0
  103. package/dist/components/progress/Steps.svelte +195 -0
  104. package/dist/components/progress/Steps.svelte.d.ts +14 -0
  105. package/dist/components/session-nav/transcriptModel.js +66 -49
  106. package/dist/components/shell/NavigationMenu.svelte +422 -0
  107. package/dist/components/shell/NavigationMenu.svelte.d.ts +43 -0
  108. package/dist/components/shell/Sidebar.svelte +276 -0
  109. package/dist/components/shell/Sidebar.svelte.d.ts +32 -0
  110. package/dist/components/shell/SidebarGroup.svelte +180 -0
  111. package/dist/components/shell/SidebarGroup.svelte.d.ts +19 -0
  112. package/dist/components/shell/SidebarItem.svelte +170 -0
  113. package/dist/components/shell/SidebarItem.svelte.d.ts +24 -0
  114. package/dist/components/shell/shell.d.ts +30 -0
  115. package/dist/components/shell/shell.js +65 -0
  116. package/dist/components/table/DataTable.svelte +364 -0
  117. package/dist/components/table/DataTable.svelte.d.ts +4 -0
  118. package/dist/components/table/flexRender.d.ts +31 -0
  119. package/dist/components/table/flexRender.js +32 -0
  120. package/dist/components/table/types.d.ts +28 -0
  121. package/dist/components/table/types.js +1 -0
  122. package/dist/components/toast/ToastHost.svelte +21 -0
  123. package/dist/components/toast/ToastHost.svelte.d.ts +3 -0
  124. package/dist/components/toast/ToastItem.svelte +287 -0
  125. package/dist/components/toast/ToastItem.svelte.d.ts +9 -0
  126. package/dist/data/emojis.js +4 -4
  127. package/dist/dnd/autoScroll.js +12 -10
  128. package/dist/dnd/createDndBoard.svelte.js +110 -76
  129. package/dist/dnd/createDndBoard.test.js +161 -85
  130. package/dist/dnd/draggable.js +23 -15
  131. package/dist/dnd/dropTarget.js +29 -15
  132. package/dist/dnd/index.d.ts +0 -1
  133. package/dist/dnd/index.js +0 -1
  134. package/dist/dnd/types.d.ts +0 -9
  135. package/dist/dnd/types.js +14 -2
  136. package/dist/index.d.ts +60 -6
  137. package/dist/index.js +73 -6
  138. package/dist/showcase/AnimatedBeam.svelte +183 -0
  139. package/dist/showcase/AnimatedBeam.svelte.d.ts +26 -0
  140. package/dist/showcase/AuroraDrift.svelte +74 -0
  141. package/dist/showcase/AuroraDrift.svelte.d.ts +9 -0
  142. package/dist/showcase/BentoCard.svelte +50 -0
  143. package/dist/showcase/BentoCard.svelte.d.ts +14 -0
  144. package/dist/showcase/BentoGrid.svelte +43 -0
  145. package/dist/showcase/BentoGrid.svelte.d.ts +12 -0
  146. package/dist/showcase/BorderBeam.svelte +79 -0
  147. package/dist/showcase/BorderBeam.svelte.d.ts +12 -0
  148. package/dist/showcase/ClipWipe.svelte +82 -0
  149. package/dist/showcase/ClipWipe.svelte.d.ts +12 -0
  150. package/dist/showcase/Confetti.svelte +71 -0
  151. package/dist/showcase/Confetti.svelte.d.ts +10 -0
  152. package/dist/showcase/CursorSpotlight.svelte +85 -0
  153. package/dist/showcase/CursorSpotlight.svelte.d.ts +9 -0
  154. package/dist/showcase/DeviceMock.svelte +186 -0
  155. package/dist/showcase/DeviceMock.svelte.d.ts +11 -0
  156. package/dist/showcase/Dock.svelte +123 -0
  157. package/dist/showcase/Dock.svelte.d.ts +17 -0
  158. package/dist/showcase/FlipCard.svelte +103 -0
  159. package/dist/showcase/FlipCard.svelte.d.ts +10 -0
  160. package/dist/showcase/FocusReveal.svelte +83 -0
  161. package/dist/showcase/FocusReveal.svelte.d.ts +11 -0
  162. package/dist/showcase/Globe.svelte +202 -0
  163. package/dist/showcase/Globe.svelte.d.ts +16 -0
  164. package/dist/showcase/KenBurns.svelte +105 -0
  165. package/dist/showcase/KenBurns.svelte.d.ts +13 -0
  166. package/dist/showcase/Marquee.svelte +89 -0
  167. package/dist/showcase/Marquee.svelte.d.ts +9 -0
  168. package/dist/showcase/ParallaxTilt.svelte +98 -0
  169. package/dist/showcase/ParallaxTilt.svelte.d.ts +10 -0
  170. package/dist/showcase/ScrambleText.svelte +98 -0
  171. package/dist/showcase/ScrambleText.svelte.d.ts +11 -0
  172. package/dist/showcase/ShineSweep.svelte +81 -0
  173. package/dist/showcase/ShineSweep.svelte.d.ts +9 -0
  174. package/dist/showcase/Terminal.svelte +230 -0
  175. package/dist/showcase/Terminal.svelte.d.ts +18 -0
  176. package/dist/showcase/TextSplitReveal.svelte +146 -0
  177. package/dist/showcase/TextSplitReveal.svelte.d.ts +13 -0
  178. package/dist/showcase/TextWave.svelte +55 -0
  179. package/dist/showcase/TextWave.svelte.d.ts +8 -0
  180. package/dist/showcase/TypewriterText.svelte +130 -0
  181. package/dist/showcase/TypewriterText.svelte.d.ts +9 -0
  182. package/dist/showcase/backdrop/GrainGradient.svelte +126 -0
  183. package/dist/showcase/backdrop/GrainGradient.svelte.d.ts +23 -0
  184. package/dist/showcase/backdrop/MeshGradient.svelte +104 -0
  185. package/dist/showcase/backdrop/MeshGradient.svelte.d.ts +20 -0
  186. package/dist/showcase/backdrop/Metaballs.svelte +116 -0
  187. package/dist/showcase/backdrop/Metaballs.svelte.d.ts +18 -0
  188. package/dist/showcase/backdrop/NeuroNoise.svelte +115 -0
  189. package/dist/showcase/backdrop/NeuroNoise.svelte.d.ts +20 -0
  190. package/dist/showcase/backdrop/ShowcaseBackdrop.svelte +48 -0
  191. package/dist/showcase/backdrop/ShowcaseBackdrop.svelte.d.ts +12 -0
  192. package/dist/showcase/generative/GenerativeBackdrop.svelte +234 -0
  193. package/dist/showcase/generative/GenerativeBackdrop.svelte.d.ts +9 -0
  194. package/dist/showcase/generative/intent.d.ts +7 -0
  195. package/dist/showcase/generative/intent.js +67 -0
  196. package/dist/showcase/generative/manifest.d.ts +9 -0
  197. package/dist/showcase/generative/manifest.js +97 -0
  198. package/dist/showcase/generative/types.d.ts +48 -0
  199. package/dist/showcase/generative/types.js +8 -0
  200. package/dist/showcase/index.d.ts +36 -0
  201. package/dist/showcase/index.js +46 -0
  202. package/dist/showcase/scroll/SmoothScroll.svelte +49 -0
  203. package/dist/showcase/scroll/SmoothScroll.svelte.d.ts +12 -0
  204. package/dist/showcase/scroll/scrollProgress.d.ts +24 -0
  205. package/dist/showcase/scroll/scrollProgress.js +65 -0
  206. package/dist/showcase/scroll/scrollReveal.d.ts +30 -0
  207. package/dist/showcase/scroll/scrollReveal.js +70 -0
  208. package/dist/stores/commandPalette.svelte.d.ts +24 -0
  209. package/dist/stores/commandPalette.svelte.js +82 -0
  210. package/dist/stores/confirmDialog.svelte.js +5 -5
  211. package/dist/stores/inputDialog.svelte.js +5 -5
  212. package/dist/stores/toast.svelte.d.ts +28 -0
  213. package/dist/stores/toast.svelte.js +33 -0
  214. package/dist/styles/motion.css +46 -0
  215. package/dist/styles/tokens.css +31 -0
  216. package/dist/transitions/glide.d.ts +31 -0
  217. package/dist/transitions/glide.js +58 -0
  218. package/dist/types/booking.js +4 -4
  219. package/dist/types/comments.js +2 -2
  220. package/dist/types/messaging.js +3 -3
  221. package/dist/types/pipeline.js +2 -2
  222. package/dist/utils/currency.js +4 -2
  223. package/dist/utils/daisyuiColors.js +27 -25
  224. package/dist/utils/dateFormatters.d.ts +20 -0
  225. package/dist/utils/dateFormatters.js +65 -44
  226. package/dist/utils/googleMapsLoader.js +100 -46
  227. package/dist/utils/mentionParser.js +30 -30
  228. package/dist/utils/phone.js +27 -21
  229. package/dist/utils/phoneFormat.js +9 -9
  230. package/dist/utils/sound.d.ts +6 -0
  231. package/dist/utils/sound.js +113 -0
  232. package/dist/utils/splitText.d.ts +25 -0
  233. package/dist/utils/splitText.js +94 -0
  234. package/dist/utils/taskUtils.js +1 -1
  235. package/dist/utils/themeWipe.d.ts +21 -0
  236. package/dist/utils/themeWipe.js +94 -0
  237. package/dist/utils/viewTransition.d.ts +23 -0
  238. package/dist/utils/viewTransition.js +75 -0
  239. package/package.json +56 -4
@@ -0,0 +1,270 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ export interface TabItem {
5
+ id: string;
6
+ label: string;
7
+ disabled?: boolean;
8
+ }
9
+
10
+ interface Props {
11
+ items: TabItem[];
12
+ active?: string;
13
+ variant?: 'pill' | 'underline';
14
+ children?: Snippet<[string]>;
15
+ }
16
+
17
+ let { items, active = $bindable(items[0]?.id ?? ''), variant = 'pill', children }: Props = $props();
18
+
19
+ let tabRefs: Record<string, HTMLButtonElement> = {};
20
+ let indicatorEl: HTMLElement | null = $state(null);
21
+ let listEl: HTMLElement | null = $state(null);
22
+
23
+ function moveIndicator(targetId: string) {
24
+ const btn = tabRefs[targetId];
25
+ if (!btn || !indicatorEl || !listEl) return;
26
+
27
+ const listRect = listEl.getBoundingClientRect();
28
+ const btnRect = btn.getBoundingClientRect();
29
+
30
+ const x = btnRect.left - listRect.left;
31
+ const w = btnRect.width;
32
+
33
+ if (variant === 'pill') {
34
+ indicatorEl.style.transform = `translateX(${x}px)`;
35
+ indicatorEl.style.width = `${w}px`;
36
+ indicatorEl.style.height = `${btn.offsetHeight}px`;
37
+ } else {
38
+ // underline: position under the button
39
+ indicatorEl.style.transform = `translateX(${x}px) scaleX(${w})`;
40
+ }
41
+ }
42
+
43
+ // Sync indicator after DOM settles on mount and whenever active changes
44
+ $effect(() => {
45
+ if (!listEl) return;
46
+ // Tick to allow DOM to paint button dimensions
47
+ requestAnimationFrame(() => moveIndicator(active));
48
+ });
49
+
50
+ function select(id: string) {
51
+ if (items.find((t) => t.id === id)?.disabled) return;
52
+ active = id;
53
+ moveIndicator(id);
54
+ }
55
+
56
+ function handleKeydown(e: KeyboardEvent, currentId: string) {
57
+ const enabled = items.filter((t) => !t.disabled);
58
+ const idx = enabled.findIndex((t) => t.id === currentId);
59
+ if (idx === -1) return;
60
+
61
+ let next = -1;
62
+ if (e.key === 'ArrowRight') next = (idx + 1) % enabled.length;
63
+ else if (e.key === 'ArrowLeft') next = (idx - 1 + enabled.length) % enabled.length;
64
+ else if (e.key === 'Home') next = 0;
65
+ else if (e.key === 'End') next = enabled.length - 1;
66
+ else return;
67
+
68
+ e.preventDefault();
69
+ const nextTab = enabled[next];
70
+ select(nextTab.id);
71
+ tabRefs[nextTab.id]?.focus();
72
+ }
73
+ </script>
74
+
75
+ <div class="jatui-tabs" class:tabs-pill={variant === 'pill'} class:tabs-underline={variant === 'underline'}>
76
+ <!-- Tab list -->
77
+ <div
78
+ role="tablist"
79
+ aria-label="Tab navigation"
80
+ class="tab-list"
81
+ bind:this={listEl}
82
+ >
83
+ <!-- Gliding indicator -->
84
+ {#if variant === 'pill'}
85
+ <span
86
+ class="tab-indicator tab-indicator-pill"
87
+ aria-hidden="true"
88
+ bind:this={indicatorEl}
89
+ ></span>
90
+ {:else}
91
+ <span
92
+ class="tab-indicator tab-indicator-underline"
93
+ aria-hidden="true"
94
+ bind:this={indicatorEl}
95
+ ></span>
96
+ {/if}
97
+
98
+ {#each items as item (item.id)}
99
+ <button
100
+ type="button"
101
+ role="tab"
102
+ aria-selected={active === item.id}
103
+ aria-controls={`tab-panel-${item.id}`}
104
+ id={`tab-${item.id}`}
105
+ tabindex={active === item.id ? 0 : -1}
106
+ disabled={item.disabled}
107
+ class="tab-btn"
108
+ class:tab-active={active === item.id}
109
+ bind:this={tabRefs[item.id]}
110
+ onclick={() => select(item.id)}
111
+ onkeydown={(e) => handleKeydown(e, item.id)}
112
+ >
113
+ {item.label}
114
+ </button>
115
+ {/each}
116
+ </div>
117
+
118
+ <!-- Tab panels -->
119
+ {#each items as item (item.id)}
120
+ <div
121
+ role="tabpanel"
122
+ id={`tab-panel-${item.id}`}
123
+ aria-labelledby={`tab-${item.id}`}
124
+ hidden={active !== item.id}
125
+ class="tab-panel"
126
+ >
127
+ {@render children?.(item.id)}
128
+ </div>
129
+ {/each}
130
+ </div>
131
+
132
+ <style>
133
+ @layer components {
134
+ .jatui-tabs {
135
+ display: flex;
136
+ flex-direction: column;
137
+ gap: 1rem;
138
+ }
139
+
140
+ /* ── Tab list ─────────────────────────────────────────── */
141
+ .tab-list {
142
+ position: relative;
143
+ display: flex;
144
+ flex-direction: row;
145
+ align-items: center;
146
+ gap: 0;
147
+ }
148
+
149
+ /* ── Pill variant ─────────────────────────────────────── */
150
+ .tabs-pill .tab-list {
151
+ background-color: oklch(var(--b2, 93% 0 0) / 1);
152
+ border-radius: 0.75rem;
153
+ padding: 3px;
154
+ gap: 0;
155
+ }
156
+
157
+ .tab-indicator-pill {
158
+ position: absolute;
159
+ top: 0;
160
+ left: 0;
161
+ background-color: oklch(var(--b1, 100% 0 0) / 1);
162
+ border-radius: 0.6rem;
163
+ box-shadow: 0 1px 3px oklch(0% 0 0 / 10%);
164
+ transition:
165
+ transform var(--motion-move, 300ms) var(--motion-glide, cubic-bezier(0.16, 1, 0.3, 1)),
166
+ width var(--motion-move, 300ms) var(--motion-glide, cubic-bezier(0.16, 1, 0.3, 1)),
167
+ height var(--motion-move, 300ms) var(--motion-glide, cubic-bezier(0.16, 1, 0.3, 1));
168
+ pointer-events: none;
169
+ z-index: 0;
170
+ will-change: transform, width;
171
+ }
172
+
173
+ @media (prefers-reduced-motion: reduce) {
174
+ .tab-indicator-pill {
175
+ transition: none;
176
+ }
177
+ }
178
+
179
+ /* ── Underline variant ────────────────────────────────── */
180
+ .tabs-underline .tab-list {
181
+ border-bottom: 1px solid oklch(var(--b3, 88% 0 0) / 1);
182
+ padding-bottom: 0;
183
+ gap: 0;
184
+ }
185
+
186
+ .tab-indicator-underline {
187
+ position: absolute;
188
+ bottom: -1px;
189
+ left: 0;
190
+ width: 1px;
191
+ height: 2px;
192
+ background-color: oklch(var(--p, 55% 0.2 270) / 1);
193
+ transform-origin: left center;
194
+ border-radius: 1px;
195
+ transition:
196
+ transform var(--motion-move, 300ms) var(--motion-glide, cubic-bezier(0.16, 1, 0.3, 1));
197
+ pointer-events: none;
198
+ z-index: 1;
199
+ will-change: transform;
200
+ }
201
+
202
+ @media (prefers-reduced-motion: reduce) {
203
+ .tab-indicator-underline {
204
+ transition: none;
205
+ }
206
+ }
207
+
208
+ /* ── Tab buttons ──────────────────────────────────────── */
209
+ .tab-btn {
210
+ position: relative;
211
+ z-index: 1;
212
+ padding: 0.375rem 0.875rem;
213
+ font-size: 0.875rem;
214
+ font-weight: 500;
215
+ border-radius: 0.5rem;
216
+ border: none;
217
+ background: transparent;
218
+ cursor: pointer;
219
+ color: oklch(var(--bc, 20% 0 0) / 0.6);
220
+ transition:
221
+ color var(--motion-fast, 150ms) var(--motion-glide, cubic-bezier(0.16, 1, 0.3, 1));
222
+ white-space: nowrap;
223
+ flex-shrink: 0;
224
+ }
225
+
226
+ .tab-btn:hover:not(:disabled) {
227
+ color: oklch(var(--bc, 20% 0 0) / 0.85);
228
+ }
229
+
230
+ .tab-btn.tab-active {
231
+ color: oklch(var(--bc, 20% 0 0) / 1);
232
+ }
233
+
234
+ .tab-btn:disabled {
235
+ opacity: 0.4;
236
+ cursor: not-allowed;
237
+ }
238
+
239
+ .tab-btn:focus-visible {
240
+ outline: 2px solid oklch(var(--p, 55% 0.2 270) / 1);
241
+ outline-offset: 2px;
242
+ }
243
+
244
+ /* Underline: active tab gets primary color */
245
+ .tabs-underline .tab-btn.tab-active {
246
+ color: oklch(var(--p, 55% 0.2 270) / 1);
247
+ }
248
+
249
+ /* Underline: button bottom padding so indicator aligns */
250
+ .tabs-underline .tab-btn {
251
+ border-radius: 0.375rem 0.375rem 0 0;
252
+ padding-bottom: 0.625rem;
253
+ }
254
+
255
+ @media (prefers-reduced-motion: reduce) {
256
+ .tab-btn {
257
+ transition: none;
258
+ }
259
+ }
260
+
261
+ /* ── Panel ────────────────────────────────────────────── */
262
+ .tab-panel[hidden] {
263
+ display: none;
264
+ }
265
+
266
+ .tab-panel {
267
+ outline: none;
268
+ }
269
+ }
270
+ </style>
@@ -0,0 +1,15 @@
1
+ import type { Snippet } from 'svelte';
2
+ export interface TabItem {
3
+ id: string;
4
+ label: string;
5
+ disabled?: boolean;
6
+ }
7
+ interface Props {
8
+ items: TabItem[];
9
+ active?: string;
10
+ variant?: 'pill' | 'underline';
11
+ children?: Snippet<[string]>;
12
+ }
13
+ declare const Tabs: import("svelte").Component<Props, {}, "active">;
14
+ type Tabs = ReturnType<typeof Tabs>;
15
+ export default Tabs;
@@ -3,7 +3,8 @@
3
3
  * TextInput Component
4
4
  *
5
5
  * Labeled text input with validation, error state, help text, and character count.
6
- * Converted from Svelte 4 to Svelte 5 runes.
6
+ * Adds: motion-token error shake (embedded keyframe, no host-app dependency),
7
+ * opt-in floatingLabel prop (label translates/scales on focus-or-filled, pure CSS).
7
8
  */
8
9
 
9
10
  interface Props {
@@ -21,6 +22,13 @@
21
22
  minlength?: number;
22
23
  pattern?: string;
23
24
  showCount?: boolean;
25
+ /**
26
+ * When true, the label floats inside the field at rest and lifts up on
27
+ * focus or when filled (kinetics "Floating Label"). Works alongside the
28
+ * existing `label` and `error` props. Backwards-compatible: existing
29
+ * consumers are unaffected when this prop is absent/false.
30
+ */
31
+ floatingLabel?: boolean;
24
32
  class?: string;
25
33
  id?: string;
26
34
  name?: string;
@@ -42,6 +50,7 @@
42
50
  minlength,
43
51
  pattern,
44
52
  showCount = false,
53
+ floatingLabel = false,
45
54
  class: className = '',
46
55
  id = `text-input-${Math.random().toString(36).substr(2, 9)}`,
47
56
  name = '',
@@ -51,7 +60,13 @@
51
60
  const sizeClass = $derived({ sm: 'input-sm', md: '', lg: 'input-lg' }[size]);
52
61
 
53
62
  const inputClass = $derived(
54
- ['input input-bordered w-full transition-shadow duration-200', sizeClass, error ? 'input-error' : '', disabled ? 'input-disabled' : '', className]
63
+ [
64
+ 'input input-bordered w-full transition-shadow duration-200',
65
+ sizeClass,
66
+ error ? 'input-error' : '',
67
+ disabled ? 'input-disabled' : '',
68
+ className
69
+ ]
55
70
  .filter(Boolean)
56
71
  .join(' ')
57
72
  );
@@ -59,12 +74,12 @@
59
74
  const characterCount = $derived(value?.length || 0);
60
75
  const isOverLimit = $derived(maxlength ? characterCount > maxlength : false);
61
76
 
62
- // Error-state shake (transitions.dev "Error state shake"). When `error`
63
- // transitions from empty to set, replay the `t-input-shake` keyframe from the
64
- // host app's transitions.css. Self-contained reflow-restart so a repeat error
65
- // re-shakes. Colour stays on the `input-error` class; this owns only motion.
66
77
  let inputEl: HTMLInputElement | undefined = $state();
67
78
  let hadError = false;
79
+
80
+ // Error shake: embedded keyframe (jatui-shake-x), no host-app dependency.
81
+ // Motion tokens: cubic-bezier from kinetics reference, duration ~450ms.
82
+ // Self-contained reflow-restart so repeat errors re-shake.
68
83
  $effect(() => {
69
84
  const hasError = !!error;
70
85
  if (hasError && !hadError && inputEl) {
@@ -72,44 +87,57 @@
72
87
  typeof window !== 'undefined' &&
73
88
  window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
74
89
  if (!reduce) {
75
- inputEl.classList.remove('t-input-shake');
76
- void inputEl.offsetWidth; // force reflow to restart the animation
77
- inputEl.classList.add('t-input-shake');
90
+ inputEl.classList.remove('jatui-input-shake');
91
+ void inputEl.offsetWidth;
92
+ inputEl.classList.add('jatui-input-shake');
78
93
  }
79
94
  }
80
95
  hadError = hasError;
81
96
  });
82
97
  </script>
83
98
 
84
- <div class="form-control w-full">
85
- <label for={id} class="label">
86
- <span class="text-[0.8125rem] font-medium text-base-content/85">
87
- {label}
88
- {#if required}
89
- <span class="text-error ml-1">*</span>
90
- {/if}
91
- </span>
92
- </label>
93
-
94
- <input
95
- bind:this={inputEl}
96
- {id}
97
- {name}
98
- {type}
99
- {placeholder}
100
- {required}
101
- {disabled}
102
- {readonly}
103
- {maxlength}
104
- {minlength}
105
- {pattern}
106
- autocomplete={autocomplete as AutoFill}
107
- bind:value
108
- class={inputClass}
109
- aria-describedby={error ? `${id}-error` : helpText ? `${id}-help` : undefined}
110
- aria-invalid={error ? 'true' : 'false'}
111
- onanimationend={() => inputEl?.classList.remove('t-input-shake')}
112
- />
99
+ <div class="form-control w-full" class:jatui-float-wrap={floatingLabel}>
100
+ {#if !floatingLabel}
101
+ <label for={id} class="label">
102
+ <span class="text-[0.8125rem] font-medium text-base-content/85">
103
+ {label}
104
+ {#if required}
105
+ <span class="text-error ml-1">*</span>
106
+ {/if}
107
+ </span>
108
+ </label>
109
+ {/if}
110
+
111
+ <div class:jatui-float-field={floatingLabel}>
112
+ <input
113
+ bind:this={inputEl}
114
+ {id}
115
+ {name}
116
+ {type}
117
+ placeholder={floatingLabel ? ' ' : placeholder}
118
+ {required}
119
+ {disabled}
120
+ {readonly}
121
+ {maxlength}
122
+ {minlength}
123
+ {pattern}
124
+ autocomplete={autocomplete as AutoFill}
125
+ bind:value
126
+ class={inputClass}
127
+ aria-describedby={error ? `${id}-error` : helpText ? `${id}-help` : undefined}
128
+ aria-invalid={error ? 'true' : 'false'}
129
+ onanimationend={() => inputEl?.classList.remove('jatui-input-shake')}
130
+ />
131
+
132
+ {#if floatingLabel}
133
+ <label for={id} class="jatui-float-label">
134
+ {label}
135
+ {#if required}
136
+ <span class="text-error ml-1" aria-hidden="true">*</span>
137
+ {/if}
138
+ </label>
139
+ {/if}
140
+ </div>
113
141
 
114
142
  {#if showCount && maxlength}
115
143
  <div class="label">
@@ -129,3 +157,75 @@
129
157
  </div>
130
158
  {/if}
131
159
  </div>
160
+
161
+ <style>
162
+ /* ── Error shake ──────────────────────────────────────────────
163
+ Decaying horizontal oscillation on error onset, retriggerable.
164
+ Uses the kinetics cubic-bezier for authenticity; transform-only. */
165
+ @keyframes jatui-shake-x {
166
+ 10%, 90% { transform: translateX(-1px); }
167
+ 20%, 80% { transform: translateX(2px); }
168
+ 30%, 50%, 70% { transform: translateX(-4px); }
169
+ 40%, 60% { transform: translateX(4px); }
170
+ 100% { transform: translateX(0); }
171
+ }
172
+
173
+ :global(.jatui-input-shake) {
174
+ animation: jatui-shake-x 450ms cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
175
+ }
176
+
177
+ @media (prefers-reduced-motion: reduce) {
178
+ :global(.jatui-input-shake) {
179
+ animation: none;
180
+ }
181
+ }
182
+
183
+ /* ── Floating Label ───────────────────────────────────────────
184
+ Label sits inside the field at rest, lifts + scales on focus or filled.
185
+ Pure CSS: uses :placeholder-shown to detect empty state (input must have
186
+ placeholder=" " — enforced in the template when floatingLabel=true).
187
+ Transform-only: translateY + scale, no layout shift.
188
+ Easing: --motion-glide (ease-out-expo), duration --motion-fast (150ms). */
189
+ .jatui-float-wrap {
190
+ position: relative;
191
+ }
192
+
193
+ .jatui-float-field {
194
+ position: relative;
195
+ }
196
+
197
+ /* Nudge input padding to make room for the floating label */
198
+ .jatui-float-field input {
199
+ padding-top: 1.125rem;
200
+ padding-bottom: 0.25rem;
201
+ }
202
+
203
+ .jatui-float-label {
204
+ position: absolute;
205
+ left: 1rem;
206
+ top: 50%;
207
+ transform: translateY(-50%);
208
+ font-size: 0.875rem;
209
+ color: oklch(var(--bc) / 0.55);
210
+ pointer-events: none;
211
+ transform-origin: left center;
212
+ transition:
213
+ transform var(--motion-fast, 150ms) var(--motion-glide, cubic-bezier(0.16, 1, 0.3, 1)),
214
+ color var(--motion-fast, 150ms) var(--motion-glide, cubic-bezier(0.16, 1, 0.3, 1)),
215
+ font-size var(--motion-fast, 150ms) var(--motion-glide, cubic-bezier(0.16, 1, 0.3, 1));
216
+ }
217
+
218
+ /* Lift label: on focus or when field has a value (:not(:placeholder-shown)) */
219
+ .jatui-float-field input:focus ~ .jatui-float-label,
220
+ .jatui-float-field input:not(:placeholder-shown) ~ .jatui-float-label {
221
+ transform: translateY(-130%) scale(0.82);
222
+ color: oklch(var(--p, var(--bc)));
223
+ font-size: 0.75rem;
224
+ }
225
+
226
+ @media (prefers-reduced-motion: reduce) {
227
+ .jatui-float-label {
228
+ transition: none;
229
+ }
230
+ }
231
+ </style>
@@ -2,7 +2,8 @@
2
2
  * TextInput Component
3
3
  *
4
4
  * Labeled text input with validation, error state, help text, and character count.
5
- * Converted from Svelte 4 to Svelte 5 runes.
5
+ * Adds: motion-token error shake (embedded keyframe, no host-app dependency),
6
+ * opt-in floatingLabel prop (label translates/scales on focus-or-filled, pure CSS).
6
7
  */
7
8
  interface Props {
8
9
  label: string;
@@ -19,6 +20,13 @@ interface Props {
19
20
  minlength?: number;
20
21
  pattern?: string;
21
22
  showCount?: boolean;
23
+ /**
24
+ * When true, the label floats inside the field at rest and lifts up on
25
+ * focus or when filled (kinetics "Floating Label"). Works alongside the
26
+ * existing `label` and `error` props. Backwards-compatible: existing
27
+ * consumers are unaffected when this prop is absent/false.
28
+ */
29
+ floatingLabel?: boolean;
22
30
  class?: string;
23
31
  id?: string;
24
32
  name?: string;
@@ -279,6 +279,14 @@
279
279
  {/if}
280
280
  </button>
281
281
 
282
+ <!-- Equalizer bars — functional status: visible while playing, static on pause/reduced-motion -->
283
+ <div class="vp-eq" aria-hidden="true">
284
+ <span class="vp-eq-bar" class:vp-eq-playing={isPlaying && !isPaused} style="--vp-eq-delay: -0.45s"></span>
285
+ <span class="vp-eq-bar" class:vp-eq-playing={isPlaying && !isPaused} style="--vp-eq-delay: -0.15s"></span>
286
+ <span class="vp-eq-bar" class:vp-eq-playing={isPlaying && !isPaused} style="--vp-eq-delay: -0.62s"></span>
287
+ <span class="vp-eq-bar" class:vp-eq-playing={isPlaying && !isPaused} style="--vp-eq-delay: -0.30s"></span>
288
+ </div>
289
+
282
290
  <!-- Volume slider -->
283
291
  <input
284
292
  type="range"
@@ -420,3 +428,46 @@
420
428
  </div>
421
429
  </div>
422
430
  {/if}
431
+
432
+ <style>
433
+ /* Equalizer bars — 4 bars, phase-offset scaleY loop (functional status). */
434
+ .vp-eq {
435
+ display: flex;
436
+ align-items: flex-end;
437
+ gap: 2px;
438
+ height: 14px;
439
+ padding: 0 2px;
440
+ }
441
+
442
+ .vp-eq-bar {
443
+ display: block;
444
+ width: 3px;
445
+ height: 100%;
446
+ border-radius: 2px;
447
+ background-color: currentColor;
448
+ opacity: 0.45;
449
+ transform: scaleY(0.25);
450
+ transform-origin: bottom;
451
+ transition: opacity 150ms var(--motion-glide), transform 150ms var(--motion-glide);
452
+ }
453
+
454
+ .vp-eq-bar.vp-eq-playing {
455
+ opacity: 0.75;
456
+ animation: vp-eq-pump 1s ease-in-out infinite;
457
+ animation-delay: var(--vp-eq-delay, 0s);
458
+ }
459
+
460
+ @keyframes vp-eq-pump {
461
+ 0%, 100% { transform: scaleY(0.25); }
462
+ 50% { transform: scaleY(1); }
463
+ }
464
+
465
+ @media (prefers-reduced-motion: reduce) {
466
+ .vp-eq-bar.vp-eq-playing {
467
+ animation: none;
468
+ /* Static affordance: taller bars + accent color when playing */
469
+ transform: scaleY(0.6);
470
+ opacity: 1;
471
+ }
472
+ }
473
+ </style>
@@ -0,0 +1,36 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ type?: 'info' | 'success' | 'warning' | 'error';
4
+ title?: string;
5
+ class?: string;
6
+ children?: import('svelte').Snippet;
7
+ }
8
+
9
+ let { type = 'info', title, class: className = '', children }: Props = $props();
10
+
11
+ const typeClass = $derived(
12
+ { info: 'alert-info', success: 'alert-success', warning: 'alert-warning', error: 'alert-error' }[type]
13
+ );
14
+
15
+ const icons: Record<string, string> = {
16
+ info: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
17
+ success: 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z',
18
+ warning: 'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z',
19
+ error: 'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z'
20
+ };
21
+ </script>
22
+
23
+ <div
24
+ class="alert {typeClass} {className}"
25
+ role={type === 'error' ? 'alert' : 'status'}
26
+ >
27
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
28
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={icons[type]} />
29
+ </svg>
30
+ <div>
31
+ {#if title}
32
+ <h3 class="font-bold">{title}</h3>
33
+ {/if}
34
+ {#if children}{@render children()}{/if}
35
+ </div>
36
+ </div>
@@ -0,0 +1,9 @@
1
+ interface Props {
2
+ type?: 'info' | 'success' | 'warning' | 'error';
3
+ title?: string;
4
+ class?: string;
5
+ children?: import('svelte').Snippet;
6
+ }
7
+ declare const Alert: import("svelte").Component<Props, {}, "">;
8
+ type Alert = ReturnType<typeof Alert>;
9
+ export default Alert;
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ export interface BreadcrumbItem {
3
+ label: string;
4
+ href?: string;
5
+ }
6
+
7
+ interface Props {
8
+ items: BreadcrumbItem[];
9
+ class?: string;
10
+ }
11
+
12
+ let { items, class: className = '' }: Props = $props();
13
+ </script>
14
+
15
+ <div class="breadcrumbs text-sm {className}">
16
+ <ul>
17
+ {#each items as item, i}
18
+ <li>
19
+ {#if item.href && i < items.length - 1}
20
+ <a href={item.href}>{item.label}</a>
21
+ {:else}
22
+ <span>{item.label}</span>
23
+ {/if}
24
+ </li>
25
+ {/each}
26
+ </ul>
27
+ </div>
@@ -0,0 +1,11 @@
1
+ export interface BreadcrumbItem {
2
+ label: string;
3
+ href?: string;
4
+ }
5
+ interface Props {
6
+ items: BreadcrumbItem[];
7
+ class?: string;
8
+ }
9
+ declare const Breadcrumb: import("svelte").Component<Props, {}, "">;
10
+ type Breadcrumb = ReturnType<typeof Breadcrumb>;
11
+ export default Breadcrumb;