mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-08-29 06:00:55 +02:00
refactors: animations cleanup
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user