"use client";
import { useState, type ButtonHTMLAttributes, type CSSProperties } from "react";
import "./tally-button.css";
/**
* Tally Button
* A save or like button with a count that turns like an odometer. Only the
* digits that change move, one after another from the left, so 1,299 → 1,300
* rolls three wheels in a little cascade while the 1 stays put. A ring
* leaves the icon as it fills.
*/
type Props = Omit, "onChange"> & {
/** What it does, e.g. "Save". */
label: string;
/** The count with you not included. */
count: number;
/** Plural noun for the accessible name, e.g. "saves". */
noun: string;
icon?: "bookmark" | "heart";
pressed?: boolean;
defaultPressed?: boolean;
onChange?: (pressed: boolean) => void;
theme?: "paper" | "night";
motion?: "full" | "reduced";
};
const fmt = (n: number) => n.toLocaleString("en-US");
function Odometer({ value, from }: { value: number; from: number }) {
const now = fmt(value).split(""), was = fmt(from).split("");
// Compare places from the right; the changed ones roll in order from the left.
const changed = now.map((c, i) => c !== was[was.length - (now.length - i)]);
let order = 0;
return (
{now.map((c, i) => {
const key = now.length - i;
if (!/\d/.test(c)) return {c};
const delay = changed[i] ? order++ * 70 : 0;
return (
{Array.from({ length: 10 }, (_, k) => {k})}
);
})}
);
}
export function TallyButton({ label, count, noun, icon = "bookmark", pressed, defaultPressed = false, onChange, theme = "night", motion = "full", className = "", onClick, ...rest }: Props) {
const [inner, setInner] = useState(defaultPressed);
const on = pressed ?? inner;
const value = count + (on ? 1 : 0);
// The value before the last change, so we know which digits moved.
const [from, setFrom] = useState(value);
const [pulse, setPulse] = useState(0);
return (
);
}