{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "three-flip-card", "title": "3D Flip Card", "description": "A compound flip card with 3D tilt on hover and click-to-flip front/back. | 3D傾き効果とクリックで裏表が入れ替わる、マウス追従の3D傾き効果付きフリップカードコンポーネント。", "dependencies": [], "registryDependencies": [ "three-card" ], "files": [ { "path": "components/card/3d-flip-card.tsx", "content": "\"use client\";\n\nimport React, {\n createContext,\n useState,\n useCallback,\n use,\n type ReactNode,\n} from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport ThreeDCard from \"@/components/card/3d-card\";\n\n/** Inline flip container: two faces, CSS 3D transform. Does not own state; receives isFlipped/flipDirection from Root. */\nfunction CardFlipContainer({\n isFlipped,\n flipDirection,\n containerClassName,\n front,\n back,\n}: {\n isFlipped: boolean;\n flipDirection: \"horizontal\" | \"vertical\";\n containerClassName?: string;\n front: ReactNode;\n back: ReactNode;\n}) {\n const isHorizontal = flipDirection === \"horizontal\";\n const rotate = isFlipped ? \"180deg\" : \"0deg\";\n const transformInner = isHorizontal\n ? { transform: `rotateY(${rotate})` }\n : { transform: `rotateX(${rotate})` };\n const transformBack = isHorizontal\n ? { transform: \"rotateY(180deg)\" }\n : { transform: \"rotateX(180deg)\" };\n\n return (\n \n \n \n {front}\n \n \n {back}\n \n \n \n );\n}\n\n/** Context value for flip state and actions. Injected by FlipCard.Root. */\ninterface FlipCardContextValue {\n isFlipped: boolean;\n flip: () => void;\n onFlip?: (flipped: boolean) => void;\n}\n\n/** Which face (front or back) the current subtree belongs to. */\ntype FlipCardSide = \"front\" | \"back\" | null;\n\nconst FlipCardContext = createContext(null);\nconst FlipCardSideContext = createContext(null);\n\n/** Hook to access flip state and actions. Must be used within FlipCard.Root. */\nexport function useFlipCard() {\n const ctx = use(FlipCardContext);\n if (!ctx) {\n throw new Error(\"useFlipCard must be used within FlipCard.Root\");\n }\n return ctx;\n}\n\n/** Root flip card wrapper. Manages flip state and CardFlipContainer. */\nfunction FlipCardRoot({\n children,\n className,\n containerClassName,\n flipDirection = \"horizontal\",\n onFlip,\n}: {\n children: ReactNode;\n className?: string;\n containerClassName?: string;\n flipDirection?: \"horizontal\" | \"vertical\";\n onFlip?: (flipped: boolean) => void;\n}) {\n const [isFlipped, setIsFlipped] = useState(false);\n\n const flip = useCallback(() => {\n setIsFlipped((prev) => {\n const next = !prev;\n onFlip?.(next);\n return next;\n });\n }, [onFlip]);\n\n const value: FlipCardContextValue = {\n isFlipped,\n flip,\n onFlip,\n };\n\n const childArray = React.Children.toArray(children);\n const [front, back] = [\n childArray[0] ??
,\n childArray[1] ??
,\n ];\n\n return (\n \n \n \n );\n}\n\n/** Front face slot. Renders the first visible side. */\nfunction FlipCardFront({ children }: { children: ReactNode }) {\n return (\n \n {children}\n \n );\n}\n\n/** Back face slot. Renders the second side shown when flipped. */\nfunction FlipCardBack({ children }: { children: ReactNode }) {\n return (\n \n {children}\n \n );\n}\n\n/** 3D perspective container. Wraps CardContainer for mouse-tracking tilt. */\nfunction FlipCardContainer({\n children,\n className,\n containerClassName,\n}: {\n children?: ReactNode;\n className?: string;\n containerClassName?: string;\n}) {\n const ctx = use(FlipCardContext);\n const side = use(FlipCardSideContext);\n\n const opacityClass =\n ctx && side === \"front\"\n ? ctx.isFlipped\n ? \"opacity-0\"\n : \"\"\n : ctx && side === \"back\"\n ? ctx.isFlipped\n ? \"\"\n : \"opacity-0\"\n : \"\";\n\n const mergedContainerClassName = cn(\n \"w-full h-full transition-all\",\n opacityClass,\n containerClassName\n );\n\n return (\n \n {children}\n \n );\n}\n\n/** Card surface with preserve-3d. Wraps CardBody. */\nfunction FlipCardBody({\n children,\n className,\n}: {\n children: ReactNode;\n className?: string;\n}) {\n return (\n \n {children}\n \n );\n}\n\n/** 3D transform item. Wraps CardItem for translateZ/rotate effects. */\nfunction FlipCardItem({\n as,\n children,\n className,\n translateX = 0,\n translateY = 0,\n translateZ = 0,\n rotateX = 0,\n rotateY = 0,\n rotateZ = 0,\n ...rest\n}: {\n as?: React.ElementType;\n children: ReactNode;\n className?: string;\n translateX?: number | string;\n translateY?: number | string;\n translateZ?: number | string;\n rotateX?: number | string;\n rotateY?: number | string;\n rotateZ?: number | string;\n [key: string]: unknown;\n}) {\n return (\n >>\n | undefined\n }\n translateX={translateX}\n translateY={translateY}\n translateZ={translateZ}\n rotateX={rotateX}\n rotateY={rotateY}\n rotateZ={rotateZ}\n className={className}\n {...rest}\n >\n {children}\n \n );\n}\n\nconst FlipCard = {\n Root: FlipCardRoot,\n Front: FlipCardFront,\n Back: FlipCardBack,\n Container: FlipCardContainer,\n Body: FlipCardBody,\n Item: FlipCardItem,\n};\n\nexport { FlipCard };\n", "type": "registry:ui" } ], "type": "registry:ui" }