@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
@@ -59,7 +59,25 @@
59
59
  stage.droppableIn !== false
60
60
  );
61
61
 
62
- const flipDuration = $derived(drag?.flipDurationMs ?? 200);
62
+ const flipDuration = $derived(drag?.flipDurationMs ?? 300); // var(--motion-move) = 300ms
63
+
64
+ // Track which item was just dropped to play a spring settle pulse
65
+ let settledItemId = $state<string | number | null>(null);
66
+ let settleTimer: ReturnType<typeof setTimeout> | null = null;
67
+
68
+ function handleFinalize(e: CustomEvent) {
69
+ onDndFinalize?.(e);
70
+ // Find the dropped item (first item if array changed, or use event detail)
71
+ const droppedItem = e.detail?.items?.[0];
72
+ if (droppedItem?.id !== undefined) {
73
+ if (settleTimer) clearTimeout(settleTimer);
74
+ settledItemId = droppedItem.id;
75
+ // Spring settle lasts ~400ms (single overshoot)
76
+ settleTimer = setTimeout(() => {
77
+ settledItemId = null;
78
+ }, 400);
79
+ }
80
+ }
63
81
  </script>
64
82
 
65
83
  <div
@@ -119,11 +137,14 @@
119
137
  dropTargetClasses: drag?.dropTargetClass ? [drag.dropTargetClass] : []
120
138
  }}
121
139
  onconsider={(e) => onDndConsider?.(e)}
122
- onfinalize={(e) => onDndFinalize?.(e)}
140
+ onfinalize={handleFinalize}
123
141
  class="flex flex-col gap-2 min-h-8 flex-1"
124
142
  >
125
143
  {#each items as item (item.id)}
126
- <div animate:flip={{ duration: flipDuration }}>
144
+ <div
145
+ animate:flip={{ duration: flipDuration, easing: (t) => t }}
146
+ class={settledItemId === item.id ? 'pipeline-card-settle' : ''}
147
+ >
127
148
  {#if card}
128
149
  {@render card({ item, stage })}
129
150
  {:else}
@@ -161,3 +182,23 @@
161
182
  </div>
162
183
  {/if}
163
184
  </div>
185
+
186
+ <style>
187
+ /* Spring settle pulse — single overshoot scale on drop (gesture-release, sanctioned). */
188
+ .pipeline-card-settle > :global(*) {
189
+ animation: pipeline-settle 400ms var(--motion-spring) forwards;
190
+ transform-origin: center;
191
+ }
192
+
193
+ @keyframes pipeline-settle {
194
+ 0% { transform: scale(1); }
195
+ 40% { transform: scale(1.02); }
196
+ 100% { transform: scale(1); }
197
+ }
198
+
199
+ @media (prefers-reduced-motion: reduce) {
200
+ .pipeline-card-settle > :global(*) {
201
+ animation: none;
202
+ }
203
+ }
204
+ </style>
@@ -0,0 +1,189 @@
1
+ <script lang="ts">
2
+ interface Segment {
3
+ value: number;
4
+ color?: string;
5
+ }
6
+
7
+ interface Props {
8
+ value?: number;
9
+ indeterminate?: boolean;
10
+ segments?: Segment[];
11
+ color?: 'primary' | 'secondary' | 'accent' | 'success' | 'warning' | 'error' | 'info';
12
+ size?: 'xs' | 'sm' | 'md' | 'lg';
13
+ label?: string;
14
+ showLabel?: boolean;
15
+ class?: string;
16
+ }
17
+
18
+ let {
19
+ value = 0,
20
+ indeterminate = false,
21
+ segments,
22
+ color = 'primary',
23
+ size = 'md',
24
+ label,
25
+ showLabel = false,
26
+ class: className = ''
27
+ }: Props = $props();
28
+
29
+ const clampedValue = $derived(Math.min(100, Math.max(0, value)));
30
+
31
+ const heightMap: Record<NonNullable<Props['size']>, string> = {
32
+ xs: '4px',
33
+ sm: '6px',
34
+ md: '8px',
35
+ lg: '12px'
36
+ };
37
+
38
+ const colorMap: Record<NonNullable<Props['color']>, string> = {
39
+ primary: 'oklch(var(--p))',
40
+ secondary: 'oklch(var(--s))',
41
+ accent: 'oklch(var(--a))',
42
+ success: 'oklch(var(--su))',
43
+ warning: 'oklch(var(--wa))',
44
+ error: 'oklch(var(--er))',
45
+ info: 'oklch(var(--in))'
46
+ };
47
+
48
+ const trackHeight = $derived(heightMap[size]);
49
+ const fillColor = $derived(colorMap[color]);
50
+ </script>
51
+
52
+ <div class="progress-wrap {className}" aria-label={label ?? 'Progress'}>
53
+ {#if label && showLabel}
54
+ <div class="progress-label">
55
+ <span>{label}</span>
56
+ {#if !indeterminate}
57
+ <span class="progress-pct">{clampedValue}%</span>
58
+ {/if}
59
+ </div>
60
+ {/if}
61
+
62
+ <div
63
+ class="progress-track"
64
+ style="height:{trackHeight}"
65
+ role="progressbar"
66
+ aria-valuenow={indeterminate ? undefined : clampedValue}
67
+ aria-valuemin={0}
68
+ aria-valuemax={100}
69
+ aria-label={label ?? 'Progress'}
70
+ >
71
+ {#if indeterminate}
72
+ <div class="progress-indeterminate" style="background:{fillColor}"></div>
73
+ {:else if segments && segments.length > 0}
74
+ <div class="progress-segments">
75
+ {#each segments as seg, i}
76
+ <div
77
+ class="progress-segment"
78
+ class:progress-segment--filled={i < value}
79
+ style="--seg-color:{seg.color ? seg.color : fillColor}"
80
+ ></div>
81
+ {/each}
82
+ </div>
83
+ {:else}
84
+ <div
85
+ class="progress-fill"
86
+ style="width:{clampedValue}%;background:{fillColor}"
87
+ ></div>
88
+ {/if}
89
+ </div>
90
+ </div>
91
+
92
+ <style>
93
+ .progress-wrap {
94
+ display: flex;
95
+ flex-direction: column;
96
+ gap: 0.375rem;
97
+ width: 100%;
98
+ }
99
+
100
+ .progress-label {
101
+ display: flex;
102
+ justify-content: space-between;
103
+ align-items: center;
104
+ font-size: 0.8125rem;
105
+ color: oklch(var(--bc) / 0.7);
106
+ }
107
+
108
+ .progress-pct {
109
+ font-variant-numeric: tabular-nums;
110
+ font-size: 0.75rem;
111
+ }
112
+
113
+ .progress-track {
114
+ position: relative;
115
+ width: 100%;
116
+ border-radius: 9999px;
117
+ background: oklch(var(--b3) / 1);
118
+ overflow: hidden;
119
+ }
120
+
121
+ /* Determinate fill — gentle glide spring on width change */
122
+ .progress-fill {
123
+ height: 100%;
124
+ border-radius: 9999px;
125
+ transition: width 0.9s var(--motion-glide);
126
+ }
127
+
128
+ /* Indeterminate sweep */
129
+ .progress-indeterminate {
130
+ position: absolute;
131
+ inset: 0;
132
+ width: 40%;
133
+ border-radius: 9999px;
134
+ animation: progress-slide 1.4s var(--motion-glide) infinite;
135
+ }
136
+
137
+ @keyframes progress-slide {
138
+ 0% {
139
+ transform: translateX(-100%);
140
+ }
141
+ 100% {
142
+ transform: translateX(320%);
143
+ }
144
+ }
145
+
146
+ /* Segment loader */
147
+ .progress-segments {
148
+ display: flex;
149
+ height: 100%;
150
+ gap: 3px;
151
+ padding: 0 2px;
152
+ }
153
+
154
+ .progress-segment {
155
+ flex: 1;
156
+ height: 100%;
157
+ border-radius: 9999px;
158
+ background: oklch(var(--b1) / 1);
159
+ overflow: hidden;
160
+ position: relative;
161
+ }
162
+
163
+ .progress-segment::after {
164
+ content: '';
165
+ display: block;
166
+ height: 100%;
167
+ background: var(--seg-color);
168
+ transform: scaleX(0);
169
+ transform-origin: left;
170
+ transition: transform 0.35s var(--motion-glide);
171
+ }
172
+
173
+ .progress-segment--filled::after {
174
+ transform: scaleX(1);
175
+ }
176
+
177
+ @media (prefers-reduced-motion: reduce) {
178
+ .progress-fill {
179
+ transition: none;
180
+ }
181
+ .progress-indeterminate {
182
+ animation: none;
183
+ transform: translateX(140%);
184
+ }
185
+ .progress-segment::after {
186
+ transition: none;
187
+ }
188
+ }
189
+ </style>
@@ -0,0 +1,17 @@
1
+ interface Segment {
2
+ value: number;
3
+ color?: string;
4
+ }
5
+ interface Props {
6
+ value?: number;
7
+ indeterminate?: boolean;
8
+ segments?: Segment[];
9
+ color?: 'primary' | 'secondary' | 'accent' | 'success' | 'warning' | 'error' | 'info';
10
+ size?: 'xs' | 'sm' | 'md' | 'lg';
11
+ label?: string;
12
+ showLabel?: boolean;
13
+ class?: string;
14
+ }
15
+ declare const ProgressBar: import("svelte").Component<Props, {}, "">;
16
+ type ProgressBar = ReturnType<typeof ProgressBar>;
17
+ export default ProgressBar;
@@ -0,0 +1,138 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ value?: number;
4
+ size?: number;
5
+ strokeWidth?: number;
6
+ color?: 'primary' | 'secondary' | 'accent' | 'success' | 'warning' | 'error' | 'info';
7
+ label?: string | null;
8
+ showValue?: boolean;
9
+ class?: string;
10
+ }
11
+
12
+ let {
13
+ value = 0,
14
+ size = 80,
15
+ strokeWidth = 6,
16
+ color = 'primary',
17
+ label = null,
18
+ showValue = true,
19
+ class: className = ''
20
+ }: Props = $props();
21
+
22
+ const clampedValue = $derived(Math.min(100, Math.max(0, value)));
23
+
24
+ const r = $derived((size - strokeWidth) / 2);
25
+ const circumference = $derived(2 * Math.PI * r);
26
+ const offset = $derived(circumference * (1 - clampedValue / 100));
27
+ const cx = $derived(size / 2);
28
+ const cy = $derived(size / 2);
29
+
30
+ const colorMap: Record<NonNullable<Props['color']>, string> = {
31
+ primary: 'oklch(var(--p))',
32
+ secondary: 'oklch(var(--s))',
33
+ accent: 'oklch(var(--a))',
34
+ success: 'oklch(var(--su))',
35
+ warning: 'oklch(var(--wa))',
36
+ error: 'oklch(var(--er))',
37
+ info: 'oklch(var(--in))'
38
+ };
39
+
40
+ const strokeColor = $derived(colorMap[color]);
41
+ const fontSize = $derived(Math.round(size * 0.2));
42
+ </script>
43
+
44
+ <div
45
+ class="progress-ring {className}"
46
+ style="width:{size}px;height:{size}px"
47
+ role="progressbar"
48
+ aria-valuenow={clampedValue}
49
+ aria-valuemin={0}
50
+ aria-valuemax={100}
51
+ aria-label={label ?? 'Progress'}
52
+ >
53
+ <!-- Rotate -90deg so fill starts from 12 o'clock -->
54
+ <svg viewBox="0 0 {size} {size}" style="transform:rotate(-90deg)">
55
+ <!-- Track -->
56
+ <circle
57
+ {cx}
58
+ {cy}
59
+ r={r}
60
+ fill="none"
61
+ stroke="oklch(var(--b3) / 1)"
62
+ stroke-width={strokeWidth}
63
+ />
64
+ <!-- Progress arc -->
65
+ <circle
66
+ {cx}
67
+ {cy}
68
+ r={r}
69
+ fill="none"
70
+ stroke={strokeColor}
71
+ stroke-width={strokeWidth}
72
+ stroke-linecap="round"
73
+ stroke-dasharray={circumference}
74
+ stroke-dashoffset={offset}
75
+ class="ring-prog"
76
+ />
77
+ </svg>
78
+
79
+ {#if showValue || label}
80
+ <div class="ring-label" style="font-size:{fontSize}px">
81
+ {#if showValue}
82
+ <span class="ring-value">{clampedValue}%</span>
83
+ {/if}
84
+ {#if label}
85
+ <span class="ring-sublabel">{label}</span>
86
+ {/if}
87
+ </div>
88
+ {/if}
89
+ </div>
90
+
91
+ <style>
92
+ .progress-ring {
93
+ position: relative;
94
+ display: inline-flex;
95
+ align-items: center;
96
+ justify-content: center;
97
+ flex-shrink: 0;
98
+ }
99
+
100
+ .progress-ring svg {
101
+ position: absolute;
102
+ inset: 0;
103
+ width: 100%;
104
+ height: 100%;
105
+ }
106
+
107
+ .ring-prog {
108
+ transition: stroke-dashoffset 0.9s var(--motion-glide);
109
+ }
110
+
111
+ .ring-label {
112
+ display: flex;
113
+ flex-direction: column;
114
+ align-items: center;
115
+ justify-content: center;
116
+ line-height: 1.1;
117
+ color: oklch(var(--bc) / 1);
118
+ pointer-events: none;
119
+ z-index: 1;
120
+ }
121
+
122
+ .ring-value {
123
+ font-variant-numeric: tabular-nums;
124
+ font-weight: 600;
125
+ }
126
+
127
+ .ring-sublabel {
128
+ font-size: 0.625em;
129
+ color: oklch(var(--bc) / 0.6);
130
+ margin-top: 0.125em;
131
+ }
132
+
133
+ @media (prefers-reduced-motion: reduce) {
134
+ .ring-prog {
135
+ transition: none;
136
+ }
137
+ }
138
+ </style>
@@ -0,0 +1,12 @@
1
+ interface Props {
2
+ value?: number;
3
+ size?: number;
4
+ strokeWidth?: number;
5
+ color?: 'primary' | 'secondary' | 'accent' | 'success' | 'warning' | 'error' | 'info';
6
+ label?: string | null;
7
+ showValue?: boolean;
8
+ class?: string;
9
+ }
10
+ declare const ProgressRing: import("svelte").Component<Props, {}, "">;
11
+ type ProgressRing = ReturnType<typeof ProgressRing>;
12
+ export default ProgressRing;
@@ -0,0 +1,195 @@
1
+ <script lang="ts">
2
+ interface Step {
3
+ label?: string;
4
+ description?: string;
5
+ }
6
+
7
+ interface Props {
8
+ steps: Step[] | string[];
9
+ current?: number;
10
+ color?: 'primary' | 'secondary' | 'accent' | 'success' | 'warning' | 'error' | 'info';
11
+ size?: 'sm' | 'md' | 'lg';
12
+ class?: string;
13
+ }
14
+
15
+ let {
16
+ steps,
17
+ current = 1,
18
+ color = 'primary',
19
+ size = 'md',
20
+ class: className = ''
21
+ }: Props = $props();
22
+
23
+ const normalized = $derived(
24
+ steps.map((s) => (typeof s === 'string' ? { label: s } : s))
25
+ );
26
+
27
+ const count = $derived(normalized.length);
28
+ const trackFill = $derived(count > 1 ? (current - 1) / (count - 1) : 1);
29
+
30
+ const colorMap: Record<NonNullable<Props['color']>, string> = {
31
+ primary: 'oklch(var(--p))',
32
+ secondary: 'oklch(var(--s))',
33
+ accent: 'oklch(var(--a))',
34
+ success: 'oklch(var(--su))',
35
+ warning: 'oklch(var(--wa))',
36
+ error: 'oklch(var(--er))',
37
+ info: 'oklch(var(--in))'
38
+ };
39
+ const colorFg: Record<NonNullable<Props['color']>, string> = {
40
+ primary: 'oklch(var(--pc))',
41
+ secondary: 'oklch(var(--sc))',
42
+ accent: 'oklch(var(--ac))',
43
+ success: 'oklch(var(--suc))',
44
+ warning: 'oklch(var(--wac))',
45
+ error: 'oklch(var(--erc))',
46
+ info: 'oklch(var(--inc))'
47
+ };
48
+
49
+ const fillColor = $derived(colorMap[color]);
50
+ const fgColor = $derived(colorFg[color]);
51
+
52
+ const nodeSize: Record<NonNullable<Props['size']>, number> = { sm: 24, md: 32, lg: 40 };
53
+ const nodePx = $derived(nodeSize[size]);
54
+ const fontSize: Record<NonNullable<Props['size']>, string> = { sm: '0.6875rem', md: '0.8125rem', lg: '1rem' };
55
+ const labelSize = $derived(fontSize[size]);
56
+ </script>
57
+
58
+ <div
59
+ class="steps-wrap {className}"
60
+ role="list"
61
+ aria-label="Progress steps"
62
+ style="--fill:{fillColor};--fg:{fgColor};--node:{nodePx}px"
63
+ >
64
+ <!-- Connector track -->
65
+ <div class="steps-track" aria-hidden="true">
66
+ <div class="steps-track-fill" style="transform:scaleX({trackFill})"></div>
67
+ </div>
68
+
69
+ <!-- Step nodes -->
70
+ {#each normalized as step, i}
71
+ {@const stepNum = i + 1}
72
+ {@const isComplete = stepNum < current}
73
+ {@const isActive = stepNum === current}
74
+ <div
75
+ class="step"
76
+ class:step--complete={isComplete}
77
+ class:step--active={isActive}
78
+ role="listitem"
79
+ aria-current={isActive ? 'step' : undefined}
80
+ >
81
+ <div class="step-node" style="font-size:{labelSize}">
82
+ {#if isComplete}
83
+ <!-- Check mark drawn in on complete -->
84
+ <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="step-check" aria-hidden="true">
85
+ <polyline points="3,8 6.5,12 13,4" />
86
+ </svg>
87
+ {:else}
88
+ <span>{stepNum}</span>
89
+ {/if}
90
+ </div>
91
+ {#if step.label}
92
+ <span class="step-label" style="font-size:{labelSize}">{step.label}</span>
93
+ {/if}
94
+ </div>
95
+ {/each}
96
+ </div>
97
+
98
+ <style>
99
+ .steps-wrap {
100
+ position: relative;
101
+ display: flex;
102
+ align-items: flex-start;
103
+ justify-content: space-between;
104
+ width: 100%;
105
+ padding-top: calc(var(--node) / 2);
106
+ }
107
+
108
+ /* The connector track runs behind the nodes */
109
+ .steps-track {
110
+ position: absolute;
111
+ top: calc(var(--node) / 2);
112
+ left: calc(var(--node) / 2);
113
+ right: calc(var(--node) / 2);
114
+ height: 3px;
115
+ border-radius: 9999px;
116
+ background: oklch(var(--b3) / 1);
117
+ overflow: hidden;
118
+ transform: translateY(-50%);
119
+ }
120
+
121
+ .steps-track-fill {
122
+ height: 100%;
123
+ background: var(--fill);
124
+ transform-origin: left;
125
+ transition: transform 0.5s var(--motion-spring);
126
+ }
127
+
128
+ /* Nodes */
129
+ .step {
130
+ display: flex;
131
+ flex-direction: column;
132
+ align-items: center;
133
+ gap: 0.375rem;
134
+ z-index: 1;
135
+ position: relative;
136
+ }
137
+
138
+ .step-node {
139
+ width: var(--node);
140
+ height: var(--node);
141
+ border-radius: 9999px;
142
+ background: oklch(var(--b3) / 1);
143
+ color: oklch(var(--bc) / 0.5);
144
+ display: flex;
145
+ align-items: center;
146
+ justify-content: center;
147
+ font-weight: 600;
148
+ border: 2px solid oklch(var(--b3) / 1);
149
+ transition:
150
+ background 0.2s var(--motion-glide),
151
+ color 0.2s var(--motion-glide),
152
+ border-color 0.2s var(--motion-glide),
153
+ transform 0.4s var(--motion-spring);
154
+ }
155
+
156
+ .step--active .step-node {
157
+ background: oklch(var(--b1) / 1);
158
+ border-color: var(--fill);
159
+ color: var(--fill);
160
+ transform: scale(1.18);
161
+ }
162
+
163
+ .step--complete .step-node {
164
+ background: var(--fill);
165
+ border-color: var(--fill);
166
+ color: var(--fg);
167
+ }
168
+
169
+ .step-check {
170
+ width: 55%;
171
+ height: 55%;
172
+ }
173
+
174
+ .step-label {
175
+ color: oklch(var(--bc) / 0.6);
176
+ text-align: center;
177
+ max-width: 5rem;
178
+ line-height: 1.2;
179
+ transition: color 0.2s var(--motion-glide);
180
+ }
181
+
182
+ .step--active .step-label,
183
+ .step--complete .step-label {
184
+ color: oklch(var(--bc) / 1);
185
+ }
186
+
187
+ @media (prefers-reduced-motion: reduce) {
188
+ .steps-track-fill {
189
+ transition: none;
190
+ }
191
+ .step-node {
192
+ transition: none;
193
+ }
194
+ }
195
+ </style>
@@ -0,0 +1,14 @@
1
+ interface Step {
2
+ label?: string;
3
+ description?: string;
4
+ }
5
+ interface Props {
6
+ steps: Step[] | string[];
7
+ current?: number;
8
+ color?: 'primary' | 'secondary' | 'accent' | 'success' | 'warning' | 'error' | 'info';
9
+ size?: 'sm' | 'md' | 'lg';
10
+ class?: string;
11
+ }
12
+ declare const Steps: import("svelte").Component<Props, {}, "">;
13
+ type Steps = ReturnType<typeof Steps>;
14
+ export default Steps;