{"slug":"shared-layout-bg","name":"Shared Layout Background","description":"A pill that glides between hovered items via motion's shared layout, with blur enter/exit.","category":"motion","source_url":"https://beui.saura3h.xyz/r/shared-layout-bg/raw","detail_url":"https://beui.saura3h.xyz/r/shared-layout-bg","raw_url":"https://beui.saura3h.xyz/r/shared-layout-bg/raw","page_url":"https://beui.saura3h.xyz/components/motion/shared-layout-bg","dependencies":["lucide-react","motion","react"],"internal":["@/components/motion/shared-layout-bg","@/lib/utils"],"files":[{"path":"components/motion/shared-layout-bg.tsx","type":"component","content":"\"use client\";\n\nimport {\n  AnimatePresence,\n  motion,\n  type Variants,\n} from \"motion/react\";\nimport {\n  Children,\n  cloneElement,\n  isValidElement,\n  useId,\n  useState,\n  type ReactElement,\n  type ReactNode,\n} from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface SharedLayoutBgProps {\n  children: ReactNode;\n  className?: string;\n  /** Tailwind class applied to the moving pill. Defaults to a subtle foreground tint. */\n  pillClassName?: string;\n  /** Horizontal inset of the pill relative to each row (px). Default 20. */\n  inset?: number;\n}\n\nconst variants: Variants = {\n  initial: { opacity: 0, filter: \"blur(6px)\" },\n  animate: { opacity: 1, filter: \"blur(0px)\" },\n  exit: (isActive: boolean) =>\n    !isActive ? { opacity: 0, filter: \"blur(6px)\" } : {},\n};\n\nexport function SharedLayoutBg({\n  children,\n  className,\n  pillClassName,\n  inset = 20,\n}: SharedLayoutBgProps) {\n  const [activeId, setActiveId] = useState<number | null>(null);\n  const uid = useId();\n\n  return (\n    <div\n      onMouseLeave={() => setActiveId(null)}\n      className={cn(\"flex w-full flex-col\", className)}\n    >\n      {Children.toArray(children)\n        .filter(isValidElement)\n        .map((child, index) => {\n          const el = child as ReactElement<{ className?: string; onMouseEnter?: () => void; children?: ReactNode }>;\n          return cloneElement(\n            el,\n            {\n              key: index,\n              className: cn(\"relative\", el.props.className),\n              onMouseEnter: () => setActiveId(index),\n            },\n            <>\n              <AnimatePresence custom={activeId !== null}>\n                {activeId !== null ? (\n                  <motion.div\n                    variants={variants}\n                    initial=\"initial\"\n                    animate=\"animate\"\n                    exit=\"exit\"\n                    custom={activeId !== null}\n                    className=\"pointer-events-none absolute inset-y-0\"\n                    style={{ left: -inset, right: -inset }}\n                  >\n                    {activeId === index ? (\n                      <motion.div\n                        layoutId={`shared-bg-${uid}`}\n                        transition={{ type: \"spring\", stiffness: 205, damping: 22 }}\n                        className={cn(\n                          \"pointer-events-none h-full w-full rounded-2xl bg-(--color-fg)/[0.06]\",\n                          pillClassName,\n                        )}\n                      />\n                    ) : null}\n                  </motion.div>\n                ) : null}\n              </AnimatePresence>\n              <div className=\"relative z-10\">{el.props.children}</div>\n            </>\n          );\n        })}\n    </div>\n  );\n}\n"},{"path":"components/previews/motion/shared-layout-bg.preview.tsx","type":"preview","content":"\"use client\";\n\nimport { ArrowUpRight } from \"lucide-react\";\nimport { SharedLayoutBg } from \"@/components/motion/shared-layout-bg\";\n\nconst items = [\n  { title: \"Inbox\", body: \"12 unread threads, 3 mentions today.\" },\n  { title: \"Drafts\", body: \"4 posts waiting for a final pass.\" },\n  { title: \"Releases\", body: \"Last shipped 2 days ago, v0.4.1.\" },\n  { title: \"Billing\", body: \"Plan renews on the 1st of next month.\" },\n];\n\nexport function SharedLayoutBgPreview() {\n  return (\n    <div className=\"w-full max-w-lg px-2\">\n      <SharedLayoutBg>\n        {items.map((it) => (\n          <a\n            key={it.title}\n            href=\"#\"\n            className=\"group flex flex-col gap-1 px-2 py-3\"\n          >\n            <div className=\"flex items-center justify-between gap-3\">\n              <span className=\"text-sm font-medium text-(--color-fg)\">{it.title}</span>\n              <ArrowUpRight className=\"h-3.5 w-3.5 text-(--color-fg-muted) transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5\" />\n            </div>\n            <p className=\"text-sm text-(--color-fg-muted)\">{it.body}</p>\n          </a>\n        ))}\n      </SharedLayoutBg>\n    </div>\n  );\n}\n"},{"path":"lib/utils.ts","type":"util","content":"import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"}]}