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.
- package/README.md +52 -19
- package/dist/components/animated-beam/AnimatedBeam.d.ts +47 -11
- package/dist/components/animated-beam/AnimatedBeam.js +293 -118
- package/dist/components/animated-tooltip/AnimatedTooltip.d.ts +10 -1
- package/dist/components/animated-tooltip/AnimatedTooltip.js +170 -101
- package/dist/components/bento-grid/BentoFrame.d.ts +16 -0
- package/dist/components/bento-grid/BentoFrame.js +28 -0
- package/dist/components/bento-grid/BentoGrid.d.ts +5 -1
- package/dist/components/bento-grid/BentoGrid.js +71 -2
- package/dist/components/bento-grid/BentoGridCard.js +34 -26
- package/dist/components/bento-grid/BentoGridItem.js +10 -10
- package/dist/components/border-beam/BorderBeam.d.ts +55 -10
- package/dist/components/border-beam/BorderBeam.js +93 -16
- package/dist/components/compare/Compare.d.ts +32 -8
- package/dist/components/compare/Compare.js +213 -95
- package/dist/components/container-scroll/ContainerScroll.d.ts +26 -4
- package/dist/components/container-scroll/ContainerScroll.js +88 -49
- package/dist/components/dock/Dock.d.ts +10 -7
- package/dist/components/dock/Dock.js +76 -31
- package/dist/components/dock/DockIcon.d.ts +14 -3
- package/dist/components/dock/DockIcon.js +23 -19
- package/dist/components/dock/DockSeparator.d.ts +4 -1
- package/dist/components/dock/DockSeparator.js +5 -2
- package/dist/components/dock/types.d.ts +13 -6
- package/dist/components/dropdown-menu/menu-shared.d.ts +11 -8
- package/dist/components/editorial-engine/engine.js +1 -14
- package/dist/components/fireworks-hdr/webgl2-renderer.js +0 -19
- package/dist/components/fireworks-hdr/webgpu-renderer.js +3 -22
- package/dist/components/flip-card/FlipCard.d.ts +42 -1
- package/dist/components/flip-card/FlipCard.js +178 -25
- package/dist/components/flip-card/index.d.ts +1 -1
- package/dist/components/fluid-cursor/FluidCursor.js +5 -4
- package/dist/components/fluid-cursor/dev.d.ts +1 -0
- package/dist/components/fluid-cursor/dev.js +8 -0
- package/dist/components/fluid-cursor/fluid-shared.d.ts +0 -2
- package/dist/components/fluid-cursor/fluid-shared.js +0 -2
- package/dist/components/fluid-cursor/webgpu-engine.js +10 -24
- package/dist/components/glow-border/GlowBorder.d.ts +41 -1
- package/dist/components/glow-border/GlowBorder.js +96 -16
- package/dist/components/glow-border/index.d.ts +2 -2
- package/dist/components/gradient-button/GradientButton.d.ts +10 -5
- package/dist/components/gradient-button/GradientButton.js +25 -18
- package/dist/components/image-trail-cursor/ImageTrailCursor.js +1 -0
- package/dist/components/image-trail-cursor/trail-variants.d.ts +37 -1
- package/dist/components/image-trail-cursor/trail-variants.js +331 -8
- package/dist/components/interactive-hover-button/InteractiveHoverButton.js +30 -35
- package/dist/components/marquee/Marquee.d.ts +7 -3
- package/dist/components/marquee/Marquee.js +134 -2
- package/dist/components/marquee/ReviewCard.d.ts +2 -1
- package/dist/components/marquee/ReviewCard.js +20 -9
- package/dist/components/meteors/Meteors.d.ts +32 -10
- package/dist/components/meteors/Meteors.js +53 -20
- package/dist/components/mosaic-glow/mosaic-glow-core.d.ts +98 -10
- package/dist/components/neon-border/NeonBorder.d.ts +24 -5
- package/dist/components/neon-border/NeonBorder.js +37 -7
- package/dist/components/pagination/Pagination.js +220 -82
- package/dist/components/rainbow-button/RainbowButton.js +13 -5
- package/dist/components/ripple-button/RippleButton.d.ts +20 -1
- package/dist/components/ripple-button/RippleButton.js +44 -21
- package/dist/components/shimmer-button/ShimmerButton.d.ts +5 -5
- package/dist/components/shimmer-button/ShimmerButton.js +17 -19
- package/dist/components/smooth-cursor/SmoothCursor.d.ts +13 -9
- package/dist/components/smooth-cursor/SmoothCursor.js +114 -163
- package/dist/components/smooth-cursor/smooth-cursor-core.d.ts +85 -0
- package/dist/components/smooth-cursor/smooth-cursor-core.js +327 -0
- package/dist/components/stepper/Step.js +32 -19
- package/dist/components/stepper/Stepper.js +18 -2
- package/dist/components/stepper/types.d.ts +13 -0
- package/dist/components/timeline/Timeline.d.ts +8 -1
- package/dist/components/timeline/Timeline.js +165 -76
- package/dist/index.js +2 -1
- package/dist/internals/markdown.d.ts +5 -0
- package/dist/internals/motion/haptics.d.ts +0 -4
- package/dist/internals/motion/presence-core.d.ts +59 -0
- package/dist/internals/motion/presence-core.js +162 -0
- package/dist/internals/motion/presence.d.ts +2 -4
- package/dist/internals/motion/presence.js +1 -156
- package/dist/internals/motion/raf.d.ts +1 -1
- package/dist/internals/motion/stagger.d.ts +3 -4
- package/dist/internals/motion/tokens.js +4 -0
- package/dist/internals/motion/transitions.d.ts +17 -17
- package/dist/internals/motion/types.d.ts +6 -7
- package/dist/internals/relative-time.d.ts +1 -1
- package/dist/styles.css +3470 -203
- package/package.json +18 -2
- package/dist/components/fireworks-hdr/webgpu-types.d.ts +0 -159
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
3
|
import { forwardRef, useState, useRef, useEffect } from "react";
|
|
4
4
|
import { cn } from "../../utils.js";
|
|
5
5
|
import { buildPageRange } from "./pagination-range.js";
|
|
6
6
|
import { useSoundCue } from "../../sound/use-sound.js";
|
|
7
|
+
import { useComposedRefs } from "../../internals/dom/use-composed-refs.js";
|
|
8
|
+
import { useIsomorphicLayoutEffect } from "../../internals/dom/ssr.js";
|
|
9
|
+
import { useReducedMotion } from "../../internals/motion/media-query.js";
|
|
10
|
+
import { EASINGS, DURATIONS } from "../../internals/motion/tokens.js";
|
|
7
11
|
/* empty css */
|
|
12
|
+
const SLIDE_MS = DURATIONS.base;
|
|
8
13
|
const pageButtonBase = "inline-flex size-8 shrink-0 items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50";
|
|
9
14
|
const navButtonBase = "inline-flex shrink-0 items-center gap-1 rounded-md px-2.5 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50";
|
|
10
15
|
const Pagination = forwardRef(
|
|
@@ -32,12 +37,138 @@ const Pagination = forwardRef(
|
|
|
32
37
|
const isFirst = safePage <= 1;
|
|
33
38
|
const isLast = safePage >= safeCount;
|
|
34
39
|
const [popArmed, setPopArmed] = useState(false);
|
|
40
|
+
const armedRef = useRef(false);
|
|
35
41
|
const lastPageRef = useRef(safePage);
|
|
42
|
+
const reduced = useReducedMotion();
|
|
43
|
+
const flipDuration = reduced ? 0 : SLIDE_MS;
|
|
44
|
+
const navRef = useRef(null);
|
|
45
|
+
const composedRef = useComposedRefs(navRef, forwardedRef);
|
|
46
|
+
const indicatorRef = useRef(null);
|
|
47
|
+
const [indicatorPlaced, setIndicatorPlaced] = useState(false);
|
|
48
|
+
function placeIndicator(animate) {
|
|
49
|
+
const el = indicatorRef.current;
|
|
50
|
+
const nav = navRef.current;
|
|
51
|
+
const current = nav?.querySelector('button[aria-current="page"]');
|
|
52
|
+
if (!el || !current || current.offsetWidth === 0) {
|
|
53
|
+
setIndicatorPlaced(false);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
let x = 0;
|
|
57
|
+
let y = 0;
|
|
58
|
+
for (let n = current; n && n !== nav; ) {
|
|
59
|
+
x += n.offsetLeft;
|
|
60
|
+
y += n.offsetTop;
|
|
61
|
+
n = n.offsetParent;
|
|
62
|
+
}
|
|
63
|
+
const transform = `translate(${x}px, ${y}px)`;
|
|
64
|
+
el.style.width = `${current.offsetWidth}px`;
|
|
65
|
+
el.style.height = `${current.offsetHeight}px`;
|
|
66
|
+
if (el.style.transform === transform) {
|
|
67
|
+
setIndicatorPlaced(true);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (animate && el.style.transform) {
|
|
71
|
+
el.style.transform = transform;
|
|
72
|
+
} else {
|
|
73
|
+
const previous = el.style.transition;
|
|
74
|
+
el.style.transition = "none";
|
|
75
|
+
el.style.transform = transform;
|
|
76
|
+
void el.offsetWidth;
|
|
77
|
+
el.style.transition = previous;
|
|
78
|
+
}
|
|
79
|
+
setIndicatorPlaced(true);
|
|
80
|
+
}
|
|
81
|
+
const itemEls = useRef(/* @__PURE__ */ new Map());
|
|
82
|
+
const itemRects = useRef(/* @__PURE__ */ new Map());
|
|
83
|
+
const itemAnims = useRef(/* @__PURE__ */ new Map());
|
|
84
|
+
const mountedRef = useRef(false);
|
|
85
|
+
function measureItems() {
|
|
86
|
+
const nav = navRef.current;
|
|
87
|
+
const origin = nav?.getBoundingClientRect();
|
|
88
|
+
const rects = /* @__PURE__ */ new Map();
|
|
89
|
+
for (const [key, el] of itemEls.current) {
|
|
90
|
+
const rect = el.getBoundingClientRect();
|
|
91
|
+
rects.set(key, {
|
|
92
|
+
left: rect.left - (origin?.left ?? 0),
|
|
93
|
+
top: rect.top - (origin?.top ?? 0)
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return rects;
|
|
97
|
+
}
|
|
98
|
+
const itemsKey = items.join(",");
|
|
99
|
+
useIsomorphicLayoutEffect(() => {
|
|
100
|
+
const nav = navRef.current;
|
|
101
|
+
if (safePage !== lastPageRef.current) {
|
|
102
|
+
lastPageRef.current = safePage;
|
|
103
|
+
armedRef.current = true;
|
|
104
|
+
nav?.setAttribute("data-armed", "true");
|
|
105
|
+
setPopArmed(true);
|
|
106
|
+
}
|
|
107
|
+
const firstCommit = !mountedRef.current;
|
|
108
|
+
mountedRef.current = true;
|
|
109
|
+
const origin = nav?.getBoundingClientRect();
|
|
110
|
+
const nextRects = /* @__PURE__ */ new Map();
|
|
111
|
+
for (const [key, el] of itemEls.current) {
|
|
112
|
+
const running = itemAnims.current.get(key);
|
|
113
|
+
let driftX = 0;
|
|
114
|
+
let driftY = 0;
|
|
115
|
+
if (running) {
|
|
116
|
+
const live = el.getBoundingClientRect();
|
|
117
|
+
running.cancel();
|
|
118
|
+
const settled = el.getBoundingClientRect();
|
|
119
|
+
driftX = live.left - settled.left;
|
|
120
|
+
driftY = live.top - settled.top;
|
|
121
|
+
}
|
|
122
|
+
itemAnims.current.delete(key);
|
|
123
|
+
const rect = el.getBoundingClientRect();
|
|
124
|
+
const left = rect.left - (origin?.left ?? 0);
|
|
125
|
+
const top = rect.top - (origin?.top ?? 0);
|
|
126
|
+
nextRects.set(key, { left, top });
|
|
127
|
+
if (firstCommit || !flipDuration || typeof el.animate !== "function") continue;
|
|
128
|
+
const prev = itemRects.current.get(key);
|
|
129
|
+
if (prev) {
|
|
130
|
+
const dx = prev.left + driftX - left;
|
|
131
|
+
const dy = prev.top + driftY - top;
|
|
132
|
+
if (!dx && !dy) continue;
|
|
133
|
+
itemAnims.current.set(
|
|
134
|
+
key,
|
|
135
|
+
el.animate([{ transform: `translate(${dx}px, ${dy}px)` }, { transform: "none" }], {
|
|
136
|
+
duration: flipDuration,
|
|
137
|
+
easing: EASINGS.out
|
|
138
|
+
})
|
|
139
|
+
);
|
|
140
|
+
} else {
|
|
141
|
+
itemAnims.current.set(
|
|
142
|
+
key,
|
|
143
|
+
el.animate([{ opacity: 0 }, { opacity: 1 }], {
|
|
144
|
+
duration: DURATIONS.fast,
|
|
145
|
+
delay: 60,
|
|
146
|
+
easing: "linear",
|
|
147
|
+
fill: "backwards"
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
itemRects.current = nextRects;
|
|
153
|
+
placeIndicator(armedRef.current);
|
|
154
|
+
}, [safePage, itemsKey, flipDuration]);
|
|
36
155
|
useEffect(() => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
156
|
+
const nav = navRef.current;
|
|
157
|
+
if (!nav || typeof ResizeObserver === "undefined") return;
|
|
158
|
+
const ro = new ResizeObserver(() => {
|
|
159
|
+
const gliding = [...itemAnims.current.values()].some((a) => a.playState === "running");
|
|
160
|
+
if (!gliding) itemRects.current = measureItems();
|
|
161
|
+
placeIndicator(false);
|
|
162
|
+
});
|
|
163
|
+
ro.observe(nav);
|
|
164
|
+
return () => ro.disconnect();
|
|
165
|
+
}, []);
|
|
166
|
+
function registerItem(key) {
|
|
167
|
+
return (el) => {
|
|
168
|
+
if (el) itemEls.current.set(key, el);
|
|
169
|
+
else itemEls.current.delete(key);
|
|
170
|
+
};
|
|
171
|
+
}
|
|
41
172
|
function goTo(next) {
|
|
42
173
|
if (disabled) return;
|
|
43
174
|
const clamped = Math.max(1, Math.min(Math.floor(next), Math.max(safeCount, 1)));
|
|
@@ -62,90 +193,97 @@ const Pagination = forwardRef(
|
|
|
62
193
|
if (disabled || isLast) return;
|
|
63
194
|
goTo(safeCount);
|
|
64
195
|
}
|
|
65
|
-
return /* @__PURE__ */
|
|
196
|
+
return /* @__PURE__ */ jsxs(
|
|
66
197
|
"nav",
|
|
67
198
|
{
|
|
68
|
-
ref:
|
|
199
|
+
ref: composedRef,
|
|
69
200
|
"aria-label": label,
|
|
70
201
|
"data-armed": popArmed ? "true" : void 0,
|
|
202
|
+
"data-indicator": indicatorPlaced ? "" : void 0,
|
|
71
203
|
className: cn("ft-pagination", className),
|
|
72
|
-
children:
|
|
73
|
-
|
|
74
|
-
"
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
204
|
+
children: [
|
|
205
|
+
/* @__PURE__ */ jsxs("ul", { className: "flex items-center gap-1", children: [
|
|
206
|
+
showEdges && /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
207
|
+
"button",
|
|
208
|
+
{
|
|
209
|
+
type: "button",
|
|
210
|
+
className: navButtonBase,
|
|
211
|
+
disabled: disabled || isFirst,
|
|
212
|
+
"aria-label": "First page",
|
|
213
|
+
title: "First page",
|
|
214
|
+
onClick: handleFirst,
|
|
215
|
+
children: "« First"
|
|
216
|
+
}
|
|
217
|
+
) }),
|
|
218
|
+
/* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
219
|
+
"button",
|
|
220
|
+
{
|
|
221
|
+
type: "button",
|
|
222
|
+
className: navButtonBase,
|
|
223
|
+
disabled: disabled || isFirst,
|
|
224
|
+
"aria-label": "Previous page",
|
|
225
|
+
title: "Previous page",
|
|
226
|
+
onClick: handlePrevious,
|
|
227
|
+
children: previousLabel ?? "‹ Previous"
|
|
228
|
+
}
|
|
229
|
+
) }),
|
|
230
|
+
items.map((item, i) => {
|
|
231
|
+
const key = item === "ellipsis" ? `ellipsis-${i}` : `page-${item}`;
|
|
232
|
+
return /* @__PURE__ */ jsx("li", { ref: registerItem(key), children: item === "ellipsis" ? (
|
|
233
|
+
// Decorative only: it stands for a run of hidden pages, not a
|
|
234
|
+
// control, so it must not take focus or be reachable by Tab.
|
|
235
|
+
/* @__PURE__ */ jsx(
|
|
236
|
+
"span",
|
|
237
|
+
{
|
|
238
|
+
"aria-hidden": "true",
|
|
239
|
+
className: "text-muted-foreground flex size-8 items-center justify-center select-none",
|
|
240
|
+
children: "…"
|
|
241
|
+
}
|
|
242
|
+
)
|
|
243
|
+
) : /* @__PURE__ */ jsx(
|
|
244
|
+
"button",
|
|
245
|
+
{
|
|
246
|
+
type: "button",
|
|
247
|
+
className: cn(
|
|
248
|
+
pageButtonBase,
|
|
249
|
+
item === safePage ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
250
|
+
),
|
|
251
|
+
disabled,
|
|
252
|
+
"aria-current": item === safePage ? "page" : void 0,
|
|
253
|
+
"aria-label": `Go to page ${item}`,
|
|
254
|
+
title: `Go to page ${item}`,
|
|
255
|
+
onClick: () => goTo(item),
|
|
256
|
+
children: item
|
|
257
|
+
}
|
|
258
|
+
) }, key);
|
|
259
|
+
}),
|
|
260
|
+
/* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
261
|
+
"button",
|
|
262
|
+
{
|
|
263
|
+
type: "button",
|
|
264
|
+
className: navButtonBase,
|
|
265
|
+
disabled: disabled || isLast,
|
|
266
|
+
"aria-label": "Next page",
|
|
267
|
+
title: "Next page",
|
|
268
|
+
onClick: handleNext,
|
|
269
|
+
children: nextLabel ?? "Next ›"
|
|
270
|
+
}
|
|
271
|
+
) }),
|
|
272
|
+
showEdges && /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
273
|
+
"button",
|
|
102
274
|
{
|
|
103
|
-
|
|
104
|
-
className:
|
|
105
|
-
|
|
275
|
+
type: "button",
|
|
276
|
+
className: navButtonBase,
|
|
277
|
+
disabled: disabled || isLast,
|
|
278
|
+
"aria-label": "Last page",
|
|
279
|
+
title: "Last page",
|
|
280
|
+
onClick: handleLast,
|
|
281
|
+
children: "Last »"
|
|
106
282
|
}
|
|
107
|
-
)
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
type: "button",
|
|
112
|
-
className: cn(
|
|
113
|
-
pageButtonBase,
|
|
114
|
-
item === safePage ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
115
|
-
),
|
|
116
|
-
disabled,
|
|
117
|
-
"aria-current": item === safePage ? "page" : void 0,
|
|
118
|
-
"aria-label": `Go to page ${item}`,
|
|
119
|
-
title: `Go to page ${item}`,
|
|
120
|
-
onClick: () => goTo(item),
|
|
121
|
-
children: item
|
|
122
|
-
}
|
|
123
|
-
) }, item === "ellipsis" ? `ellipsis-${i}` : `page-${item}`)),
|
|
124
|
-
/* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
125
|
-
"button",
|
|
126
|
-
{
|
|
127
|
-
type: "button",
|
|
128
|
-
className: navButtonBase,
|
|
129
|
-
disabled: disabled || isLast,
|
|
130
|
-
"aria-label": "Next page",
|
|
131
|
-
title: "Next page",
|
|
132
|
-
onClick: handleNext,
|
|
133
|
-
children: nextLabel ?? "Next ›"
|
|
134
|
-
}
|
|
135
|
-
) }),
|
|
136
|
-
showEdges && /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
137
|
-
"button",
|
|
138
|
-
{
|
|
139
|
-
type: "button",
|
|
140
|
-
className: navButtonBase,
|
|
141
|
-
disabled: disabled || isLast,
|
|
142
|
-
"aria-label": "Last page",
|
|
143
|
-
title: "Last page",
|
|
144
|
-
onClick: handleLast,
|
|
145
|
-
children: "Last »"
|
|
146
|
-
}
|
|
147
|
-
) })
|
|
148
|
-
] })
|
|
283
|
+
) })
|
|
284
|
+
] }),
|
|
285
|
+
/* @__PURE__ */ jsx("span", { ref: indicatorRef, className: "ft-pagination-indicator", "aria-hidden": "true" })
|
|
286
|
+
]
|
|
149
287
|
}
|
|
150
288
|
);
|
|
151
289
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
3
|
import { forwardRef } from "react";
|
|
4
4
|
import { cn } from "../../utils.js";
|
|
5
5
|
import { useSoundCue } from "../../sound/use-sound.js";
|
|
@@ -24,7 +24,7 @@ const RainbowButton = forwardRef(
|
|
|
24
24
|
className
|
|
25
25
|
);
|
|
26
26
|
if (href) {
|
|
27
|
-
return /* @__PURE__ */
|
|
27
|
+
return /* @__PURE__ */ jsxs(
|
|
28
28
|
"a",
|
|
29
29
|
{
|
|
30
30
|
ref,
|
|
@@ -35,11 +35,15 @@ const RainbowButton = forwardRef(
|
|
|
35
35
|
role: disabled ? "link" : void 0,
|
|
36
36
|
tabIndex: disabled ? -1 : void 0,
|
|
37
37
|
onClick: handleClick,
|
|
38
|
-
children
|
|
38
|
+
children: [
|
|
39
|
+
/* @__PURE__ */ jsx("span", { className: "rainbow-button__glow", "aria-hidden": "true", children: /* @__PURE__ */ jsx("span", {}) }),
|
|
40
|
+
/* @__PURE__ */ jsx("span", { className: "rainbow-button__beam", "aria-hidden": "true" }),
|
|
41
|
+
children
|
|
42
|
+
]
|
|
39
43
|
}
|
|
40
44
|
);
|
|
41
45
|
}
|
|
42
|
-
return /* @__PURE__ */
|
|
46
|
+
return /* @__PURE__ */ jsxs(
|
|
43
47
|
"button",
|
|
44
48
|
{
|
|
45
49
|
ref,
|
|
@@ -48,7 +52,11 @@ const RainbowButton = forwardRef(
|
|
|
48
52
|
type,
|
|
49
53
|
disabled,
|
|
50
54
|
onClick: handleClick,
|
|
51
|
-
children
|
|
55
|
+
children: [
|
|
56
|
+
/* @__PURE__ */ jsx("span", { className: "rainbow-button__glow", "aria-hidden": "true", children: /* @__PURE__ */ jsx("span", {}) }),
|
|
57
|
+
/* @__PURE__ */ jsx("span", { className: "rainbow-button__beam", "aria-hidden": "true" }),
|
|
58
|
+
children
|
|
59
|
+
]
|
|
52
60
|
}
|
|
53
61
|
);
|
|
54
62
|
}
|
|
@@ -2,7 +2,7 @@ import type { ButtonHTMLAttributes, ReactNode } from "react";
|
|
|
2
2
|
export interface RippleButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "className"> {
|
|
3
3
|
/** Additional CSS classes */
|
|
4
4
|
className?: string;
|
|
5
|
-
/** Color of the ripple
|
|
5
|
+
/** Color of the ripple: its glow, its rings and the border tint */
|
|
6
6
|
rippleColor?: string;
|
|
7
7
|
/** Animation duration in milliseconds */
|
|
8
8
|
duration?: number;
|
|
@@ -14,4 +14,23 @@ export interface RippleButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonE
|
|
|
14
14
|
*/
|
|
15
15
|
sound?: boolean;
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Where a ripple starts and how big it grows: from the pointer, or from the
|
|
19
|
+
* centre for a keyboard press (no pointer position), with a diameter that
|
|
20
|
+
* reaches the farthest corner.
|
|
21
|
+
*/
|
|
22
|
+
export declare function rippleGeometry(rect: {
|
|
23
|
+
left: number;
|
|
24
|
+
top: number;
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
}, event: {
|
|
28
|
+
clientX: number;
|
|
29
|
+
clientY: number;
|
|
30
|
+
detail: number;
|
|
31
|
+
}): {
|
|
32
|
+
x: number;
|
|
33
|
+
y: number;
|
|
34
|
+
size: number;
|
|
35
|
+
};
|
|
17
36
|
export declare const RippleButton: import("react").ForwardRefExoticComponent<RippleButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
@@ -5,63 +5,85 @@ import { useComposedRefs } from "../../internals/dom/use-composed-refs.js";
|
|
|
5
5
|
import { useSoundCue } from "../../sound/use-sound.js";
|
|
6
6
|
import { cn } from "../../utils.js";
|
|
7
7
|
/* empty css */
|
|
8
|
+
function rippleGeometry(rect, event) {
|
|
9
|
+
const keyboard = event.detail === 0;
|
|
10
|
+
const cx = keyboard ? rect.width / 2 : event.clientX - rect.left;
|
|
11
|
+
const cy = keyboard ? rect.height / 2 : event.clientY - rect.top;
|
|
12
|
+
const dx = Math.max(cx, rect.width - cx);
|
|
13
|
+
const dy = Math.max(cy, rect.height - cy);
|
|
14
|
+
const size = 2 * Math.hypot(dx, dy);
|
|
15
|
+
return { x: cx - size / 2, y: cy - size / 2, size };
|
|
16
|
+
}
|
|
8
17
|
const RippleButton = forwardRef(
|
|
9
18
|
({
|
|
10
19
|
className,
|
|
11
|
-
rippleColor = "#
|
|
12
|
-
duration =
|
|
20
|
+
rippleColor = "#60a5fa",
|
|
21
|
+
duration = 900,
|
|
13
22
|
children,
|
|
14
23
|
onClick,
|
|
24
|
+
onPointerMove,
|
|
15
25
|
sound = false,
|
|
16
26
|
...restProps
|
|
17
27
|
}, forwardedRef) => {
|
|
18
28
|
const buttonRef = useRef(null);
|
|
19
29
|
const composedRef = useComposedRefs(forwardedRef, buttonRef);
|
|
20
30
|
const [ripples, setRipples] = useState([]);
|
|
21
|
-
const
|
|
31
|
+
const nextRippleKey = useRef(0);
|
|
22
32
|
const playCue = useSoundCue(sound);
|
|
33
|
+
function handleClick(event) {
|
|
34
|
+
if (!restProps.disabled) playCue("press");
|
|
35
|
+
createRipple(event);
|
|
36
|
+
onClick?.(event);
|
|
37
|
+
}
|
|
38
|
+
function handlePointerMove(event) {
|
|
39
|
+
const el = event.currentTarget;
|
|
40
|
+
const rect = el.getBoundingClientRect();
|
|
41
|
+
el.style.setProperty("--ripple-x", `${event.clientX - rect.left}px`);
|
|
42
|
+
el.style.setProperty("--ripple-y", `${event.clientY - rect.top}px`);
|
|
43
|
+
onPointerMove?.(event);
|
|
44
|
+
}
|
|
23
45
|
function createRipple(event) {
|
|
24
46
|
const button = buttonRef.current;
|
|
25
47
|
if (!button) return;
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const x = event.clientX - rect.left - size / 2;
|
|
29
|
-
const y = event.clientY - rect.top - size / 2;
|
|
30
|
-
const newRipple = { x, y, size, key: nextKey.current++ };
|
|
48
|
+
const { x, y, size } = rippleGeometry(button.getBoundingClientRect(), event);
|
|
49
|
+
const newRipple = { x, y, size, key: nextRippleKey.current++ };
|
|
31
50
|
setRipples((prev) => [...prev, newRipple]);
|
|
32
51
|
setTimeout(() => {
|
|
33
52
|
setRipples((prev) => prev.filter((r) => r.key !== newRipple.key));
|
|
34
53
|
}, duration);
|
|
35
54
|
}
|
|
36
|
-
function handleClick(event) {
|
|
37
|
-
if (!restProps.disabled) playCue("press");
|
|
38
|
-
createRipple(event);
|
|
39
|
-
onClick?.(event);
|
|
40
|
-
}
|
|
41
55
|
return /* @__PURE__ */ jsxs(
|
|
42
56
|
"button",
|
|
43
57
|
{
|
|
44
58
|
ref: composedRef,
|
|
45
59
|
className: cn(
|
|
46
|
-
"ripple-button relative flex cursor-pointer items-center justify-center overflow-hidden",
|
|
47
|
-
"bg-background text-
|
|
60
|
+
"ripple-button relative isolate flex cursor-pointer items-center justify-center overflow-hidden",
|
|
61
|
+
"bg-background text-foreground border-border rounded-lg border px-5 py-2.5 text-center font-medium",
|
|
62
|
+
"shadow-[inset_0_1px_0_rgb(255_255_255/0.06),0_1px_2px_rgb(0_0_0/0.08)]",
|
|
63
|
+
"transition-[transform,border-color,background-color] duration-300 ease-out",
|
|
64
|
+
"hover:bg-muted/40 active:scale-[0.98] disabled:pointer-events-none disabled:opacity-50",
|
|
48
65
|
className
|
|
49
66
|
),
|
|
50
|
-
style: {
|
|
67
|
+
style: {
|
|
68
|
+
"--ripple-duration": `${duration}ms`,
|
|
69
|
+
"--ripple-color": rippleColor
|
|
70
|
+
},
|
|
71
|
+
"data-rippling": ripples.length > 0 ? "" : void 0,
|
|
51
72
|
onClick: handleClick,
|
|
73
|
+
onPointerMove: handlePointerMove,
|
|
52
74
|
...restProps,
|
|
53
75
|
children: [
|
|
54
76
|
/* @__PURE__ */ jsx("div", { className: "relative z-10", children }),
|
|
55
|
-
/* @__PURE__ */ jsx("span", { className: "
|
|
77
|
+
/* @__PURE__ */ jsx("span", { className: "ripple-hover", "aria-hidden": "true" }),
|
|
78
|
+
/* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute inset-0", "aria-hidden": "true", children: ripples.map((ripple) => /* @__PURE__ */ jsx(
|
|
56
79
|
"span",
|
|
57
80
|
{
|
|
58
|
-
className: "ripple-animation absolute rounded-full
|
|
81
|
+
className: "ripple-animation absolute rounded-full",
|
|
59
82
|
style: {
|
|
60
83
|
width: `${ripple.size}px`,
|
|
61
84
|
height: `${ripple.size}px`,
|
|
62
85
|
top: `${ripple.y}px`,
|
|
63
|
-
left: `${ripple.x}px
|
|
64
|
-
backgroundColor: rippleColor
|
|
86
|
+
left: `${ripple.x}px`
|
|
65
87
|
}
|
|
66
88
|
},
|
|
67
89
|
ripple.key
|
|
@@ -73,5 +95,6 @@ const RippleButton = forwardRef(
|
|
|
73
95
|
);
|
|
74
96
|
RippleButton.displayName = "RippleButton";
|
|
75
97
|
export {
|
|
76
|
-
RippleButton
|
|
98
|
+
RippleButton,
|
|
99
|
+
rippleGeometry
|
|
77
100
|
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { ButtonHTMLAttributes, ReactNode } from "react";
|
|
2
2
|
type BaseProps = {
|
|
3
|
-
/**
|
|
3
|
+
/** Colour of the sheen */
|
|
4
4
|
shimmerColor?: string;
|
|
5
|
-
/** Thickness of the
|
|
5
|
+
/** Thickness of the rim the sheen glints on */
|
|
6
6
|
shimmerSize?: string;
|
|
7
7
|
/** Button border radius */
|
|
8
8
|
borderRadius?: string;
|
|
9
|
-
/** Duration of the
|
|
9
|
+
/** Duration of one sheen cycle: the sweep, then a pause */
|
|
10
10
|
shimmerDuration?: string;
|
|
11
11
|
/** Button background color */
|
|
12
12
|
background?: string;
|
|
@@ -15,8 +15,8 @@ type BaseProps = {
|
|
|
15
15
|
/** Button content */
|
|
16
16
|
children?: ReactNode;
|
|
17
17
|
/**
|
|
18
|
-
* Plays the matching interface cue through the sound controller. Off
|
|
19
|
-
*
|
|
18
|
+
* Plays the matching interface cue through the sound controller. Off by
|
|
19
|
+
* default; only audible once the user has enabled sound.
|
|
20
20
|
*/
|
|
21
21
|
sound?: boolean;
|
|
22
22
|
};
|
|
@@ -13,12 +13,12 @@ const ShimmerButton = forwardRef(
|
|
|
13
13
|
shimmerDuration = "3s",
|
|
14
14
|
background = "rgba(0, 0, 0, 1)",
|
|
15
15
|
children,
|
|
16
|
-
sound = false,
|
|
17
16
|
onClick,
|
|
17
|
+
onPointerMove,
|
|
18
|
+
sound = false,
|
|
18
19
|
...restProps
|
|
19
20
|
}, ref) => {
|
|
20
21
|
const styleVars = {
|
|
21
|
-
"--spread": "90deg",
|
|
22
22
|
"--shimmer-color": shimmerColor,
|
|
23
23
|
"--radius": borderRadius,
|
|
24
24
|
"--speed": shimmerDuration,
|
|
@@ -30,34 +30,32 @@ const ShimmerButton = forwardRef(
|
|
|
30
30
|
if (sound && !restProps.disabled) playCue("press");
|
|
31
31
|
onClick?.(event);
|
|
32
32
|
}
|
|
33
|
+
function handlePointerMove(event) {
|
|
34
|
+
const el = event.currentTarget;
|
|
35
|
+
const rect = el.getBoundingClientRect();
|
|
36
|
+
el.style.setProperty("--mx", `${event.clientX - rect.left}px`);
|
|
37
|
+
el.style.setProperty("--my", `${event.clientY - rect.top}px`);
|
|
38
|
+
onPointerMove?.(event);
|
|
39
|
+
}
|
|
33
40
|
return /* @__PURE__ */ jsxs(
|
|
34
41
|
"button",
|
|
35
42
|
{
|
|
36
43
|
ref,
|
|
37
44
|
className: cn(
|
|
38
|
-
"shimmer-button group relative
|
|
39
|
-
"transform-gpu transition-transform duration-300 ease-
|
|
45
|
+
"shimmer-button group relative isolate flex cursor-pointer items-center justify-center overflow-hidden [border-radius:var(--radius)] px-6 py-3 whitespace-nowrap text-white/90",
|
|
46
|
+
"transform-gpu transition-[transform,color] duration-300 ease-out hover:text-white active:scale-[0.98]",
|
|
40
47
|
className
|
|
41
48
|
),
|
|
42
49
|
style: styleVars,
|
|
43
50
|
onClick: handleClick,
|
|
51
|
+
onPointerMove: handlePointerMove,
|
|
44
52
|
...restProps,
|
|
45
53
|
children: [
|
|
46
|
-
/* @__PURE__ */ jsx("
|
|
47
|
-
|
|
48
|
-
/* @__PURE__ */ jsx(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
className: cn(
|
|
52
|
-
"insert-0 absolute size-full",
|
|
53
|
-
"rounded-2xl px-4 py-1.5 text-sm font-medium shadow-[inset_0_-8px_10px_#ffffff1f]",
|
|
54
|
-
"transform-gpu transition-all duration-300 ease-in-out",
|
|
55
|
-
"group-hover:shadow-[inset_0_-6px_10px_#ffffff3f]",
|
|
56
|
-
"group-active:shadow-[inset_0_-10px_10px_#ffffff3f]"
|
|
57
|
-
)
|
|
58
|
-
}
|
|
59
|
-
),
|
|
60
|
-
/* @__PURE__ */ jsx("div", { className: "absolute [inset:var(--cut)] -z-20 [border-radius:var(--radius)] [background:var(--bg)]" })
|
|
54
|
+
/* @__PURE__ */ jsx("span", { className: "shimmer-button__rim", "aria-hidden": "true" }),
|
|
55
|
+
/* @__PURE__ */ jsx("span", { className: "shimmer-button__face", "aria-hidden": "true" }),
|
|
56
|
+
/* @__PURE__ */ jsx("span", { className: "relative z-10", children }),
|
|
57
|
+
/* @__PURE__ */ jsx("span", { className: "shimmer-button__sheen", "aria-hidden": "true" }),
|
|
58
|
+
/* @__PURE__ */ jsx("span", { className: "shimmer-button__spot", "aria-hidden": "true" })
|
|
61
59
|
]
|
|
62
60
|
}
|
|
63
61
|
);
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
damping?: number;
|
|
5
|
-
/** Controls the spring stiffness (default: 400) */
|
|
6
|
-
stiffness?: number;
|
|
7
|
-
/** Controls the virtual mass of the animated object (default: 1) */
|
|
8
|
-
mass?: number;
|
|
9
|
-
}
|
|
2
|
+
import { type SpringConfig } from "./smooth-cursor-core.js";
|
|
3
|
+
export type { SpringConfig };
|
|
10
4
|
export interface SmoothCursorProps {
|
|
11
5
|
/** Custom cursor node to replace the default arrow cursor */
|
|
12
6
|
cursor?: ReactNode;
|
|
@@ -14,5 +8,15 @@ export interface SmoothCursorProps {
|
|
|
14
8
|
springConfig?: SpringConfig;
|
|
15
9
|
/** Additional CSS classes */
|
|
16
10
|
className?: string;
|
|
11
|
+
/** Name shown in a pill that trails the arrow. No pill when omitted. */
|
|
12
|
+
label?: string;
|
|
13
|
+
/** Fill colour of the arrow and the name pill (any CSS colour) */
|
|
14
|
+
color?: string;
|
|
15
|
+
/** Rotate the cursor toward its direction of travel (the arrow stays upright when false) */
|
|
16
|
+
rotate?: boolean;
|
|
17
|
+
/** Milliseconds without movement before the name pill dims; 0 disables */
|
|
18
|
+
idleFade?: number;
|
|
19
|
+
/** Spring for the name pill; missing fields derive from `springConfig` (stiffness × 0.55, damping × 1.1) */
|
|
20
|
+
labelSpring?: SpringConfig;
|
|
17
21
|
}
|
|
18
|
-
export declare function SmoothCursor({ cursor, springConfig, className }: SmoothCursorProps): import("react").JSX.Element;
|
|
22
|
+
export declare function SmoothCursor({ cursor, springConfig, className, label, color, rotate, idleFade, labelSpring, }: SmoothCursorProps): import("react").JSX.Element;
|