fancy-ui-react 0.3.0 → 0.4.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 (86) hide show
  1. package/README.md +52 -19
  2. package/dist/components/animated-beam/AnimatedBeam.d.ts +47 -11
  3. package/dist/components/animated-beam/AnimatedBeam.js +293 -118
  4. package/dist/components/animated-tooltip/AnimatedTooltip.d.ts +10 -1
  5. package/dist/components/animated-tooltip/AnimatedTooltip.js +170 -101
  6. package/dist/components/bento-grid/BentoFrame.d.ts +16 -0
  7. package/dist/components/bento-grid/BentoFrame.js +28 -0
  8. package/dist/components/bento-grid/BentoGrid.d.ts +5 -1
  9. package/dist/components/bento-grid/BentoGrid.js +71 -2
  10. package/dist/components/bento-grid/BentoGridCard.js +34 -26
  11. package/dist/components/bento-grid/BentoGridItem.js +10 -10
  12. package/dist/components/border-beam/BorderBeam.d.ts +55 -10
  13. package/dist/components/border-beam/BorderBeam.js +93 -16
  14. package/dist/components/compare/Compare.d.ts +32 -8
  15. package/dist/components/compare/Compare.js +213 -95
  16. package/dist/components/container-scroll/ContainerScroll.d.ts +26 -4
  17. package/dist/components/container-scroll/ContainerScroll.js +88 -49
  18. package/dist/components/dock/Dock.d.ts +10 -7
  19. package/dist/components/dock/Dock.js +76 -31
  20. package/dist/components/dock/DockIcon.d.ts +14 -3
  21. package/dist/components/dock/DockIcon.js +23 -19
  22. package/dist/components/dock/DockSeparator.d.ts +4 -1
  23. package/dist/components/dock/DockSeparator.js +5 -2
  24. package/dist/components/dock/types.d.ts +13 -6
  25. package/dist/components/dropdown-menu/menu-shared.d.ts +11 -8
  26. package/dist/components/editorial-engine/engine.js +1 -14
  27. package/dist/components/fireworks-hdr/webgl2-renderer.js +0 -19
  28. package/dist/components/fireworks-hdr/webgpu-renderer.js +3 -22
  29. package/dist/components/flip-card/FlipCard.d.ts +42 -1
  30. package/dist/components/flip-card/FlipCard.js +178 -25
  31. package/dist/components/flip-card/index.d.ts +1 -1
  32. package/dist/components/fluid-cursor/FluidCursor.js +5 -4
  33. package/dist/components/fluid-cursor/dev.d.ts +1 -0
  34. package/dist/components/fluid-cursor/dev.js +8 -0
  35. package/dist/components/fluid-cursor/fluid-shared.d.ts +0 -2
  36. package/dist/components/fluid-cursor/fluid-shared.js +0 -2
  37. package/dist/components/fluid-cursor/webgpu-engine.js +10 -24
  38. package/dist/components/glow-border/GlowBorder.d.ts +41 -1
  39. package/dist/components/glow-border/GlowBorder.js +96 -16
  40. package/dist/components/glow-border/index.d.ts +2 -2
  41. package/dist/components/gradient-button/GradientButton.d.ts +10 -5
  42. package/dist/components/gradient-button/GradientButton.js +25 -18
  43. package/dist/components/image-trail-cursor/ImageTrailCursor.js +1 -0
  44. package/dist/components/image-trail-cursor/trail-variants.d.ts +37 -1
  45. package/dist/components/image-trail-cursor/trail-variants.js +331 -8
  46. package/dist/components/interactive-hover-button/InteractiveHoverButton.js +30 -35
  47. package/dist/components/marquee/Marquee.d.ts +7 -3
  48. package/dist/components/marquee/Marquee.js +134 -2
  49. package/dist/components/marquee/ReviewCard.d.ts +2 -1
  50. package/dist/components/marquee/ReviewCard.js +20 -9
  51. package/dist/components/meteors/Meteors.d.ts +32 -10
  52. package/dist/components/meteors/Meteors.js +53 -20
  53. package/dist/components/mosaic-glow/mosaic-glow-core.d.ts +98 -10
  54. package/dist/components/neon-border/NeonBorder.d.ts +24 -5
  55. package/dist/components/neon-border/NeonBorder.js +37 -7
  56. package/dist/components/pagination/Pagination.js +220 -82
  57. package/dist/components/rainbow-button/RainbowButton.js +13 -5
  58. package/dist/components/ripple-button/RippleButton.d.ts +20 -1
  59. package/dist/components/ripple-button/RippleButton.js +44 -21
  60. package/dist/components/shimmer-button/ShimmerButton.d.ts +5 -5
  61. package/dist/components/shimmer-button/ShimmerButton.js +17 -19
  62. package/dist/components/smooth-cursor/SmoothCursor.d.ts +13 -9
  63. package/dist/components/smooth-cursor/SmoothCursor.js +114 -163
  64. package/dist/components/smooth-cursor/smooth-cursor-core.d.ts +85 -0
  65. package/dist/components/smooth-cursor/smooth-cursor-core.js +327 -0
  66. package/dist/components/stepper/Step.js +32 -19
  67. package/dist/components/stepper/Stepper.js +18 -2
  68. package/dist/components/stepper/types.d.ts +13 -0
  69. package/dist/components/timeline/Timeline.d.ts +8 -1
  70. package/dist/components/timeline/Timeline.js +165 -76
  71. package/dist/index.js +2 -1
  72. package/dist/internals/markdown.d.ts +5 -0
  73. package/dist/internals/motion/haptics.d.ts +0 -4
  74. package/dist/internals/motion/presence-core.d.ts +59 -0
  75. package/dist/internals/motion/presence-core.js +162 -0
  76. package/dist/internals/motion/presence.d.ts +2 -4
  77. package/dist/internals/motion/presence.js +1 -156
  78. package/dist/internals/motion/raf.d.ts +1 -1
  79. package/dist/internals/motion/stagger.d.ts +3 -4
  80. package/dist/internals/motion/tokens.js +4 -0
  81. package/dist/internals/motion/transitions.d.ts +17 -17
  82. package/dist/internals/motion/types.d.ts +6 -7
  83. package/dist/internals/relative-time.d.ts +1 -1
  84. package/dist/styles.css +3470 -203
  85. package/package.json +18 -2
  86. package/dist/components/fireworks-hdr/webgpu-types.d.ts +0 -159
@@ -0,0 +1,327 @@
1
+ "use client";
2
+ const DEFAULT_DAMPING = 45;
3
+ const DEFAULT_STIFFNESS = 400;
4
+ const DEFAULT_MASS = 1;
5
+ const LABEL_STIFFNESS_SCALE = 0.55;
6
+ const LABEL_DAMPING_SCALE = 1.1;
7
+ const MAX_DT = 0.064;
8
+ const WAKE_DT = 1 / 60;
9
+ const MIN_ROTATION_DISTANCE = 0.1;
10
+ const ROTATION_EASE = 0.3;
11
+ const LABEL_TETHER = 24;
12
+ const LABEL_TILT_PER_VELOCITY = 35e-4;
13
+ const LABEL_MAX_TILT = 5;
14
+ const SETTLE_DISTANCE = 0.05;
15
+ const SETTLE_VELOCITY = 1;
16
+ const SETTLE_ROTATION = 0.05;
17
+ function resolveConfig(springConfig) {
18
+ return {
19
+ damping: springConfig?.damping ?? DEFAULT_DAMPING,
20
+ stiffness: springConfig?.stiffness ?? DEFAULT_STIFFNESS,
21
+ mass: springConfig?.mass ?? DEFAULT_MASS
22
+ };
23
+ }
24
+ function resolveLabelConfig(labelSpring, springConfig) {
25
+ const base = resolveConfig(springConfig);
26
+ return {
27
+ damping: labelSpring?.damping ?? base.damping * LABEL_DAMPING_SCALE,
28
+ stiffness: labelSpring?.stiffness ?? base.stiffness * LABEL_STIFFNESS_SCALE,
29
+ mass: labelSpring?.mass ?? base.mass
30
+ };
31
+ }
32
+ function createSmoothCursor(el, options) {
33
+ const { cursor } = el;
34
+ let label = el.label ?? null;
35
+ const onVisibleChange = options.onVisibleChange;
36
+ const onIdleChange = options.onIdleChange;
37
+ let springConfig = options.springConfig;
38
+ let labelSpring = options.labelSpring;
39
+ let rotate = options.rotate ?? true;
40
+ let idleFade = Math.max(0, options.idleFade ?? 0);
41
+ let reducedMotion = options.reducedMotion ?? false;
42
+ let posX = 0;
43
+ let posY = 0;
44
+ let velX = 0;
45
+ let velY = 0;
46
+ let targetX = 0;
47
+ let targetY = 0;
48
+ let labelX = 0;
49
+ let labelY = 0;
50
+ let labelVelX = 0;
51
+ let labelVelY = 0;
52
+ let rotation = 0;
53
+ let prevX = 0;
54
+ let prevY = 0;
55
+ let visible = false;
56
+ let idle = false;
57
+ let idleTimer = null;
58
+ let rafId = null;
59
+ let lastTime = 0;
60
+ let sleeping = false;
61
+ let waking = false;
62
+ let destroyed = false;
63
+ function setVisible(next) {
64
+ if (visible === next) return;
65
+ visible = next;
66
+ onVisibleChange?.(visible);
67
+ }
68
+ function setIdle(next) {
69
+ if (idle === next) return;
70
+ idle = next;
71
+ onIdleChange?.(idle);
72
+ }
73
+ function clearIdleTimer() {
74
+ if (idleTimer !== null) {
75
+ clearTimeout(idleTimer);
76
+ idleTimer = null;
77
+ }
78
+ }
79
+ function markActive() {
80
+ clearIdleTimer();
81
+ setIdle(false);
82
+ if (idleFade > 0) {
83
+ idleTimer = setTimeout(() => {
84
+ idleTimer = null;
85
+ setIdle(true);
86
+ }, idleFade);
87
+ }
88
+ }
89
+ function startAnimation() {
90
+ if (rafId === null) {
91
+ if (sleeping) {
92
+ waking = true;
93
+ } else {
94
+ lastTime = 0;
95
+ }
96
+ sleeping = false;
97
+ rafId = requestAnimationFrame(animate);
98
+ }
99
+ }
100
+ function stopAnimation() {
101
+ sleeping = false;
102
+ waking = false;
103
+ if (rafId !== null) {
104
+ cancelAnimationFrame(rafId);
105
+ rafId = null;
106
+ }
107
+ }
108
+ function writeLabel(tilt) {
109
+ if (!label) return;
110
+ label.style.transform = tilt === 0 ? `translate3d(${labelX}px, ${labelY}px, 0)` : `translate3d(${labelX}px, ${labelY}px, 0) rotate(${tilt}deg)`;
111
+ }
112
+ function snapLabel() {
113
+ labelX = posX;
114
+ labelY = posY;
115
+ labelVelX = 0;
116
+ labelVelY = 0;
117
+ writeLabel(0);
118
+ }
119
+ function snapToTarget() {
120
+ posX = targetX;
121
+ posY = targetY;
122
+ velX = 0;
123
+ velY = 0;
124
+ if (!rotate) rotation = 0;
125
+ cursor.style.transform = `translate3d(${posX}px, ${posY}px, 0)`;
126
+ snapLabel();
127
+ }
128
+ function placeAtTarget() {
129
+ posX = targetX;
130
+ posY = targetY;
131
+ prevX = targetX;
132
+ prevY = targetY;
133
+ labelX = targetX;
134
+ labelY = targetY;
135
+ labelVelX = 0;
136
+ labelVelY = 0;
137
+ }
138
+ function onMouseMove(e) {
139
+ targetX = e.clientX;
140
+ targetY = e.clientY;
141
+ if (!visible) {
142
+ placeAtTarget();
143
+ setVisible(true);
144
+ }
145
+ markActive();
146
+ if (reducedMotion) {
147
+ snapToTarget();
148
+ } else {
149
+ startAnimation();
150
+ }
151
+ }
152
+ function onMouseLeave() {
153
+ setVisible(false);
154
+ clearIdleTimer();
155
+ setIdle(false);
156
+ stopAnimation();
157
+ }
158
+ function onMouseEnter(e) {
159
+ targetX = e.clientX;
160
+ targetY = e.clientY;
161
+ placeAtTarget();
162
+ setVisible(true);
163
+ markActive();
164
+ if (!reducedMotion) {
165
+ startAnimation();
166
+ } else {
167
+ snapToTarget();
168
+ }
169
+ }
170
+ function animate(time) {
171
+ rafId = null;
172
+ if (!visible) return;
173
+ if (lastTime === 0) {
174
+ lastTime = time;
175
+ rafId = requestAnimationFrame(animate);
176
+ return;
177
+ }
178
+ let dt = Math.min((time - lastTime) / 1e3, MAX_DT);
179
+ if (waking) {
180
+ dt = Math.min(dt, WAKE_DT);
181
+ waking = false;
182
+ }
183
+ lastTime = time;
184
+ const { stiffness, damping, mass } = resolveConfig(springConfig);
185
+ const forceX = -stiffness * (posX - targetX) - damping * velX;
186
+ const forceY = -stiffness * (posY - targetY) - damping * velY;
187
+ const accX = forceX / mass;
188
+ const accY = forceY / mass;
189
+ velX += accX * dt;
190
+ velY += accY * dt;
191
+ posX += velX * dt;
192
+ posY += velY * dt;
193
+ if (rotate) {
194
+ const dx = posX - prevX;
195
+ const dy = posY - prevY;
196
+ const distance = Math.sqrt(dx * dx + dy * dy);
197
+ if (distance > MIN_ROTATION_DISTANCE) {
198
+ const targetRotation = Math.atan2(dy, dx) * (180 / Math.PI) + 90;
199
+ let diff = targetRotation - rotation;
200
+ while (diff > 180) diff -= 360;
201
+ while (diff < -180) diff += 360;
202
+ rotation += diff * ROTATION_EASE;
203
+ }
204
+ } else if (rotation !== 0) {
205
+ rotation += -rotation * ROTATION_EASE;
206
+ if (Math.abs(rotation) < SETTLE_ROTATION) rotation = 0;
207
+ }
208
+ prevX = posX;
209
+ prevY = posY;
210
+ let tilt = 0;
211
+ let labelSettled = true;
212
+ if (label) {
213
+ const lc = resolveLabelConfig(labelSpring, springConfig);
214
+ const lfx = -lc.stiffness * (labelX - posX) - lc.damping * labelVelX;
215
+ const lfy = -lc.stiffness * (labelY - posY) - lc.damping * labelVelY;
216
+ labelVelX += lfx / lc.mass * dt;
217
+ labelVelY += lfy / lc.mass * dt;
218
+ labelX += labelVelX * dt;
219
+ labelY += labelVelY * dt;
220
+ const gapX = labelX - posX;
221
+ const gapY = labelY - posY;
222
+ const gap = Math.sqrt(gapX * gapX + gapY * gapY);
223
+ if (gap > LABEL_TETHER) {
224
+ const k = LABEL_TETHER / gap;
225
+ labelX = posX + gapX * k;
226
+ labelY = posY + gapY * k;
227
+ }
228
+ tilt = Math.max(
229
+ -LABEL_MAX_TILT,
230
+ Math.min(LABEL_MAX_TILT, labelVelX * LABEL_TILT_PER_VELOCITY)
231
+ );
232
+ if (Math.abs(tilt) < 0.01) tilt = 0;
233
+ labelSettled = Math.abs(labelX - targetX) < SETTLE_DISTANCE && Math.abs(labelY - targetY) < SETTLE_DISTANCE && Math.abs(labelVelX) < SETTLE_VELOCITY && Math.abs(labelVelY) < SETTLE_VELOCITY;
234
+ }
235
+ const settled = labelSettled && Math.abs(posX - targetX) < SETTLE_DISTANCE && Math.abs(posY - targetY) < SETTLE_DISTANCE && Math.abs(velX) < SETTLE_VELOCITY && Math.abs(velY) < SETTLE_VELOCITY && (rotate || rotation === 0);
236
+ if (settled) {
237
+ posX = targetX;
238
+ posY = targetY;
239
+ velX = 0;
240
+ velY = 0;
241
+ prevX = posX;
242
+ prevY = posY;
243
+ if (label) {
244
+ labelX = targetX;
245
+ labelY = targetY;
246
+ labelVelX = 0;
247
+ labelVelY = 0;
248
+ tilt = 0;
249
+ }
250
+ }
251
+ cursor.style.transform = `translate3d(${posX}px, ${posY}px, 0) rotate(${rotation}deg)`;
252
+ writeLabel(tilt);
253
+ if (settled) {
254
+ sleeping = true;
255
+ return;
256
+ }
257
+ rafId = requestAnimationFrame(animate);
258
+ }
259
+ const previousBodyCursor = document.body.style.cursor;
260
+ document.body.style.cursor = "none";
261
+ document.addEventListener("mousemove", onMouseMove);
262
+ document.documentElement.addEventListener("mouseleave", onMouseLeave);
263
+ document.documentElement.addEventListener("mouseenter", onMouseEnter);
264
+ return {
265
+ setOptions(next) {
266
+ if (destroyed) return;
267
+ if ("springConfig" in next) {
268
+ springConfig = next.springConfig;
269
+ }
270
+ if ("labelSpring" in next) {
271
+ labelSpring = next.labelSpring;
272
+ }
273
+ if ("rotate" in next) {
274
+ rotate = next.rotate ?? true;
275
+ if (!rotate && rotation !== 0) {
276
+ if (reducedMotion) {
277
+ rotation = 0;
278
+ cursor.style.transform = `translate3d(${posX}px, ${posY}px, 0)`;
279
+ } else if (visible) {
280
+ startAnimation();
281
+ }
282
+ }
283
+ }
284
+ if ("idleFade" in next) {
285
+ idleFade = Math.max(0, next.idleFade ?? 0);
286
+ clearIdleTimer();
287
+ if (idleFade === 0) {
288
+ setIdle(false);
289
+ } else if (visible && !idle) {
290
+ markActive();
291
+ }
292
+ }
293
+ if ("reducedMotion" in next) {
294
+ reducedMotion = next.reducedMotion ?? false;
295
+ if (reducedMotion) {
296
+ stopAnimation();
297
+ snapToTarget();
298
+ } else if (visible) {
299
+ startAnimation();
300
+ }
301
+ }
302
+ },
303
+ setLabel(next) {
304
+ if (destroyed || next === label) return;
305
+ label = next;
306
+ if (!label) return;
307
+ snapLabel();
308
+ },
309
+ resize() {
310
+ },
311
+ destroy() {
312
+ if (destroyed) return;
313
+ destroyed = true;
314
+ document.body.style.cursor = previousBodyCursor;
315
+ document.removeEventListener("mousemove", onMouseMove);
316
+ document.documentElement.removeEventListener("mouseleave", onMouseLeave);
317
+ document.documentElement.removeEventListener("mouseenter", onMouseEnter);
318
+ clearIdleTimer();
319
+ stopAnimation();
320
+ }
321
+ };
322
+ }
323
+ export {
324
+ createSmoothCursor,
325
+ resolveConfig,
326
+ resolveLabelConfig
327
+ };
@@ -30,32 +30,40 @@ const Step = forwardRef(function Step2({ label, description, children, className
30
30
  const clickable = stepper?.clickable ?? false;
31
31
  const isFirst = index === 0;
32
32
  const isLast = stepper ? index === stepper.count - 1 : true;
33
- const connectorDone = stepper ? index <= stepper.current : false;
33
+ const railFrom = orientation === "vertical" ? index : index - 1;
34
+ const connectorDone = stepper && index !== -1 ? railFrom < stepper.current : false;
35
+ const animate = stepper?.animate ?? false;
36
+ const origin = stepper?.origin ?? 0;
37
+ const forward = stepper ? stepper.current >= origin : true;
38
+ const railOrder = Math.max(0, forward ? railFrom - origin : origin - 1 - railFrom);
39
+ const arrival = stepper && forward && index > origin && index <= stepper.current ? index - origin - 1 + 0.6 : void 0;
40
+ const railStyle = { "--ft-step-order": railOrder };
41
+ const bulletStyle = arrival === void 0 ? void 0 : { "--ft-step-arrival": arrival };
34
42
  function handleClick() {
35
43
  if (!stepper || !clickable || index === -1) return;
36
44
  stepper.select(index);
37
45
  }
38
46
  const bulletClasses = cn(
39
- "ft-step-bullet relative inline-flex size-7 shrink-0 items-center justify-center rounded-full text-[13px] font-semibold",
47
+ "ft-step-bullet relative inline-flex size-7 shrink-0 items-center justify-center rounded-full text-[12px] font-semibold tabular-nums",
40
48
  status === "done" && "ft-step-bullet-done",
41
- status === "current" && "ft-step-bullet-current",
42
- status === "upcoming" && "border-border text-muted-foreground border-[1.5px] bg-transparent"
49
+ status === "current" && "ft-step-bullet-current ft-step-bullet-halo",
50
+ status === "upcoming" && "ft-step-bullet-upcoming text-muted-foreground"
43
51
  );
44
52
  const defaultBullet = status === "done" ? /* @__PURE__ */ jsx(
45
53
  "svg",
46
54
  {
47
- className: "size-3.5",
55
+ className: "ft-step-glyph ft-step-check size-3.5",
48
56
  viewBox: "0 0 24 24",
49
57
  fill: "none",
50
58
  stroke: "currentColor",
51
- strokeWidth: "2.5",
59
+ strokeWidth: "2.75",
52
60
  strokeLinecap: "round",
53
61
  strokeLinejoin: "round",
54
62
  "aria-hidden": "true",
55
- children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" })
63
+ children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5", pathLength: 1 })
56
64
  }
57
- ) : /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: index + 1 });
58
- const bulletContent = /* @__PURE__ */ jsxs("span", { className: bulletClasses, "data-status": status, children: [
65
+ ) : /* @__PURE__ */ jsx("span", { className: "ft-step-glyph", "aria-hidden": "true", children: index + 1 });
66
+ const bulletContent = /* @__PURE__ */ jsxs("span", { className: bulletClasses, "data-status": status, style: bulletStyle, children: [
59
67
  children ?? defaultBullet,
60
68
  /* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
61
69
  " ",
@@ -71,13 +79,15 @@ const Step = forwardRef(function Step2({ label, description, children, className
71
79
  "span",
72
80
  {
73
81
  className: cn(
74
- "text-xs",
75
- status === "current" ? "text-foreground font-medium" : "text-muted-foreground"
82
+ "ft-step-label text-xs transition-colors",
83
+ status === "current" && "text-foreground font-medium",
84
+ status === "done" && "text-foreground/80",
85
+ status === "upcoming" && "text-muted-foreground"
76
86
  ),
77
87
  children: label
78
88
  }
79
89
  ) : null,
80
- description ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: description }) : null
90
+ description ? /* @__PURE__ */ jsx("span", { className: "ft-step-description text-muted-foreground/80 text-[11px] leading-snug", children: description }) : null
81
91
  ]
82
92
  }
83
93
  );
@@ -87,20 +97,22 @@ const Step = forwardRef(function Step2({ label, description, children, className
87
97
  ref,
88
98
  className: cn(
89
99
  "ft-step flex",
90
- orientation === "vertical" ? cn("flex-row items-stretch gap-3", isLast ? "pb-0" : "pb-6") : cn("flex-col items-center", isFirst ? "flex-none" : "flex-1"),
100
+ orientation === "vertical" ? cn("flex-row items-stretch gap-3", isLast ? "pb-0" : "pb-7") : cn("flex-col items-center", isFirst ? "flex-none" : "flex-1"),
101
+ animate && "ft-step-animate",
91
102
  className
92
103
  ),
93
104
  "data-status": status,
94
105
  "data-orientation": orientation,
95
106
  "aria-current": status === "current" ? "step" : void 0,
96
- children: orientation === "horizontal" ? /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center", children: [
107
+ children: orientation === "horizontal" ? /* @__PURE__ */ jsxs("div", { className: "flex w-full items-start", children: [
97
108
  !isFirst && /* @__PURE__ */ jsx(
98
109
  "span",
99
110
  {
100
111
  className: cn(
101
- "ft-step-connector mt-[13px] h-0.5 flex-1",
102
- connectorDone ? "ft-step-connector-done" : "bg-border"
112
+ "ft-step-connector bg-border relative mx-1.5 mt-[13px] h-0.5 flex-1 rounded-full",
113
+ connectorDone && "ft-step-connector-done ft-step-connector-lit"
103
114
  ),
115
+ style: railStyle,
104
116
  "aria-hidden": "true"
105
117
  }
106
118
  ),
@@ -120,15 +132,16 @@ const Step = forwardRef(function Step2({ label, description, children, className
120
132
  textContent
121
133
  ] })
122
134
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
123
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center self-stretch", children: [
135
+ /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center self-stretch", !isLast && "-mb-7"), children: [
124
136
  bulletContent,
125
137
  !isLast && /* @__PURE__ */ jsx(
126
138
  "span",
127
139
  {
128
140
  className: cn(
129
- "ft-step-connector my-1 w-0.5 flex-1",
130
- connectorDone ? "ft-step-connector-done" : "bg-border"
141
+ "ft-step-connector bg-border relative my-1 w-0.5 flex-1 rounded-full",
142
+ connectorDone && "ft-step-connector-done ft-step-connector-lit"
131
143
  ),
144
+ style: railStyle,
132
145
  "aria-hidden": "true"
133
146
  }
134
147
  )
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
- import { forwardRef, useState, useRef, useCallback, useMemo } from "react";
3
+ import { forwardRef, useState, useRef, useCallback, useEffect, useMemo } from "react";
4
4
  import { cn } from "../../utils.js";
5
5
  import { useSoundCue } from "../../sound/use-sound.js";
6
+ import { useReducedMotion } from "../../internals/motion/media-query.js";
6
7
  import { STEPPER_KEY } from "./types.js";
7
8
  const Stepper = forwardRef(function Stepper2({
8
9
  current,
@@ -35,6 +36,18 @@ const Stepper = forwardRef(function Stepper2({
35
36
  };
36
37
  }, []);
37
38
  const indexOf = useCallback((id) => registered.indexOf(id), [registered]);
39
+ const reduced = useReducedMotion();
40
+ const [asked, setAsked] = useState(false);
41
+ useEffect(() => {
42
+ setAsked(true);
43
+ }, []);
44
+ const animate = asked && !reduced;
45
+ const [run, setRun] = useState(() => ({ settled: activeIndex, origin: 0 }));
46
+ let origin = run.origin;
47
+ if (run.settled !== activeIndex) {
48
+ origin = run.settled;
49
+ setRun({ settled: activeIndex, origin: run.settled });
50
+ }
38
51
  const playCue = useSoundCue(sound);
39
52
  const select = useCallback(
40
53
  (index) => {
@@ -52,11 +65,13 @@ const Stepper = forwardRef(function Stepper2({
52
65
  clickable,
53
66
  current: activeIndex,
54
67
  count: registered.length,
68
+ origin,
69
+ animate,
55
70
  register,
56
71
  indexOf,
57
72
  select
58
73
  }),
59
- [orientation, clickable, activeIndex, registered, register, indexOf, select]
74
+ [orientation, clickable, activeIndex, registered, origin, animate, register, indexOf, select]
60
75
  );
61
76
  return /* @__PURE__ */ jsx(STEPPER_KEY.Provider, { value: context, children: /* @__PURE__ */ jsx(
62
77
  "ol",
@@ -69,6 +84,7 @@ const Stepper = forwardRef(function Stepper2({
69
84
  className
70
85
  ),
71
86
  "data-orientation": orientation,
87
+ "data-motion": animate ? "full" : "reduced",
72
88
  children
73
89
  }
74
90
  ) });
@@ -34,6 +34,19 @@ export interface StepperContext {
34
34
  readonly current: number;
35
35
  /** Total number of currently registered steps. */
36
36
  readonly count: number;
37
+ /**
38
+ * The active index before the latest change (0 on first paint). Steps
39
+ * order their rail sweep and bullet arrival by their distance from it.
40
+ * Optional so a hand-rolled context without it still type-checks; a
41
+ * missing value reads as "no run to order".
42
+ */
43
+ readonly origin?: number;
44
+ /**
45
+ * Whether the motion layer (rail sweep, breathing halo, check draw) is
46
+ * on: true on the client once `prefers-reduced-motion` has been read and
47
+ * is not `reduce`. Optional for the same reason as `origin`.
48
+ */
49
+ readonly animate?: boolean;
37
50
  /**
38
51
  * Registers a step under a stable per-instance id (typically
39
52
  * `useFancyId()`). Returns an unregister function; call it on unmount.
@@ -14,5 +14,12 @@ export interface TimelineProps {
14
14
  className?: string;
15
15
  /** Content render prop, called for each item */
16
16
  content?: (item: TimelineItem) => ReactNode;
17
+ /**
18
+ * Colour of the travelling head, its trail and the lit dots. Any CSS
19
+ * colour. Unset, it follows the theme's `--primary` token and falls back
20
+ * to a soft violet when no theme is present. Also settable from CSS via
21
+ * `--timeline-accent`.
22
+ */
23
+ accent?: string;
17
24
  }
18
- export declare function Timeline({ items, title, description, className, content }: TimelineProps): import("react").JSX.Element;
25
+ export declare function Timeline({ items, title, description, className, content, accent, }: TimelineProps): import("react").JSX.Element;