"use client"; import { ChevronDown } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { type ComponentPropsWithoutRef, cloneElement, createContext, forwardRef, isValidElement, type ReactElement, useCallback, useContext, useId, useState, } from "react"; import { EASE_OUT, SPRING_PRESS } from "@/lib/ease"; import { useHoverCapable } from "@/lib/hooks/use-hover-capable"; import { cn } from "@/lib/utils"; // Card is the plain building block: a surface plus shadcn-compatible slots // (CardHeader / CardTitle / CardDescription / CardAction / CardContent / // CardFooter). `TiltCard` adds cursor-tracked 3D tilt, `StatCard` is a KPI // readout and `BentoCard` a marketing tile; reach for those for their effect, // and for this one when you just need a well-behaved container. export type CardVariant = "default" | "outline" | "elevated" | "interactive"; const variantClass: Record = { default: "border border-border bg-card shadow-sm", outline: "border border-border bg-transparent", elevated: "border border-border bg-card shadow-md", interactive: cn( "cursor-pointer border border-border bg-card shadow-sm outline-none transition-[border-color,box-shadow] duration-200", "hover:border-border-strong hover:shadow-md", "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background", ), }; /* ------------------------------------------------------------------------ */ /* Expand state */ /* ------------------------------------------------------------------------ */ interface CardContextValue { expanded: boolean; setExpanded: (next: boolean) => void; contentId: string; } const CardContext = createContext(null); function useCardContext(part: string) { const ctx = useContext(CardContext); if (!ctx) throw new Error(`${part} must be used inside .`); return ctx; } /* ------------------------------------------------------------------------ */ /* Card */ /* ------------------------------------------------------------------------ */ type ClonableChild = ReactElement<{ className?: string }>; export interface CardProps extends ComponentPropsWithoutRef<"div"> { variant?: CardVariant; /** * Merge the card surface onto the single child element instead of * rendering a `
` — e.g. `…` * or a router `` / ` ); } /** Collapsible region of the card, revealed by `CardExpandTrigger`. */ export function CardExpandContent({ className, children, ...props }: Omit< ComponentPropsWithoutRef<"div">, "onAnimationStart" | "onDrag" | "onDragStart" | "onDragEnd" | "onDragOver" >) { const ctx = useCardContext("CardExpandContent"); const reduce = useReducedMotion(); return ( {ctx.expanded ? ( // Height is animated on purpose: disclosing content is this part's // layout job. Reduced motion skips the height morph and only fades.
{children}
) : null}
); }