Files
ui/src/card.tsx
T

87 lines
1.9 KiB
TypeScript

import { Slot } from "@radix-ui/react-slot";
import type { VariantProps } from "class-variance-authority";
import { cva } from "class-variance-authority";
import { type HTMLMotionProps, motion } from "motion/react";
import { forwardRef, type HTMLAttributes, type ReactNode } from "react";
import { cn } from "@/lib/utils";
import { CardDepthProvider, useCardDepth } from "./card-context";
const cardVariants = cva(
`flex flex-col gap-2 ring-2 rounded-card
ring-accent transition-colors grow justify-start
`,
{
variants: {
padding: {
true: "p-card",
false: "",
},
interactive: {
false: "",
true: "hover:bg-accent/30 cursor-pointer",
},
},
defaultVariants: {
interactive: false,
padding: true,
},
},
);
const BASE_RADIUS = 0.625 * 3;
const PADDING = 1.25;
const MIN_RADIUS = 0;
type CommonCardProps = {
asChild?: boolean;
className?: string;
children: ReactNode;
style?: HTMLAttributes<unknown>["style"];
} & VariantProps<typeof cardVariants>;
const Card = forwardRef<HTMLDivElement, CommonCardProps>(
({ asChild, className, interactive, padding, style, ...props }, ref) => {
const depth = useCardDepth();
const radius = Math.max(BASE_RADIUS - depth * PADDING, MIN_RADIUS); // 4px floor
const Comp = asChild ? Slot : "div";
return (
<CardDepthProvider>
<Comp
ref={ref}
style={{ borderRadius: `${radius}rem`, ...style }}
className={cn(cardVariants({ interactive, className, padding }))}
{...props}
/>
</CardDepthProvider>
);
},
);
Card.displayName = "Card";
const MCard = motion.create(Card);
export const AnimatedCard: React.FC<
CommonCardProps & HTMLMotionProps<"div">
> = (props) => {
return (
<MCard
variants={{ enter: { scale: 1 }, from: { scale: 0 } }}
transition={{
type: "spring",
stiffness: 200,
damping: 15,
mass: 2,
}}
{...props}
/>
);
};
export default Card;
export { cardVariants };