feat(feed): spring/fill/burst animation on paw reactions
Adds a CSS-only spring-scale + expanding-burst-overlay animation triggered when a pet paw-reacts to a post, respecting prefers-reduced-motion. Only plays on the like transition, not unlike. Gitea #15
This commit is contained in:
1 parent
4d34b4cd3d
commit
e39b3f1f6e
2 files changed
+67
-9
No files matched your search
@@ -17,17 +17,24 @@ export function PawButton({ postId, petId, initialCount, initialReacted }: PawBu
|
||||
const queryClient = useQueryClient();
|
||||
const [reacted, setReacted] = useState(initialReacted);
|
||||
const [count, setCount] = useState(initialCount);
|
||||
const [animationKey, setAnimationKey] = useState(0);
|
||||
const [showBurst, setShowBurst] = useState(false);
|
||||
|
||||
const toggleMutation = useMutation(
|
||||
trpc.reactions.toggle.mutationOptions({
|
||||
onMutate: () => {
|
||||
const prevReacted = reacted;
|
||||
const prevCount = count;
|
||||
const nextReacted = !reacted;
|
||||
// Optimistically flip the icon only — `reacted` can be stale (callers
|
||||
// currently always pass initialReacted={false}), so count is corrected
|
||||
// precisely from the server's actual `data.reacted` in onSuccess
|
||||
// instead of guessing a +/-1 delta off a possibly-wrong prior state.
|
||||
setReacted(!reacted);
|
||||
setReacted(nextReacted);
|
||||
if (nextReacted) {
|
||||
setAnimationKey((key) => key + 1);
|
||||
setShowBurst(true);
|
||||
}
|
||||
return { prevReacted, prevCount };
|
||||
},
|
||||
onSuccess: (data, _vars, context) => {
|
||||
@@ -61,14 +68,24 @@ export function PawButton({ postId, petId, initialCount, initialReacted }: PawBu
|
||||
aria-label={reacted ? "Remove paw" : "Paw this post"}
|
||||
className="flex items-center gap-1.5 text-sm transition-colors disabled:opacity-60"
|
||||
>
|
||||
<PawPrint
|
||||
className={`h-4 w-4 transition-colors ${
|
||||
reacted
|
||||
? "fill-orange-500 text-orange-500"
|
||||
: "text-muted-foreground hover:text-orange-500"
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="relative inline-flex h-4 w-4 items-center justify-center">
|
||||
<PawPrint
|
||||
key={animationKey}
|
||||
className={`h-4 w-4 transition-colors ${
|
||||
reacted
|
||||
? "fill-orange-500 text-orange-500 animate-paw-spring"
|
||||
: "text-muted-foreground hover:text-orange-500"
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{showBurst && (
|
||||
<PawPrint
|
||||
className="absolute inset-0 h-4 w-4 fill-orange-500 text-orange-500 animate-paw-burst"
|
||||
aria-hidden="true"
|
||||
onAnimationEnd={() => setShowBurst(false)}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
{count > 0 && (
|
||||
<span className={reacted ? "text-orange-500" : "text-muted-foreground"}>
|
||||
{count}
|
||||
|
||||
Reference in new issue
Block a user