Compare commits
4
Commits
v0.5.0
...
42ddfd0527
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42ddfd0527 | ||
|
|
1db797b4db | ||
|
|
2d746c0d33 | ||
|
|
10a29ded6a |
+12
@@ -4,6 +4,11 @@ dist-showcase/
|
||||
dist-demo/
|
||||
*.log
|
||||
.DS_Store
|
||||
|
||||
# Generated by scripts/build_demo_pdf.py — the rendered demo PDF and the
|
||||
# intermediate page PNGs it rasterizes on the way there (~2 MB).
|
||||
output/
|
||||
tmp/
|
||||
# impeccable-ignore-start
|
||||
.impeccable/config.local.json
|
||||
.impeccable/hook.cache.json
|
||||
@@ -14,3 +19,10 @@ dist-demo/
|
||||
|
||||
# tsup temp (mount cannot unlink)
|
||||
tsup.config.bundled_*.mjs
|
||||
|
||||
# A real .npmrc carries the Gitea registry _authToken — never commit it.
|
||||
# This pattern matches the exact filename only, so .npmrc.example (which is
|
||||
# meant to be committed) is unaffected.
|
||||
# Publishing from a clone: prefer `npm publish --userconfig <path outside the
|
||||
# repo>` over writing a token into the working tree at all.
|
||||
.npmrc
|
||||
|
||||
@@ -8,10 +8,13 @@ export function AnimatedNumber({
|
||||
value,
|
||||
format = defaultFormat,
|
||||
className,
|
||||
immediate = false,
|
||||
}: {
|
||||
value: number;
|
||||
format?: (n: number) => string;
|
||||
className?: string;
|
||||
/** Count up on mount instead of waiting to scroll into view. Dashboards. */
|
||||
immediate?: boolean;
|
||||
}) {
|
||||
const ref = React.useRef<HTMLSpanElement>(null);
|
||||
const inView = useInView(ref, { once: true, margin: "-20% 0px" });
|
||||
@@ -19,11 +22,12 @@ export function AnimatedNumber({
|
||||
const mv = useMotionValue(0);
|
||||
const spring = useSpring(mv, { stiffness: 90, damping: 20 });
|
||||
const [display, setDisplay] = React.useState(format(0));
|
||||
const active = immediate || inView;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (reduce) { setDisplay(format(value)); return; }
|
||||
if (inView) mv.set(value);
|
||||
}, [inView, value, reduce, mv, format]);
|
||||
if (active) mv.set(value);
|
||||
}, [active, value, reduce, mv, format]);
|
||||
|
||||
React.useEffect(() => spring.on("change", (v) => setDisplay(format(v))), [spring, format]);
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ export interface StatCardProps {
|
||||
intent?: StatIntent;
|
||||
icon?: React.ReactNode;
|
||||
className?: string;
|
||||
/** Count up on mount. Pass through to AnimatedNumber. */
|
||||
immediate?: boolean;
|
||||
}
|
||||
|
||||
/** Dashboard metric tile. Pass `animate` for a count-up, or `value` for static. */
|
||||
@@ -36,6 +38,7 @@ export function StatCard({
|
||||
intent = "default",
|
||||
icon,
|
||||
className,
|
||||
immediate,
|
||||
}: StatCardProps) {
|
||||
return (
|
||||
<div className={cn("rounded-lg border border-border bg-surface p-4 shadow-sm", className)}>
|
||||
@@ -44,7 +47,7 @@ export function StatCard({
|
||||
{icon && <span className="text-muted [&_svg]:size-4">{icon}</span>}
|
||||
</div>
|
||||
<p className={cn("mt-1 font-display text-2xl font-bold", INTENT[intent])}>
|
||||
{animate != null ? <AnimatedNumber value={animate} format={format} /> : value}
|
||||
{animate != null ? <AnimatedNumber value={animate} format={format} immediate={immediate} /> : value}
|
||||
</p>
|
||||
{sub && <p className="mt-0.5 text-xs text-muted">{sub}</p>}
|
||||
</div>
|
||||
|
||||
@@ -9,19 +9,24 @@ export function FadeIn({
|
||||
delay = 0,
|
||||
y = 8,
|
||||
className,
|
||||
immediate = false,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
delay?: number;
|
||||
y?: number;
|
||||
className?: string;
|
||||
/** Animate on mount instead of waiting to scroll into view. Dashboards. */
|
||||
immediate?: boolean;
|
||||
}) {
|
||||
const reduce = useReducedMotion();
|
||||
const visible = { opacity: 1, y: 0 };
|
||||
return (
|
||||
<motion.div
|
||||
className={className}
|
||||
initial={reduce ? false : { opacity: 0, y }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-10% 0px" }}
|
||||
{...(immediate
|
||||
? { animate: visible }
|
||||
: { whileInView: visible, viewport: { once: true, margin: "-10% 0px" as const } })}
|
||||
transition={reduce ? { duration: 0 } : { duration: 0.4, ease: easeOut, delay }}
|
||||
>
|
||||
{children}
|
||||
|
||||
Reference in New Issue
Block a user