refactors: animations cleanup

This commit is contained in:
zhom
2026-07-16 23:56:59 +04:00
parent cef522649a
commit e1c9ce6525
14 changed files with 1070 additions and 645 deletions
+85
View File
@@ -0,0 +1,85 @@
"use client";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import type { ComponentProps, ReactNode } from "react";
import { MOTION_EASE_OUT, MOTION_SPRING_POSITION } from "@/lib/motion";
import { cn } from "@/lib/utils";
interface AnimatedDisclosureItemProps
extends ComponentProps<typeof motion.div> {
children: ReactNode;
}
export function AnimatedDisclosureItem({
children,
...props
}: AnimatedDisclosureItemProps) {
const reduceMotion = useReducedMotion();
return (
<motion.div
layout={reduceMotion ? false : "position"}
transition={reduceMotion ? { duration: 0 } : MOTION_SPRING_POSITION}
{...props}
>
{children}
</motion.div>
);
}
export function AnimatedDisclosureChevron({
open,
children,
className,
}: {
open: boolean;
children: ReactNode;
className?: string;
}) {
const reduceMotion = useReducedMotion();
return (
<motion.span
aria-hidden="true"
animate={{ rotate: open ? 90 : 0 }}
transition={{
duration: reduceMotion ? 0 : 0.16,
ease: MOTION_EASE_OUT,
}}
className={cn("inline-flex shrink-0", className)}
>
{children}
</motion.span>
);
}
export function AnimatedDisclosureContent({
open,
children,
className,
}: {
open: boolean;
children: ReactNode;
className?: string;
}) {
const reduceMotion = useReducedMotion();
return (
<AnimatePresence initial={false}>
{open && (
<motion.div
initial={{ opacity: 0, y: reduceMotion ? 0 : -4 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: reduceMotion ? 0 : -4 }}
transition={{
duration: reduceMotion ? 0.15 : 0.16,
ease: MOTION_EASE_OUT,
}}
className={cn(className)}
>
{children}
</motion.div>
)}
</AnimatePresence>
);
}
+7 -1
View File
@@ -5,7 +5,7 @@ import type * as React from "react";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,background-color,border-color,box-shadow,transform] duration-[120ms] ease-[var(--ease-out)] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
@@ -28,6 +28,12 @@ const buttonVariants = cva(
icon: "size-9",
},
},
compoundVariants: [
{
variant: ["default", "destructive", "outline", "secondary", "ghost"],
className: "active:scale-[0.98] motion-reduce:active:scale-100",
},
],
defaultVariants: {
variant: "default",
size: "default",
+59
View File
@@ -0,0 +1,59 @@
"use client";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import type { Key, ReactNode } from "react";
import { MOTION_EASE_OUT } from "@/lib/motion";
import { cn } from "@/lib/utils";
interface StepTransitionProps {
transitionKey: Key;
direction: 1 | -1;
children: ReactNode;
className?: string;
}
export function StepTransition({
transitionKey,
direction,
children,
className,
}: StepTransitionProps) {
const reduceMotion = useReducedMotion();
return (
<AnimatePresence initial={false} mode="wait" custom={direction}>
<motion.div
key={transitionKey}
custom={direction}
variants={{
enter: (customDirection: 1 | -1) => ({
opacity: 0,
x: reduceMotion ? 0 : customDirection * 6,
}),
center: {
opacity: 1,
x: 0,
transition: {
duration: reduceMotion ? 0.16 : 0.18,
ease: MOTION_EASE_OUT,
},
},
exit: (customDirection: 1 | -1) => ({
opacity: 0,
x: reduceMotion ? 0 : customDirection * -6,
transition: {
duration: reduceMotion ? 0.16 : 0.12,
ease: MOTION_EASE_OUT,
},
}),
}}
initial="enter"
animate="center"
exit="exit"
className={cn(className)}
>
{children}
</motion.div>
</AnimatePresence>
);
}