{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hover-expand-icon-strip",
  "type": "registry:component",
  "title": "Hover-Expand Icon Strip",
  "description": "Icons in a fixed-width box that expand on hover or Tab while the others squeeze to fit.",
  "registryDependencies": [
    "https://lab.moumen.dev/r/lab-theme.json"
  ],
  "files": [
    {
      "path": "registry/lab/hover-expand-icon-strip/hover-expand-icon-strip.tsx",
      "type": "registry:component",
      "target": "@components/lab/hover-expand-icon-strip.tsx",
      "content": "\"use client\";\n\n// Hover-expand icon strip - a design-engineered fixed-width row.\n//\n// N icons (3–4) share one fixed-width box. Hovering a panel grows it while the\n// others squeeze to make room, so the row's outer width never changes. The\n// motion is a pure `flex-grow` tween (1 → 5) inside a `flex-basis: 0` row.\n// Keyboard parity: focus-visible expands a panel exactly like hover (motion's\n// whileFocus), so tabbing the strip feels the same as mousing it.\n//\n// The label reveal is deliberately asymmetric: it enters late (a ~90ms delay\n// gives the panel room to open before the text lands, so no crushed clipped word\n// mid-expansion) with opacity + rise + blur, and exits fast with no delay so it\n// ducks out before the shrinking panel squeezes it - each variant carries its\n// own transition. Inspect (blueprint) mode swaps the name for live math.\n//\n// Animation: motion/react (variants propagate hover/focus from the panel to its\n// icon and label). Requires the lab-theme tokens. Fully Tailwind, no CSS files.\n\nimport type { ReactNode } from \"react\";\nimport { motion, MotionConfig, type Variants } from \"motion/react\";\n\nconst EASE = [0.22, 1, 0.36, 1] as const;\n\nexport interface IconStripItem {\n  label: string;\n  icon: ReactNode;\n}\n\nconst iconVariants: Variants = {\n  rest: { y: 0, transition: { duration: 0.35, ease: EASE } },\n  expanded: { y: \"-0.5rem\", transition: { duration: 0.35, ease: EASE } },\n};\n\nconst labelVariants: Variants = {\n  rest: {\n    opacity: 0,\n    y: \"0.25rem\",\n    filter: \"blur(3px)\",\n    transition: { duration: 0.15, ease: \"easeIn\" },\n  },\n  expanded: {\n    opacity: 1,\n    y: 0,\n    filter: \"blur(0px)\",\n    transition: { duration: 0.35, ease: EASE, delay: 0.09 },\n  },\n};\n\nexport default function ExpandingIconStrip({\n  items,\n  width = 320,\n  inspect = false,\n  onSelect,\n}: {\n  items: IconStripItem[];\n  width?: number;\n  inspect?: boolean;\n  onSelect?: (item: IconStripItem) => void;\n}) {\n  return (\n    <MotionConfig reducedMotion=\"user\">\n      <div\n        className={\n          \"relative flex gap-2 h-16\" +\n          (inspect\n            ? \" outline outline-[1.5px] outline-dashed outline-[#3b82f6] outline-offset-[0.375rem] rounded-[0.25rem]\"\n            : \"\")\n        }\n        data-inspect={inspect ? \"true\" : \"false\"}\n        style={{ width: `${width}px`, maxWidth: \"100%\" }}\n      >\n        {items.map((item) => (\n          <motion.button\n            key={item.label}\n            type=\"button\"\n            initial=\"rest\"\n            animate=\"rest\"\n            whileHover=\"expanded\"\n            whileFocus=\"expanded\"\n            whileTap={{ scale: 0.96 }}\n            variants={{ rest: { flexGrow: 1 }, expanded: { flexGrow: 5 } }}\n            transition={{\n              flexGrow: { duration: 0.35, ease: EASE },\n              scale: { duration: 0.15, ease: \"easeOut\" },\n            }}\n            className={\n              \"group/item relative shrink basis-0 min-w-0 flex items-center justify-center px-2 overflow-hidden rounded-[0.875rem] bg-muted text-muted-foreground \" +\n              \"[transition:background-color_250ms_ease,color_250ms_ease] \" +\n              \"hover:bg-primary hover:text-primary-foreground focus-visible:bg-primary focus-visible:text-primary-foreground \" +\n              \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\" +\n              (inspect ? \" outline outline-[1.5px] outline-dashed outline-[#ef4444] -outline-offset-2\" : \"\")\n            }\n            aria-label={item.label}\n            onClick={() => onSelect?.(item)}\n          >\n            <motion.span\n              className=\"flex-none inline-flex\"\n              variants={inspect ? undefined : iconVariants}\n            >\n              {item.icon}\n            </motion.span>\n\n            {inspect ? (\n              // Live math: the annotation follows the value it measures (flex-grow 1 → 5).\n              <span\n                className=\"absolute bottom-[0.375rem] left-1/2 -translate-x-1/2 text-[0.625rem] font-semibold text-[#dc2626] whitespace-nowrap pointer-events-none tabular-nums after:content-['flex:_1'] group-hover/item:after:content-['flex:_5'] group-focus-visible/item:after:content-['flex:_5']\"\n                aria-hidden=\"true\"\n              />\n            ) : (\n              <motion.span\n                className=\"absolute left-0 right-0 bottom-[0.4375rem] text-center text-xs font-medium whitespace-nowrap\"\n                variants={labelVariants}\n              >\n                {item.label}\n              </motion.span>\n            )}\n          </motion.button>\n        ))}\n\n        {inspect && (\n          <span className=\"absolute bottom-[calc(100%+0.75rem)] left-0 px-[0.3125rem] py-[0.0625rem] text-[0.625rem] font-medium text-[#2563eb] whitespace-nowrap bg-white border border-[#bfdbfe] rounded-[0.25rem] shadow-[0_1px_2px_rgba(0,0,0,0.08)] pointer-events-none tabular-nums\">\n            {`Fixed width · ${width}px`}\n          </span>\n        )}\n      </div>\n    </MotionConfig>\n  );\n}\n"
    }
  ],
  "dependencies": [
    "motion"
  ]
}
