{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "three-card", "title": "3D Card", "description": "A 3D card component with customizable data and optional title and description. | カスタマイズ可能なデータとオプションのタイトルと説明を備えた3Dカードコンポーネント。", "files": [ { "path": "components/card/3d-card.tsx", "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\n\nimport React, {\n createContext,\n useState,\n useContext,\n useRef,\n} from \"react\";\n\n/** Sensitivity divisor for tilt effect. Higher = less tilt. */\nconst TILT_SENSITIVITY = 25;\n\n/** CSS perspective value for 3D space. */\nconst PERSPECTIVE = \"1000px\";\n\ninterface Card3DContextValue {\n isMouseEntered: boolean;\n setIsMouseEntered: React.Dispatch>;\n}\n\nconst Card3DContext = createContext(undefined);\n\n/**\n * Root wrapper that enables 3D tilt on hover.\n * Tracks mouse position and provides hover state to children.\n *\n * @param containerClassName - Applied to the outer wrapper (centering, padding)\n * @param className - Applied to the tiltable inner div\n */\nconst CardContainer = ({\n children,\n className,\n containerClassName,\n}: {\n children?: React.ReactNode;\n className?: string;\n containerClassName?: string;\n}) => {\n const containerRef = useRef(null);\n const rectRef = useRef(null);\n const [isMouseEntered, setIsMouseEntered] = useState(false);\n\n const handleMouseEnter = () => {\n if (containerRef.current) {\n rectRef.current = containerRef.current.getBoundingClientRect();\n }\n setIsMouseEntered(true);\n };\n\n const handleMouseMove = (e: React.MouseEvent) => {\n if (!containerRef.current || !rectRef.current) return;\n const { left, top, width, height } = rectRef.current;\n const x = (e.clientX - left - width / 2) / TILT_SENSITIVITY;\n const y = (e.clientY - top - height / 2) / TILT_SENSITIVITY;\n containerRef.current.style.transform = `rotateY(${x}deg) rotateX(${y}deg)`;\n };\n\n const handleMouseLeave = () => {\n rectRef.current = null;\n if (containerRef.current) {\n containerRef.current.style.transform = \"rotateY(0deg) rotateX(0deg)\";\n }\n setIsMouseEntered(false);\n };\n\n const contextValue: Card3DContextValue = {\n isMouseEntered,\n setIsMouseEntered,\n };\n\n return (\n \n \n \n {children}\n \n \n \n );\n};\n\n/**\n * Content wrapper with 3D transform preserved.\n * Place your card content here.\n *\n * @param className - Customize size (default h-96 w-96) and layout\n */\nconst CardBody = ({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) => {\n return (\n *]:[transform-style:preserve-3d]\",\n className\n )}\n >\n {children}\n \n );\n};\n\n/**\n * Child element that moves/rotates in 3D when the card is hovered.\n *\n * @param translateX - X offset in px when hovered\n * @param translateY - Y offset in px when hovered\n * @param translateZ - Z offset (depth) in px when hovered\n * @param rotateX - X rotation in deg when hovered\n * @param rotateY - Y rotation in deg when hovered\n * @param rotateZ - Z rotation in deg when hovered\n * @param as - Render as a different element (e.g. \"span\")\n */\nconst CardItem = ({\n as: Tag = \"div\",\n children,\n className,\n translateX = 0,\n translateY = 0,\n translateZ = 0,\n rotateX = 0,\n rotateY = 0,\n rotateZ = 0,\n style,\n ...rest\n}: {\n as?: React.ElementType;\n children: React.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 style?: React.CSSProperties;\n [key: string]: unknown;\n}) => {\n const [isMouseEntered] = useMouseEnter();\n\n const transform = isMouseEntered\n ? `translateX(${translateX}px) translateY(${translateY}px) translateZ(${translateZ}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotateZ(${rotateZ}deg)`\n : \"translateX(0) translateY(0) translateZ(0) rotateX(0deg) rotateY(0deg) rotateZ(0deg)\";\n\n const tagProps = {\n className: cn(\"w-fit transition duration-200 ease-linear\", className),\n style: { ...style, transform } as React.CSSProperties,\n ...rest,\n };\n const Component = Tag as React.ComponentType<\n React.PropsWithChildren\n >;\n return {children};\n};\n\n/**\n * Hook that returns [isMouseEntered, setIsMouseEntered].\n * Must be used within CardContainer.\n */\nconst useMouseEnter = (): [\n boolean,\n React.Dispatch>,\n] => {\n const context = useContext(Card3DContext);\n if (context === undefined) {\n throw new Error(\"useMouseEnter must be used within CardContainer\");\n }\n return [context.isMouseEntered, context.setIsMouseEntered];\n};\n\nconst ThreeDCard = {\n Container: CardContainer,\n Body: CardBody,\n Item: CardItem,\n useMouseEnter,\n};\n\nexport default ThreeDCard;", "type": "registry:ui" } ], "type": "registry:ui" }