{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "staggered-entrance",
  "title": "Staggered Entrance",
  "description": "A CSS-powered staggered entrance primitive for lists, cards, steps, rows, and search results.",
  "files": [
    {
      "path": "registry/base/ui/staggered-entrance.tsx",
      "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nimport \"./staggered-entrance.css\";\n\ntype CssVariableStyle = React.CSSProperties & Record<`--${string}`, string>;\ntype StaggeredEntranceAxis = \"x\" | \"y\";\ntype StaggeredEntranceKey<TItem> = (item: TItem, index: number) => React.Key;\ntype StaggeredEntranceClassName<TItem> =\n  | string\n  | ((item: TItem, index: number) => string | undefined);\ntype StaggeredEntranceStyle<TItem> =\n  | React.CSSProperties\n  | ((item: TItem, index: number) => React.CSSProperties | undefined);\n\nexport type StaggeredEntranceProps<TItem = React.ReactNode> = Omit<\n  React.ComponentProps<\"div\">,\n  \"children\"\n> & {\n  as?: React.ElementType;\n  itemAs?: React.ElementType;\n  children?: React.ReactNode;\n  items?: readonly TItem[];\n  renderItem?: (item: TItem, index: number) => React.ReactNode;\n  delay?: number | string;\n  duration?: number | string;\n  initialDelay?: number | string;\n  staggerOffset?: number;\n  axis?: StaggeredEntranceAxis;\n  distance?: number | string;\n  /** Scale each item animates *from* before settling at 1. */\n  fromScale?: number;\n  /** Opacity each item animates *from* before settling at 1. */\n  fromOpacity?: number;\n  reverse?: boolean;\n  disabled?: boolean;\n  getItemKey?: StaggeredEntranceKey<TItem>;\n  itemClassName?: StaggeredEntranceClassName<TItem>;\n  itemStyle?: StaggeredEntranceStyle<TItem>;\n};\n\nexport type StaggeredEntranceItemProps = React.ComponentProps<\"div\"> & {\n  as?: React.ElementType;\n  index?: number;\n};\n\nexport function StaggeredEntrance<TItem = React.ReactNode>({\n  as: Component = \"div\",\n  itemAs = \"div\",\n  children,\n  items,\n  renderItem,\n  getItemKey,\n  delay = 80,\n  duration = 500,\n  initialDelay = 0,\n  staggerOffset = 0,\n  axis = \"y\",\n  distance = 10,\n  fromScale = 1,\n  fromOpacity = 0,\n  reverse = false,\n  disabled = false,\n  itemClassName,\n  itemStyle,\n  className,\n  style,\n  ...props\n}: StaggeredEntranceProps<TItem>) {\n  const entries = React.useMemo(\n    () =>\n      items\n        ? items.map((item, index) => ({\n            item,\n            content: renderItem\n              ? renderItem(item, index)\n              : (item as React.ReactNode),\n          }))\n        : React.Children.toArray(children).map((child) => ({\n            item: child as TItem,\n            content: child,\n          })),\n    [children, items, renderItem]\n  );\n  const count = entries.length;\n  const resolvedDistance = toCssLength(distance);\n  const staggeredEntranceStyle: CssVariableStyle = {\n    \"--staggered-entrance-delay\": toCssTime(delay),\n    \"--staggered-entrance-duration\": toCssTime(duration),\n    \"--staggered-entrance-initial-delay\": toCssTime(initialDelay),\n    \"--staggered-entrance-from-x\": axis === \"x\" ? resolvedDistance : \"0px\",\n    \"--staggered-entrance-from-y\": axis === \"y\" ? resolvedDistance : \"0px\",\n    \"--staggered-entrance-from-scale\": String(fromScale),\n    \"--staggered-entrance-from-opacity\": String(fromOpacity),\n    ...style,\n  };\n\n  return (\n    <Component\n      {...props}\n      data-slot=\"staggered-entrance\"\n      data-disabled={disabled ? \"true\" : undefined}\n      className={cn(\"staggered-entrance\", className)}\n      style={staggeredEntranceStyle}\n    >\n      {entries.map(({ item, content }, index) => {\n        const resolvedIndex = reverse ? count - index - 1 : index;\n        const key = getItemKey\n          ? getItemKey(item, index)\n          : getFallbackKey(content, index);\n\n        return (\n          <StaggeredEntranceItem\n            key={key}\n            as={itemAs}\n            index={resolvedIndex + staggerOffset}\n            className={resolveValue(itemClassName, item, index)}\n            style={resolveValue(itemStyle, item, index)}\n          >\n            {content}\n          </StaggeredEntranceItem>\n        );\n      })}\n    </Component>\n  );\n}\n\nexport function StaggeredEntranceItem({\n  as: Component = \"div\",\n  index,\n  className,\n  style,\n  ...props\n}: StaggeredEntranceItemProps) {\n  const itemStyle: CssVariableStyle =\n    index === undefined\n      ? (style as CssVariableStyle)\n      : { \"--staggered-entrance-index\": String(index), ...style };\n\n  return (\n    <Component\n      {...props}\n      data-slot=\"staggered-entrance-item\"\n      className={cn(\"staggered-entrance-item\", className)}\n      style={itemStyle}\n    />\n  );\n}\n\nfunction toCssTime(value: number | string) {\n  return typeof value === \"number\" ? `${value}ms` : value;\n}\n\nfunction toCssLength(value: number | string) {\n  return typeof value === \"number\" ? `${value}px` : value;\n}\n\nfunction resolveValue<TItem, TValue>(\n  value: TValue | ((item: TItem, index: number) => TValue | undefined),\n  item: TItem,\n  index: number\n) {\n  return typeof value === \"function\"\n    ? (value as (item: TItem, index: number) => TValue | undefined)(item, index)\n    : value;\n}\n\nfunction getFallbackKey(content: React.ReactNode, index: number) {\n  return React.isValidElement(content) && content.key != null\n    ? content.key\n    : index;\n}\n",
      "type": "registry:ui",
      "target": "components/ui/staggered-entrance.tsx"
    },
    {
      "path": "registry/base/ui/staggered-entrance.css",
      "content": ".staggered-entrance {\n  --staggered-entrance-delay: 80ms;\n  --staggered-entrance-duration: 500ms;\n  --staggered-entrance-initial-delay: 0ms;\n  --staggered-entrance-ease: cubic-bezier(0.23, 1, 0.32, 1);\n  --staggered-entrance-from-x: 0px;\n  --staggered-entrance-from-y: 10px;\n  --staggered-entrance-from-scale: 1;\n  --staggered-entrance-from-opacity: 0;\n}\n\n.staggered-entrance-item {\n  animation-name: staggered-entrance-enter;\n  animation-duration: var(--staggered-entrance-duration);\n  animation-timing-function: var(--staggered-entrance-ease);\n  animation-fill-mode: both;\n  animation-delay: calc(\n    var(--staggered-entrance-initial-delay) +\n      (var(--staggered-entrance-delay) * var(--staggered-entrance-index, 0))\n  );\n}\n\n.staggered-entrance[data-disabled=\"true\"] .staggered-entrance-item {\n  animation: none;\n}\n\n@keyframes staggered-entrance-enter {\n  from {\n    opacity: var(--staggered-entrance-from-opacity);\n    transform: translate3d(\n        var(--staggered-entrance-from-x),\n        var(--staggered-entrance-from-y),\n        0\n      )\n      scale(var(--staggered-entrance-from-scale));\n  }\n\n  to {\n    opacity: 1;\n    transform: translate3d(0, 0, 0) scale(1);\n  }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .staggered-entrance-item {\n    animation-duration: 1ms;\n    animation-delay: 0ms;\n  }\n}\n",
      "type": "registry:file",
      "target": "components/ui/staggered-entrance.css"
    }
  ],
  "meta": {
    "tags": [
      "stagger",
      "entrance",
      "css-only",
      "list-animation",
      "list"
    ],
    "effects": [
      "stagger",
      "fade",
      "slide"
    ]
  },
  "categories": [
    "effect"
  ],
  "type": "registry:ui"
}