{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "expandable-grid",
  "title": "Expandable Grid",
  "description": "expandable grid for previewing clicked item.",
  "files": [
    {
      "path": "registry/ecommerce/expandable-grid/index.tsx",
      "content": "'use client';\nimport { cn } from '@/lib/utils';\nimport React from 'react';\n\ntype Breakpoint = [number, number];\nconst DEFAULT_BREAKPOINTS: Breakpoint[] = [\n  [1536, 10],\n  [1280, 8],\n  [1024, 6],\n  [768, 4],\n  [640, 2],\n  [0, 1],\n];\n\ninterface TransformValues {\n  tx: number;\n  ty: number;\n  scale: number;\n  originX: number;\n  originY: number;\n}\n\ninterface ExpandableGridProps extends React.ComponentPropsWithRef<'div'> {\n  breakpoints?: Breakpoint[];\n  expandScale?: number;\n  transitionDuration?: number;\n  padding?: number;\n}\n\ninterface GridContextValue {\n  expandedIndex: number | null;\n  totalItems: number;\n  setExpandedIndex: (index: number | null) => void;\n  registerCell: (index: number, element: HTMLElement) => void;\n}\n\nconst GridContext = React.createContext<GridContextValue | null>(null);\n\nfunction useGridContext() {\n  const context = React.useContext(GridContext);\n  if (!context) {\n    throw new Error('useGridContext must be used within ExpandableGrid');\n  }\n  return context;\n}\n\nfunction getColCount(width: number, breakpoints: Breakpoint[]): number {\n  for (const [minWidth, cols] of breakpoints) {\n    if (width >= minWidth) return cols;\n  }\n  return breakpoints[breakpoints.length - 1][1];\n}\n\nfunction calculateTransform(\n  cellElement: HTMLElement,\n  gridElement: HTMLElement,\n  targetScale: number,\n  padding: number = 40,\n): TransformValues {\n  const cellRect = cellElement.getBoundingClientRect();\n  const gridRect = gridElement.getBoundingClientRect();\n\n  const originX = cellRect.left - gridRect.left + cellRect.width / 2;\n  const originY = cellRect.top - gridRect.top + cellRect.height / 2;\n  const cellCenterX = cellRect.left + cellRect.width / 2;\n  const cellCenterY = cellRect.top + cellRect.height / 2;\n  const viewportCenterX = window.innerWidth / 2;\n  const viewportCenterY = window.innerHeight / 2;\n\n  // Calculate available space in viewport (with padding)\n  const availableWidth = window.innerWidth - padding * 2;\n  const availableHeight = window.innerHeight - padding * 2;\n\n  // Calculate maximum scale that fits in viewport\n  const scaleX = availableWidth / cellRect.width;\n  const scaleY = availableHeight / cellRect.height;\n  const maxScale = Math.min(scaleX, scaleY);\n\n  // Use the smaller of targetScale or maxScale to ensure it fits\n  const finalScale = Math.min(targetScale, maxScale);\n\n  return {\n    tx: viewportCenterX - cellCenterX,\n    ty: viewportCenterY - cellCenterY,\n    scale: finalScale,\n    originX,\n    originY,\n  };\n}\n\nexport function ExpandableGrid({\n  breakpoints = DEFAULT_BREAKPOINTS,\n  expandScale = 3,\n  transitionDuration = 300,\n  padding = 24,\n  style,\n  className,\n  ...props\n}: ExpandableGridProps) {\n  const gridRef = React.useRef<HTMLDivElement>(null);\n  const cellRefs = React.useRef<Map<number, HTMLElement>>(new Map());\n  const [expandedIndex, setExpandedIndex] = React.useState<number | null>(null);\n  const [colCount, setColCount] = React.useState(1);\n  const [totalItems, setTotalItems] = React.useState(0);\n  const [cssVars, setCssVars] = React.useState({\n    tx: 0,\n    ty: 0,\n    scale: 1,\n    originX: '50%',\n    originY: '50%',\n    opacity: 1,\n  });\n\n  const registerCell = React.useCallback(\n    (index: number, element: HTMLElement) => {\n      cellRefs.current.set(index, element);\n      setTotalItems(cellRefs.current.size);\n    },\n    [],\n  );\n\n  // Handle responsive column count\n  React.useEffect(() => {\n    const updateColCount = () => {\n      const width = window.innerWidth;\n      setColCount(getColCount(width, breakpoints));\n    };\n    updateColCount();\n    window.addEventListener('resize', updateColCount);\n    return () => window.removeEventListener('resize', updateColCount);\n  }, [breakpoints]);\n\n  // Recalculate transform on window resize when expanded\n  React.useEffect(() => {\n    if (expandedIndex === null) return;\n\n    const handleResize = () => {\n      const cellElement = cellRefs.current.get(expandedIndex);\n      if (!cellElement || !gridRef.current) return;\n\n      const { tx, ty, scale, originX, originY } = calculateTransform(\n        cellElement,\n        gridRef.current,\n        expandScale,\n        padding,\n      );\n\n      setCssVars({\n        tx,\n        ty,\n        scale,\n        originX: `${originX}px`,\n        originY: `${originY}px`,\n        opacity: 1,\n      });\n    };\n\n    window.addEventListener('resize', handleResize);\n    return () => window.removeEventListener('resize', handleResize);\n  }, [expandedIndex, expandScale, padding]);\n\n  // Calculate and apply transforms when expanded index changes\n  React.useEffect(() => {\n    if (expandedIndex === null) {\n      setCssVars({\n        tx: 0,\n        ty: 0,\n        scale: 1,\n        originX: '50%',\n        originY: '50%',\n        opacity: 1,\n      });\n      return;\n    }\n\n    // Use requestAnimationFrame to ensure DOM is ready\n    requestAnimationFrame(() => {\n      const cellElement = cellRefs.current.get(expandedIndex);\n      if (!cellElement || !gridRef.current) return;\n\n      const { tx, ty, scale, originX, originY } = calculateTransform(\n        cellElement,\n        gridRef.current,\n        expandScale,\n        padding,\n      );\n\n      setCssVars({\n        tx,\n        ty,\n        scale,\n        originX: `${originX}px`,\n        originY: `${originY}px`,\n        opacity: 1,\n      });\n    });\n  }, [expandedIndex, expandScale, padding]);\n\n  // Lock body scroll when expanded\n  React.useEffect(() => {\n    if (expandedIndex !== null) {\n      document.body.style.overflow = 'hidden';\n    } else {\n      document.body.style.overflow = '';\n    }\n    return () => {\n      document.body.style.overflow = '';\n    };\n  }, [expandedIndex]);\n\n  return (\n    <GridContext.Provider\n      value={{ expandedIndex, totalItems, setExpandedIndex, registerCell }}\n    >\n      <div\n        ref={gridRef}\n        className={cn('grid gap-4', className)}\n        style={\n          {\n            '--col-count': colCount,\n            gridTemplateColumns: `repeat(var(--col-count), minmax(0, 1fr))`,\n            '--tx': `${cssVars.tx}px`,\n            '--ty': `${cssVars.ty}px`,\n            '--scale': cssVars.scale,\n            '--opacity': cssVars.opacity,\n            '--duration': `${transitionDuration}ms`,\n            transformOrigin: `${cssVars.originX} ${cssVars.originY}`,\n            transform: `translate(var(--tx), var(--ty)) scale(var(--scale))`,\n            opacity: 'var(--opacity)',\n            transition: `transform var(--duration) ease-out, opacity var(--duration) ease-out`,\n            padding: `${padding}px`,\n            ...style,\n          } as React.CSSProperties\n        }\n        {...props}\n      />\n    </GridContext.Provider>\n  );\n}\n\nexport function ExpandableGridCell({\n  index,\n  onClick,\n  className,\n  ...props\n}: React.ComponentPropsWithRef<'button'> & { index: number }) {\n  const { expandedIndex, setExpandedIndex, registerCell } = useGridContext();\n  const cellRef = React.useRef<HTMLButtonElement>(null);\n  const isExpanded = expandedIndex === index;\n  const isAnyExpanded = expandedIndex !== null;\n\n  React.useEffect(() => {\n    if (cellRef.current) {\n      registerCell(index, cellRef.current);\n    }\n  }, [index, registerCell]);\n\n  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n    if (isExpanded) {\n      setExpandedIndex(null);\n    } else if (!isAnyExpanded) {\n      setExpandedIndex(index);\n    }\n    onClick?.(e);\n  };\n\n  return (\n    <button\n      ref={cellRef}\n      className={cn(\n        'flex-col flex items-center w-[min(500px, 100%)] cursor-pointer outline-none focus:outline-none active:outline-none transition-opacity',\n        isAnyExpanded && !isExpanded && 'opacity-0 pointer-events-none',\n        className,\n      )}\n      onClick={handleClick}\n      {...props}\n    />\n  );\n}\n",
      "type": "registry:block",
      "target": "components/systaliko-ui/expandable-grid.tsx"
    }
  ],
  "type": "registry:block"
}