Spaces:
Sleeping
Sleeping
File size: 1,724 Bytes
5e0e982 709db16 5e0e982 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | /** Pressable with pointer-down scale feedback. */
import { animate } from "motion";
import type { ComponentChildren, JSX } from "preact";
import { useRef } from "preact/hooks";
import { prefersReducedMotion, springs } from "../motion/springs";
type PressableProps = {
children: ComponentChildren;
className?: string;
disabled?: boolean;
onClick?: JSX.MouseEventHandler<HTMLButtonElement>;
type?: "button" | "submit";
ariaLabel?: string;
ariaPressed?: boolean;
};
export function Pressable({
children,
className = "",
disabled = false,
onClick,
type = "button",
ariaLabel,
ariaPressed,
}: PressableProps) {
const ref = useRef<HTMLButtonElement>(null);
const active = useRef(false);
const setScale = (value: number) => {
const el = ref.current;
if (!el || disabled) return;
if (prefersReducedMotion()) {
el.style.transform = "none";
el.style.opacity = value < 1 ? "0.85" : "1";
return;
}
animate(el, { scale: value }, springs.press);
};
const onPointerDown = (event: PointerEvent) => {
if (disabled || event.button !== 0) return;
active.current = true;
ref.current?.setPointerCapture(event.pointerId);
setScale(0.98);
};
const endPress = () => {
if (!active.current) return;
active.current = false;
setScale(1);
};
return (
<button
ref={ref}
type={type}
disabled={disabled}
className={`pressable ${className}`.trim()}
aria-label={ariaLabel}
aria-pressed={ariaPressed}
onPointerDown={onPointerDown}
onPointerUp={endPress}
onPointerCancel={endPress}
onPointerLeave={endPress}
onClick={onClick}
>
{children}
</button>
);
}
|