@@ -1,10 +1,12 @@
|
||||
import * as React from "react";
|
||||
import { useInView, useMotionValue, useSpring, useReducedMotion } from "motion/react";
|
||||
|
||||
const defaultFormat = (value: number) => Math.round(value).toLocaleString();
|
||||
|
||||
/** Count-up number for dashboards/stat cards. Springs to `value` when scrolled into view. */
|
||||
export function AnimatedNumber({
|
||||
value,
|
||||
format = (n) => Math.round(n).toLocaleString(),
|
||||
format = defaultFormat,
|
||||
className,
|
||||
}: {
|
||||
value: number;
|
||||
@@ -25,5 +27,10 @@ export function AnimatedNumber({
|
||||
|
||||
React.useEffect(() => spring.on("change", (v) => setDisplay(format(v))), [spring, format]);
|
||||
|
||||
return <span ref={ref} className={className}>{display}</span>;
|
||||
return (
|
||||
<span ref={ref} className={className}>
|
||||
<span aria-hidden>{display}</span>
|
||||
<span className="sr-only">{format(value)}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,26 +10,37 @@ export function SpotlightCard({
|
||||
className?: string;
|
||||
}) {
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
const [pos, setPos] = React.useState({ x: 50, y: 50, active: false });
|
||||
const frame = React.useRef<number>();
|
||||
|
||||
const onMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const onMove = (e: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (e.pointerType !== "mouse") return;
|
||||
const r = ref.current?.getBoundingClientRect();
|
||||
if (!r) return;
|
||||
setPos({ x: ((e.clientX - r.left) / r.width) * 100, y: ((e.clientY - r.top) / r.height) * 100, active: true });
|
||||
const x = ((e.clientX - r.left) / r.width) * 100;
|
||||
const y = ((e.clientY - r.top) / r.height) * 100;
|
||||
if (frame.current) cancelAnimationFrame(frame.current);
|
||||
frame.current = requestAnimationFrame(() => {
|
||||
ref.current?.style.setProperty("--spotlight-x", `${x}%`);
|
||||
ref.current?.style.setProperty("--spotlight-y", `${y}%`);
|
||||
});
|
||||
};
|
||||
|
||||
React.useEffect(() => () => {
|
||||
if (frame.current) cancelAnimationFrame(frame.current);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
onMouseMove={onMove}
|
||||
onMouseLeave={() => setPos((p) => ({ ...p, active: false }))}
|
||||
onPointerMove={onMove}
|
||||
className={cn("group relative overflow-hidden rounded-lg border border-border bg-surface p-6 shadow-sm transition-shadow duration-slow hover:shadow-md", className)}
|
||||
style={{ "--spotlight-x": "50%", "--spotlight-y": "50%" } as React.CSSProperties}
|
||||
>
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-slow group-hover:opacity-100"
|
||||
style={{
|
||||
background: `radial-gradient(400px circle at ${pos.x}% ${pos.y}%, hsl(var(--brand) / 0.10), transparent 60%)`,
|
||||
background: "radial-gradient(400px circle at var(--spotlight-x) var(--spotlight-y), hsl(var(--brand) / 0.10), transparent 60%)",
|
||||
}}
|
||||
/>
|
||||
<div className="relative">{children}</div>
|
||||
|
||||
Reference in New Issue
Block a user