{
  "name": "stack",
  "type": "registry:ui",
  "dependencies": [
    "radix-ui"
  ],
  "files": [
    {
      "path": "ui/stack.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { Slot as SlotPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\ninterface ItemDimension {\n  itemId: number;\n  size: number;\n}\n\ntype Side = \"top\" | \"bottom\";\n\nfunction getDataState(isExpanded: boolean) {\n  return isExpanded ? \"expanded\" : \"collapsed\";\n}\n\ninterface StackContextValue {\n  side: Side;\n  childrenCount: number;\n  itemCount: number;\n  expandedItemCount: number;\n  gap: number;\n  scale: number;\n  offset: number;\n  expandOnHover: boolean;\n  isExpanded: boolean;\n  isInteracting: boolean;\n  dimensions: ItemDimension[];\n  setDimensions: React.Dispatch<React.SetStateAction<ItemDimension[]>>;\n}\n\nconst StackContext = React.createContext<StackContextValue | null>(null);\n\nfunction useStackContext(consumerName: string) {\n  const context = React.useContext(StackContext);\n  if (!context) {\n    throw new Error(`\\`${consumerName}\\` must be used within \\`Stack\\``);\n  }\n  return context;\n}\n\ninterface StackProps extends React.ComponentProps<\"div\"> {\n  side?: Side;\n  itemCount?: number;\n  expandedItemCount?: number;\n  gap?: number;\n  scale?: number;\n  offset?: number;\n  expandOnHover?: boolean;\n  asChild?: boolean;\n}\n\nfunction Stack(props: StackProps) {\n  const {\n    side = \"bottom\",\n    itemCount = 3,\n    expandedItemCount,\n    gap = 8,\n    scale = 0.05,\n    offset = 10,\n    className,\n    children,\n    style,\n    onMouseEnter: onMouseEnterProp,\n    onMouseLeave: onMouseLeaveProp,\n    onMouseMove: onMouseMoveProp,\n    onPointerDown: onPointerDownProp,\n    onPointerUp: onPointerUpProp,\n    expandOnHover = false,\n    asChild,\n    ...rootProps\n  } = props;\n\n  const [isExpanded, setIsExpanded] = React.useState(false);\n  const [isInteracting, setIsInteracting] = React.useState(false);\n  const [dimensions, setDimensions] = React.useState<ItemDimension[]>([]);\n\n  const childrenArray = React.Children.toArray(children).filter(\n    React.isValidElement,\n  );\n  const childrenCount = childrenArray.length;\n\n  const effectiveExpandedItemCount = expandedItemCount ?? childrenCount;\n\n  const onMouseEnter = React.useCallback(\n    (event: React.MouseEvent<HTMLDivElement>) => {\n      onMouseEnterProp?.(event);\n      if (event.defaultPrevented) return;\n\n      if (expandOnHover) {\n        setIsExpanded(true);\n      }\n    },\n    [expandOnHover, onMouseEnterProp],\n  );\n\n  const onMouseMove = React.useCallback(\n    (event: React.MouseEvent<HTMLDivElement>) => {\n      onMouseMoveProp?.(event);\n      if (event.defaultPrevented) return;\n\n      if (expandOnHover) {\n        setIsExpanded(true);\n      }\n    },\n    [expandOnHover, onMouseMoveProp],\n  );\n\n  const onMouseLeave = React.useCallback(\n    (event: React.MouseEvent<HTMLDivElement>) => {\n      onMouseLeaveProp?.(event);\n      if (event.defaultPrevented) return;\n\n      if (expandOnHover && !isInteracting) {\n        setIsExpanded(false);\n      }\n    },\n    [expandOnHover, isInteracting, onMouseLeaveProp],\n  );\n\n  const onPointerDown = React.useCallback(\n    (event: React.PointerEvent<HTMLDivElement>) => {\n      onPointerDownProp?.(event);\n      if (event.defaultPrevented) return;\n\n      setIsInteracting(true);\n    },\n    [onPointerDownProp],\n  );\n\n  const onPointerUp = React.useCallback(\n    (event: React.PointerEvent<HTMLDivElement>) => {\n      onPointerUpProp?.(event);\n      if (event.defaultPrevented) return;\n\n      setIsInteracting(false);\n    },\n    [onPointerUpProp],\n  );\n\n  const contextValue = React.useMemo<StackContextValue>(\n    () => ({\n      side,\n      childrenCount,\n      itemCount,\n      expandedItemCount: effectiveExpandedItemCount,\n      gap,\n      scale,\n      offset,\n      expandOnHover,\n      isExpanded,\n      isInteracting,\n      dimensions,\n      setDimensions,\n    }),\n    [\n      side,\n      childrenCount,\n      itemCount,\n      effectiveExpandedItemCount,\n      gap,\n      scale,\n      offset,\n      expandOnHover,\n      isExpanded,\n      isInteracting,\n      dimensions,\n    ],\n  );\n\n  const RootPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <StackContext.Provider value={contextValue}>\n      <RootPrimitive\n        data-slot=\"stack\"\n        data-state={getDataState(isExpanded)}\n        onMouseEnter={onMouseEnter}\n        onMouseMove={onMouseMove}\n        onMouseLeave={onMouseLeave}\n        onPointerDown={onPointerDown}\n        onPointerUp={onPointerUp}\n        {...rootProps}\n        className={cn(\"relative w-full\", className)}\n        style={\n          {\n            \"--gap\": `${gap}px`,\n            \"--offset\": `${offset}px`,\n            \"--scale\": scale,\n            ...style,\n          } as React.CSSProperties\n        }\n      >\n        {childrenArray.map((child, index) => (\n          <StackItemWrapper key={index} index={index}>\n            {child}\n          </StackItemWrapper>\n        ))}\n      </RootPrimitive>\n    </StackContext.Provider>\n  );\n}\n\nconst stackItemWrapperVariants = cva(\n  \"absolute w-full transition-all duration-300 ease-out\",\n  {\n    variants: {\n      side: {\n        top: [\n          \"top-0 left-0 origin-top\",\n          \"translate-y-[calc(var(--translate)*-1)] scale-[var(--item-scale)]\",\n          \"after:absolute after:top-full after:left-0 after:w-full after:content-['']\",\n        ],\n        bottom: [\n          \"bottom-0 left-0 origin-bottom\",\n          \"translate-y-[var(--translate)] scale-[var(--item-scale)]\",\n          \"after:absolute after:bottom-full after:left-0 after:w-full after:content-['']\",\n        ],\n      },\n      isExpanded: {\n        true: \"after:h-[calc(var(--gap)+1px)]\",\n        false: \"\",\n      },\n      isVisible: {\n        true: \"\",\n        false: \"pointer-events-none\",\n      },\n    },\n  },\n);\n\ntype StackItemWrapperElement = React.ComponentRef<typeof StackItemWrapper>;\n\ninterface StackItemWrapperProps extends React.ComponentProps<\"div\"> {\n  index: number;\n}\n\nfunction StackItemWrapper(props: StackItemWrapperProps) {\n  const { children, className, index, style, ...itemProps } = props;\n\n  const {\n    side,\n    childrenCount,\n    itemCount,\n    expandedItemCount,\n    gap,\n    scale,\n    offset,\n    isExpanded,\n    dimensions,\n    setDimensions,\n  } = useStackContext(\"StackItemWrapper\");\n\n  const itemRef = React.useRef<StackItemWrapperElement>(null);\n\n  const isFront = index === 0;\n  const isVisible = isExpanded ? index < expandedItemCount : index < itemCount;\n\n  React.useEffect(() => {\n    const itemNode = itemRef.current;\n    if (itemNode) {\n      const rect = itemNode.getBoundingClientRect();\n      const measuredHeight = rect.height;\n      const currentScale = 1 - index * scale;\n      const naturalHeight = measuredHeight / currentScale;\n\n      setDimensions((d) => {\n        const existing = d.find((item) => item.itemId === index);\n        if (!existing) {\n          return [...d, { itemId: index, size: naturalHeight }];\n        }\n        return d;\n      });\n    }\n  }, [index, scale, setDimensions]);\n\n  const itemsSizeBefore = React.useMemo(() => {\n    return dimensions.reduce((prev, curr) => {\n      if (curr.itemId >= index) return prev;\n      return prev + curr.size;\n    }, 0);\n  }, [dimensions, index]);\n\n  const itemScale = isExpanded ? 1 : 1 - index * scale;\n  const translateValue = isExpanded\n    ? index * gap + itemsSizeBefore\n    : index * offset;\n  const zIndex = childrenCount - index;\n\n  const opacity = !isVisible ? 0 : isExpanded ? 1 : 1 - index * 0.15;\n\n  return (\n    <div\n      ref={itemRef}\n      data-slot=\"stack-item-wrapper\"\n      data-index={index}\n      data-front={isFront}\n      data-visible={isVisible}\n      data-expanded={isExpanded}\n      className={cn(\n        stackItemWrapperVariants({ side, isExpanded, isVisible, className }),\n      )}\n      style={\n        {\n          \"--translate\": `${translateValue}px`,\n          \"--item-scale\": itemScale,\n          zIndex,\n          opacity,\n          ...style,\n        } as React.CSSProperties\n      }\n      {...itemProps}\n    >\n      <SlotPrimitive.Slot\n        data-index={index}\n        data-position={isFront ? \"front\" : \"back\"}\n        data-state={getDataState(isExpanded)}\n      >\n        {children}\n      </SlotPrimitive.Slot>\n    </div>\n  );\n}\n\ninterface StackItemProps extends React.ComponentProps<\"div\"> {\n  asChild?: boolean;\n}\n\nfunction StackItem(props: StackItemProps) {\n  const { asChild, className, ...itemProps } = props;\n\n  const ItemPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <ItemPrimitive\n      data-slot=\"stack-item\"\n      {...itemProps}\n      className={cn(\n        \"rounded-lg border bg-card p-4 shadow-sm transition-shadow duration-200 hover:shadow-md\",\n        className,\n      )}\n    />\n  );\n}\n\nexport { Stack, StackItem, type StackProps };\n",
      "target": ""
    }
  ]
}