{
  "slug": "confirm-morph",
  "name": "Confirm Morph",
  "description": "Adaptive inline confirmation button that morphs into question, spinner, success with undo, and error with retry.",
  "summary": "Adaptive physics-driven confirmation button that morphs between resting action, inline question, loading spinner, completed success with undo, and error state with retry. Springs smoothly to every face with zero layout jumps.",
  "category": "buttons",
  "tags": [
    "button",
    "confirm",
    "morph",
    "delete",
    "selection",
    "motion",
    "spring",
    "undo"
  ],
  "dependencies": [
    "clsx",
    "tailwind-merge",
    "motion",
    "lucide-react"
  ],
  "version": "1.0.0",
  "createdDate": "2026-09-30",
  "updatedDate": "2026-09-30",
  "interactive": true,
  "supportsColor": false,
  "highlights": [
    "Adaptive inline button morphing smoothly across resting, confirming, pending, done, and error faces",
    "Dynamic spring physics width expansion and shrink damping with zero layout jumps",
    "Self-measuring natural layout width calculation to prevent layout shifts or content squashing",
    "Full lifecycle support including async pending spinners, animated checkmark drawing with undo, and error alerts with retry",
    "Accessible keyboard handling with Escape to cancel, focus management, and polite screen reader announcements"
  ],
  "anatomy": [
    "<ConfirmMorph> (Root wrapper with animated width spring)",
    "<Face> (Motion container handling enter/exit transitions and natural size measurements)",
    "<Check> (SVG circular pop and checkmark stroke drawing animation)",
    "<DeleteSelection> (Selection orchestrator binding multi-file selection to confirm morph)"
  ],
  "physics": {
    "engine": "Motion Spring & ResizeObserver Geometry",
    "description": "Spring-driven width transitions between measured faces, with faces sliding and fading in and out.",
    "parameters": [
      {
        "label": "Grow Spring",
        "value": "stiffness: 420, damping: 32, mass: 0.8"
      },
      {
        "label": "Shrink Spring",
        "value": "stiffness: 460, damping: 34, mass: 0.8"
      },
      {
        "label": "Slide Spring",
        "value": "stiffness: 420, damping: 32, mass: 0.8 (8px horizontal slide)"
      },
      {
        "label": "Face Fade",
        "value": "opacity 160ms in, 90ms out"
      }
    ]
  },
  "accessibility": {
    "role": "button / group",
    "aria": "Exposes aria-labelledby for confirm prompts, aria-busy when pending, and polite live region announcements.",
    "reducedMotion": "With prefers-reduced-motion, faces cross-fade (120ms in, 80ms out) without sliding, width changes jump instantly, and the checkmark does not draw in.",
    "keyboard": [
      {
        "key": "Escape",
        "description": "Cancel active confirmation or dismiss settled result."
      },
      {
        "key": "Tab",
        "description": "Navigate safe choices (Cancel while asking, Undo or Retry on results)."
      },
      {
        "key": "Enter / Space",
        "description": "Trigger resting action or confirm destructive operation."
      }
    ]
  },
  "guidelines": {
    "recommended": [
      "Destructive or high-impact actions like deleting selections, revoking access, or discarding drafts",
      "Inline workflows where modal dialogs or alert banners feel disproportionately heavy",
      "Reversible operations that benefit from direct in-place Undo triggers"
    ],
    "bestPractices": [
      "Use tone='danger' for irreversible or destructive operations like deletions",
      "Always provide onUndo whenever the backend operation is reversible",
      "Pair with concise action prompts (e.g., 'Delete 3 files?')"
    ]
  },
  "props": [
    {
      "name": "label",
      "type": "React.ReactNode",
      "defaultValue": "undefined",
      "description": "Resting button label, such as 'Delete'.",
      "required": true
    },
    {
      "name": "icon",
      "type": "React.ReactNode",
      "defaultValue": "undefined",
      "description": "Optional icon rendered before the resting label."
    },
    {
      "name": "prompt",
      "type": "React.ReactNode",
      "defaultValue": "undefined",
      "description": "Question shown during confirmation. Defaults to label with a question mark."
    },
    {
      "name": "tone",
      "type": "\"danger\" | \"neutral\"",
      "defaultValue": "\"danger\"",
      "description": "Visual tone: danger tints resting and confirm faces red; neutral uses foreground tones."
    },
    {
      "name": "confirmLabel",
      "type": "string",
      "defaultValue": "\"Delete\"",
      "description": "Label for the confirmation action button."
    },
    {
      "name": "cancelLabel",
      "type": "string",
      "defaultValue": "\"Cancel\"",
      "description": "Label for the cancellation button."
    },
    {
      "name": "pendingLabel",
      "type": "string",
      "defaultValue": "\"Deleting\"",
      "description": "Text displayed beside spinner while onConfirm resolves."
    },
    {
      "name": "doneLabel",
      "type": "string",
      "defaultValue": "\"Deleted\"",
      "description": "Text displayed beside checkmark on resolution."
    },
    {
      "name": "errorLabel",
      "type": "string",
      "defaultValue": "\"Couldn’t finish\"",
      "description": "Text displayed beside alert icon on rejection."
    },
    {
      "name": "retryLabel",
      "type": "string",
      "defaultValue": "\"Retry\"",
      "description": "Label for the retry button on error."
    },
    {
      "name": "undoLabel",
      "type": "string",
      "defaultValue": "\"Undo\"",
      "description": "Label for undo button when onUndo is provided."
    },
    {
      "name": "undoingLabel",
      "type": "string",
      "defaultValue": "\"Restoring\"",
      "description": "Label shown while onUndo is running."
    },
    {
      "name": "onConfirm",
      "type": "() => void | Promise<unknown>",
      "defaultValue": "undefined",
      "description": "Callback executed on confirm. Returning a promise enters pending state."
    },
    {
      "name": "onUndo",
      "type": "() => void | Promise<unknown>",
      "defaultValue": "undefined",
      "description": "Optional callback offering undo on success."
    },
    {
      "name": "onCancel",
      "type": "() => void",
      "description": "Called when the confirmation is dismissed without confirming."
    },
    {
      "name": "state",
      "type": "\"idle\" | \"confirming\" | \"pending\" | \"done\" | \"error\"",
      "defaultValue": "undefined",
      "description": "Controlled state of the morph control."
    },
    {
      "name": "defaultState",
      "type": "\"idle\" | \"confirming\" | \"pending\" | \"done\" | \"error\"",
      "defaultValue": "\"idle\"",
      "description": "Initial state when the component is uncontrolled."
    },
    {
      "name": "onStateChange",
      "type": "(state: ConfirmMorphState) => void",
      "description": "Called whenever the state changes."
    },
    {
      "name": "confirmTimeout",
      "type": "number",
      "defaultValue": "6000",
      "description": "Milliseconds before unanswered question returns to rest (0 turns off)."
    },
    {
      "name": "resultTimeout",
      "type": "number",
      "defaultValue": "5000",
      "description": "Milliseconds a result stays before returning to rest (0 turns off)."
    },
    {
      "name": "cancelOnOutsidePress",
      "type": "boolean",
      "defaultValue": "true",
      "description": "Cancels the confirmation when the user presses outside the component."
    },
    {
      "name": "disabled",
      "type": "boolean",
      "defaultValue": "false",
      "description": "Disables the trigger."
    },
    {
      "name": "className",
      "type": "string",
      "description": "Additional CSS classes for the root element."
    },
    {
      "name": "ref",
      "type": "React.Ref<HTMLDivElement>",
      "description": "Ref to the root element."
    }
  ],
  "usage": "\"use client\";\n\nimport { ConfirmMorph } from \"@/components/ui/confirm-morph\";\n\nexport function ConfirmMorphDemo() {\n  return (\n    <ConfirmMorph\n      label=\"Delete file\"\n      prompt=\"Delete report.pdf?\"\n      onConfirm={() => fetch(\"/api/files/report.pdf\", { method: \"DELETE\" })}\n    />\n  );\n}",
  "files": [
    {
      "name": "confirm-morph.tsx",
      "path": "registry/ui/confirm-morph.tsx",
      "code": "\"use client\";\n\nimport {\n  useCallback,\n  useEffect,\n  useId,\n  useImperativeHandle,\n  useLayoutEffect,\n  useRef,\n  useState,\n  useSyncExternalStore,\n  type KeyboardEvent as ReactKeyboardEvent,\n  type ReactNode,\n  type Ref,\n} from \"react\";\nimport {\n  AnimatePresence,\n  animate,\n  motion,\n  useIsPresent,\n  useMotionValue,\n  useReducedMotion,\n  type AnimationPlaybackControls,\n  type Transition,\n  type Variants,\n} from \"motion/react\";\nimport { CircleAlert, LoaderCircle } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type ConfirmMorphState =\n  | \"idle\"\n  | \"confirming\"\n  | \"pending\"\n  | \"done\"\n  | \"error\";\n\nexport interface ConfirmMorphProps {\n  label: ReactNode;\n  icon?: ReactNode;\n  prompt?: ReactNode;\n  confirmLabel?: string;\n  cancelLabel?: string;\n  pendingLabel?: string;\n  doneLabel?: string;\n  errorLabel?: string;\n  retryLabel?: string;\n  undoLabel?: string;\n  undoingLabel?: string;\n  tone?: \"danger\" | \"neutral\";\n  onConfirm?: () => void | Promise<unknown>;\n  onUndo?: () => void | Promise<unknown>;\n  onCancel?: () => void;\n  state?: ConfirmMorphState;\n  defaultState?: ConfirmMorphState;\n  onStateChange?: (state: ConfirmMorphState) => void;\n  confirmTimeout?: number;\n  resultTimeout?: number;\n  cancelOnOutsidePress?: boolean;\n  disabled?: boolean;\n  className?: string;\n  ref?: Ref<HTMLDivElement>;\n}\n\nconst GROW: Transition = {\n  type: \"spring\",\n  stiffness: 420,\n  damping: 32,\n  mass: 0.8,\n};\n\nconst SHRINK: Transition = {\n  type: \"spring\",\n  stiffness: 460,\n  damping: 34,\n  mass: 0.8,\n};\n\nconst SLIDE: Transition = {\n  type: \"spring\",\n  stiffness: 420,\n  damping: 32,\n  mass: 0.8,\n};\n\nconst emptySubscribe = () => () => {};\n\nfunction useReducedFlag() {\n  const hydrated = useSyncExternalStore(\n    emptySubscribe,\n    () => true,\n    () => false,\n  );\n  const reducedMotion = useReducedMotion();\n  return Boolean(reducedMotion && hydrated);\n}\n\nconst faceVariants: Variants = {\n  hidden: (direction: number) => ({\n    opacity: 0,\n    x: direction * 8,\n  }),\n  shown: {\n    opacity: 1,\n    x: 0,\n    transition: {\n      x: SLIDE,\n      opacity: { duration: 0.16, ease: [0.16, 1, 0.3, 1] },\n    },\n  },\n  gone: (direction: number) => ({\n    opacity: 0,\n    x: direction * -6,\n    transition: {\n      duration: 0.09,\n      ease: [0.2, 0, 0, 1],\n    },\n  }),\n};\n\nconst fadeVariants: Variants = {\n  hidden: { opacity: 0 },\n  shown: { opacity: 1, transition: { duration: 0.12 } },\n  gone: { opacity: 0, transition: { duration: 0.08 } },\n};\n\nfunction Face({\n  id,\n  direction,\n  reduced,\n  onSize,\n  children,\n  labelledBy,\n}: {\n  id: ConfirmMorphState;\n  direction: number;\n  reduced: boolean;\n  onSize: (id: ConfirmMorphState, width: number) => void;\n  children: ReactNode;\n  labelledBy?: string;\n}) {\n  const ref = useRef<HTMLDivElement>(null);\n  const present = useIsPresent();\n\n  useLayoutEffect(() => {\n    const node = ref.current;\n    if (!node || !present) return;\n    const report = () => {\n      const flex = node.style.flex;\n      node.style.flex = \"none\";\n      const width =\n        node.scrollWidth || Math.ceil(node.getBoundingClientRect().width);\n      node.style.flex = flex;\n      onSize(id, width);\n    };\n    report();\n    if (typeof ResizeObserver === \"undefined\") return;\n    const observer = new ResizeObserver(report);\n    observer.observe(node);\n    return () => observer.disconnect();\n  }, [id, onSize, present]);\n\n  return (\n    <motion.div\n      ref={ref}\n      className=\"flex h-full items-center justify-center gap-1.5 px-3 whitespace-nowrap text-xs select-none will-change-[transform,opacity]\"\n      data-face={id}\n      custom={direction}\n      role={labelledBy ? \"group\" : undefined}\n      aria-labelledby={labelledBy}\n      variants={reduced ? fadeVariants : faceVariants}\n      initial=\"hidden\"\n      animate=\"shown\"\n      exit=\"gone\"\n      inert={!present || undefined}\n    >\n      {children}\n    </motion.div>\n  );\n}\n\nfunction Check({ reduced }: { reduced: boolean }) {\n  return (\n    <svg\n      className=\"size-4 shrink-0\"\n      viewBox=\"0 0 18 18\"\n      fill=\"none\"\n      aria-hidden=\"true\"\n    >\n      <motion.circle\n        className=\"fill-emerald-500/15 stroke-emerald-500 dark:stroke-emerald-400\"\n        cx=\"9\"\n        cy=\"9\"\n        r=\"7.5\"\n        strokeWidth=\"1.5\"\n        style={{ transformOrigin: \"9px 9px\" }}\n        initial={reduced ? false : { scale: 0.4, opacity: 0 }}\n        animate={{ scale: 1, opacity: 1 }}\n        transition={{\n          scale: { type: \"spring\", stiffness: 450, damping: 28 },\n          opacity: { duration: 0.12 },\n        }}\n      />\n      <motion.path\n        className=\"stroke-emerald-500 dark:stroke-emerald-400\"\n        d=\"M5.6 9.3 7.8 11.4 12.4 6.7\"\n        strokeWidth=\"1.8\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        initial={reduced ? false : { pathLength: 0 }}\n        animate={{ pathLength: 1 }}\n        transition={{ duration: 0.24, ease: [0.16, 1, 0.3, 1], delay: 0.08 }}\n      />\n    </svg>\n  );\n}\n\nexport function ConfirmMorph({\n  label,\n  icon,\n  prompt,\n  confirmLabel = \"Delete\",\n  cancelLabel = \"Cancel\",\n  pendingLabel = \"Deleting\",\n  doneLabel = \"Deleted\",\n  errorLabel = \"Couldn’t finish\",\n  retryLabel = \"Retry\",\n  undoLabel = \"Undo\",\n  undoingLabel = \"Restoring\",\n  tone = \"danger\",\n  onConfirm,\n  onUndo,\n  onCancel,\n  state: stateProp,\n  defaultState = \"idle\",\n  onStateChange,\n  confirmTimeout = 6000,\n  resultTimeout = 5000,\n  cancelOnOutsidePress = true,\n  disabled = false,\n  className,\n  ref,\n}: ConfirmMorphProps) {\n  const reduced = useReducedFlag();\n  const uid = useId();\n  const promptId = `${uid}-prompt`;\n  const rootRef = useRef<HTMLDivElement>(null);\n  useImperativeHandle(ref, () => rootRef.current as HTMLDivElement, []);\n\n  const [inner, setInner] = useState<ConfirmMorphState>(defaultState);\n  const state = stateProp ?? inner;\n  const [direction, setDirection] = useState(1);\n  const [working, setWorking] = useState<\"confirm\" | \"undo\">(\"confirm\");\n  const [announcement, setAnnouncement] = useState(\"\");\n\n  const live = useRef({\n    state,\n    onStateChange,\n    controlled: stateProp !== undefined,\n  });\n  useLayoutEffect(() => {\n    live.current = {\n      state,\n      onStateChange,\n      controlled: stateProp !== undefined,\n    };\n  });\n  const pendingFocus = useRef(false);\n  const run = useRef(0);\n\n  const go = useCallback((next: ConfirmMorphState) => {\n    const current = live.current.state;\n    if (next === current) return;\n    const root = rootRef.current;\n    pendingFocus.current =\n      Boolean(root) &&\n      (root?.contains(document.activeElement) ||\n        document.activeElement === document.body);\n    setDirection(next === \"idle\" ? -1 : 1);\n    if (!live.current.controlled) setInner(next);\n    live.current.state = next;\n    live.current.onStateChange?.(next);\n  }, []);\n\n  const toIdle = useCallback(() => {\n    run.current++;\n    go(\"idle\");\n  }, [go]);\n\n  const perform = useCallback(\n    async (kind: \"confirm\" | \"undo\") => {\n      const handler = kind === \"confirm\" ? onConfirm : onUndo;\n      const token = ++run.current;\n      setWorking(kind);\n      let result: void | Promise<unknown> | undefined;\n      try {\n        result = handler?.();\n      } catch {\n        go(\"error\");\n        setAnnouncement(errorLabel);\n        return;\n      }\n      if (result && typeof (result as Promise<unknown>).then === \"function\") {\n        go(\"pending\");\n        setAnnouncement(kind === \"confirm\" ? pendingLabel : undoingLabel);\n        try {\n          await result;\n        } catch {\n          if (token !== run.current) return;\n          go(\"error\");\n          setAnnouncement(errorLabel);\n          return;\n        }\n        if (token !== run.current) return;\n      }\n      if (kind === \"undo\") {\n        go(\"idle\");\n        setAnnouncement(\"Undone\");\n        return;\n      }\n      go(\"done\");\n      setAnnouncement(\n        onUndo ? `${doneLabel}. ${undoLabel} is available.` : doneLabel,\n      );\n    },\n    [\n      doneLabel,\n      errorLabel,\n      go,\n      onConfirm,\n      onUndo,\n      pendingLabel,\n      undoLabel,\n      undoingLabel,\n    ],\n  );\n\n  const cancel = useCallback(() => {\n    onCancel?.();\n    toIdle();\n    setAnnouncement(\"Cancelled\");\n  }, [onCancel, toIdle]);\n\n  const expire = useRef(() => {});\n  useLayoutEffect(() => {\n    expire.current = () => {\n      if (live.current.state === \"confirming\") cancel();\n      else toIdle();\n    };\n  });\n\n  const width = useMotionValue<number | \"auto\">(\"auto\");\n  const target = useRef(0);\n  const flight = useRef(0);\n\n  const onFaceSize = useCallback(\n    (id: ConfirmMorphState, w: number) => {\n      if (id !== live.current.state || Math.abs(w - target.current) < 0.5)\n        return;\n      const from = target.current;\n      target.current = w;\n      if (!from || reduced) {\n        width.set(w);\n        return;\n      }\n      if (width.get() === \"auto\") width.jump(from);\n      const token = ++flight.current;\n      animate(width, w, w > from ? GROW : SHRINK).then(() => {\n        if (token === flight.current) width.set(w);\n      });\n    },\n    [reduced, width],\n  );\n\n  const drain = useMotionValue(1);\n  const clock = useRef<AnimationPlaybackControls | null>(null);\n  const holds = useRef({ hover: false, hidden: false });\n  const timeout =\n    state === \"confirming\"\n      ? confirmTimeout\n      : state === \"done\" || state === \"error\"\n        ? resultTimeout\n        : 0;\n\n  const sync = useCallback(() => {\n    const control = clock.current;\n    if (!control) return;\n    const held = holds.current.hover || holds.current.hidden;\n    if (held) control.pause();\n    else control.play();\n  }, []);\n\n  useEffect(() => {\n    if (!timeout) return;\n    drain.jump(1);\n    const control = animate(drain, 0, {\n      duration: timeout / 1000,\n      ease: \"linear\",\n    });\n    clock.current = control;\n    control.then(() => {\n      if (clock.current === control) {\n        clock.current = null;\n        expire.current();\n      }\n    });\n    sync();\n    return () => {\n      if (clock.current === control) clock.current = null;\n      control.stop();\n    };\n  }, [drain, state, sync, timeout]);\n\n  useEffect(() => {\n    const onVisibility = () => {\n      holds.current.hidden = document.hidden;\n      sync();\n    };\n    document.addEventListener(\"visibilitychange\", onVisibility);\n    return () => document.removeEventListener(\"visibilitychange\", onVisibility);\n  }, [sync]);\n\n  useEffect(() => {\n    if (state !== \"confirming\" || !cancelOnOutsidePress) return;\n    const down = (event: PointerEvent) => {\n      if (!rootRef.current?.contains(event.target as Node)) cancel();\n    };\n    document.addEventListener(\"pointerdown\", down);\n    return () => document.removeEventListener(\"pointerdown\", down);\n  }, [cancel, cancelOnOutsidePress, state]);\n\n  useLayoutEffect(() => {\n    if (!pendingFocus.current) return;\n    pendingFocus.current = false;\n    const root = rootRef.current;\n    if (!root) return;\n    const faceElement = root.querySelector<HTMLElement>(\n      `[data-face=\"${state}\"]`,\n    );\n    const autofocus = faceElement?.querySelector<HTMLElement>(\n      \"[data-autofocus]:not(:disabled)\",\n    );\n    (autofocus ?? root).focus({ preventScroll: true });\n  }, [state]);\n\n  const onKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>) => {\n    if (event.key !== \"Escape\") return;\n    if (state === \"confirming\") {\n      event.preventDefault();\n      event.stopPropagation();\n      cancel();\n    } else if (state === \"done\" || state === \"error\") {\n      event.preventDefault();\n      event.stopPropagation();\n      toIdle();\n    }\n  };\n\n  const shownPrompt = prompt ?? <>{label}?</>;\n\n  const face = (() => {\n    switch (state) {\n      case \"confirming\":\n        return (\n          <>\n            <span\n              id={promptId}\n              className=\"text-xs font-medium text-zinc-100 pr-1 select-none\"\n            >\n              {shownPrompt}\n            </span>\n            <button\n              type=\"button\"\n              className=\"rounded-full px-2.5 py-1 text-xs font-medium text-zinc-300 hover:text-white bg-zinc-800 hover:bg-zinc-700 transition-colors cursor-pointer outline-none focus-visible:ring-1 focus-visible:ring-zinc-400 active:scale-95\"\n              data-autofocus\n              onClick={cancel}\n            >\n              {cancelLabel}\n            </button>\n            <button\n              type=\"button\"\n              className={cn(\n                \"rounded-full px-2.5 py-1 text-xs font-medium transition-all cursor-pointer outline-none active:scale-95 shadow-xs\",\n                tone === \"danger\"\n                  ? \"bg-rose-600 text-white hover:bg-rose-500 focus-visible:ring-1 focus-visible:ring-rose-400\"\n                  : \"bg-foreground text-background hover:bg-foreground/90 focus-visible:ring-1 focus-visible:ring-foreground/30\",\n              )}\n              disabled={disabled}\n              onClick={() => void perform(\"confirm\")}\n            >\n              {confirmLabel}\n            </button>\n          </>\n        );\n      case \"pending\":\n        return (\n          <span className=\"flex items-center gap-2 text-xs font-medium text-zinc-300 select-none\">\n            <LoaderCircle\n              className=\"size-3.5 shrink-0 animate-spin text-zinc-400\"\n              strokeWidth={2}\n              aria-hidden=\"true\"\n            />\n            <span>{working === \"undo\" ? undoingLabel : pendingLabel}</span>\n          </span>\n        );\n      case \"done\":\n        return (\n          <>\n            <span className=\"flex items-center gap-1.5 text-xs font-medium text-emerald-400 select-none\">\n              <Check reduced={reduced} />\n              <span>{doneLabel}</span>\n            </span>\n            {onUndo && (\n              <button\n                type=\"button\"\n                className=\"rounded-full px-2.5 py-1 text-xs font-medium text-zinc-300 hover:text-white bg-zinc-800 hover:bg-zinc-700 transition-colors cursor-pointer outline-none focus-visible:ring-1 focus-visible:ring-zinc-400 active:scale-95 ml-1\"\n                data-autofocus\n                onClick={() => void perform(\"undo\")}\n              >\n                {undoLabel}\n              </button>\n            )}\n          </>\n        );\n      case \"error\":\n        return (\n          <>\n            <span className=\"flex items-center gap-1.5 text-xs font-medium text-rose-400 select-none\">\n              <CircleAlert\n                className=\"size-3.5 shrink-0 text-rose-400\"\n                strokeWidth={2}\n                aria-hidden=\"true\"\n              />\n              <span>{errorLabel}</span>\n            </span>\n            <button\n              type=\"button\"\n              className=\"rounded-full px-2.5 py-1 text-xs font-medium text-rose-300 hover:text-white bg-rose-500/20 hover:bg-rose-500/30 border border-rose-500/30 transition-colors cursor-pointer outline-none focus-visible:ring-1 focus-visible:ring-rose-400 active:scale-95 ml-1\"\n              data-autofocus\n              onClick={() => void perform(working)}\n            >\n              {retryLabel}\n            </button>\n          </>\n        );\n      default:\n        return (\n          <button\n            type=\"button\"\n            className={cn(\n              \"inline-flex items-center gap-1.5 text-xs font-medium cursor-pointer transition-colors outline-none rounded-full select-none\",\n              tone === \"danger\"\n                ? \"text-rose-400 hover:text-rose-300\"\n                : \"text-zinc-200 hover:text-white\",\n              disabled && \"opacity-50 pointer-events-none cursor-not-allowed\",\n            )}\n            data-autofocus\n            disabled={disabled}\n            onClick={() => {\n              setAnnouncement(\n                typeof shownPrompt === \"string\" ? shownPrompt : \"\",\n              );\n              go(\"confirming\");\n            }}\n          >\n            {icon && (\n              <span\n                className=\"shrink-0 size-3.5 flex items-center justify-center text-current\"\n                aria-hidden=\"true\"\n              >\n                {icon}\n              </span>\n            )}\n            <span>{label}</span>\n          </button>\n        );\n    }\n  })();\n\n  return (\n    <div\n      ref={rootRef}\n      className={cn(\n        \"relative inline-flex items-center select-none\",\n        disabled &&\n          (state === \"idle\" || state === \"confirming\") &&\n          \"opacity-50 pointer-events-none\",\n        className,\n      )}\n      data-state={state}\n      data-tone={tone}\n      data-disabled={disabled || undefined}\n      tabIndex={-1}\n      onKeyDown={onKeyDown}\n      aria-busy={state === \"pending\" || undefined}\n      onPointerEnter={() => {\n        holds.current.hover = true;\n        sync();\n      }}\n      onPointerLeave={() => {\n        holds.current.hover = false;\n        sync();\n      }}\n      onPointerCancel={() => {\n        holds.current.hover = false;\n        sync();\n      }}\n    >\n      <motion.div\n        className={cn(\n          \"box-content relative inline-flex h-8 items-center justify-center overflow-hidden rounded-full border transition-colors duration-150 backdrop-blur-md\",\n          tone === \"danger\" &&\n            state === \"idle\" &&\n            \"border-rose-500/25 bg-rose-500/10 hover:border-rose-500/40 hover:bg-rose-500/15\",\n          tone === \"neutral\" &&\n            state === \"idle\" &&\n            \"border-zinc-700/60 bg-zinc-800/80 hover:border-zinc-600 hover:bg-zinc-800\",\n          state === \"confirming\" && \"border-zinc-700/80 bg-zinc-900 shadow-md\",\n          state === \"pending\" && \"border-zinc-700/80 bg-zinc-900/90 shadow-md\",\n          state === \"done\" && \"border-emerald-500/30 bg-emerald-500/10\",\n          state === \"error\" && \"border-rose-500/30 bg-rose-500/10\",\n        )}\n        style={{ width }}\n      >\n        <AnimatePresence mode=\"popLayout\" initial={false} custom={direction}>\n          <Face\n            key={state}\n            id={state}\n            direction={direction}\n            reduced={reduced}\n            onSize={onFaceSize}\n            labelledBy={state === \"confirming\" ? promptId : undefined}\n          >\n            {face}\n          </Face>\n        </AnimatePresence>\n      </motion.div>\n      <span className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n        {announcement}\n      </span>\n    </div>\n  );\n}\n\nexport default ConfirmMorph;\n",
      "content": "\"use client\";\n\nimport {\n  useCallback,\n  useEffect,\n  useId,\n  useImperativeHandle,\n  useLayoutEffect,\n  useRef,\n  useState,\n  useSyncExternalStore,\n  type KeyboardEvent as ReactKeyboardEvent,\n  type ReactNode,\n  type Ref,\n} from \"react\";\nimport {\n  AnimatePresence,\n  animate,\n  motion,\n  useIsPresent,\n  useMotionValue,\n  useReducedMotion,\n  type AnimationPlaybackControls,\n  type Transition,\n  type Variants,\n} from \"motion/react\";\nimport { CircleAlert, LoaderCircle } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type ConfirmMorphState =\n  | \"idle\"\n  | \"confirming\"\n  | \"pending\"\n  | \"done\"\n  | \"error\";\n\nexport interface ConfirmMorphProps {\n  label: ReactNode;\n  icon?: ReactNode;\n  prompt?: ReactNode;\n  confirmLabel?: string;\n  cancelLabel?: string;\n  pendingLabel?: string;\n  doneLabel?: string;\n  errorLabel?: string;\n  retryLabel?: string;\n  undoLabel?: string;\n  undoingLabel?: string;\n  tone?: \"danger\" | \"neutral\";\n  onConfirm?: () => void | Promise<unknown>;\n  onUndo?: () => void | Promise<unknown>;\n  onCancel?: () => void;\n  state?: ConfirmMorphState;\n  defaultState?: ConfirmMorphState;\n  onStateChange?: (state: ConfirmMorphState) => void;\n  confirmTimeout?: number;\n  resultTimeout?: number;\n  cancelOnOutsidePress?: boolean;\n  disabled?: boolean;\n  className?: string;\n  ref?: Ref<HTMLDivElement>;\n}\n\nconst GROW: Transition = {\n  type: \"spring\",\n  stiffness: 420,\n  damping: 32,\n  mass: 0.8,\n};\n\nconst SHRINK: Transition = {\n  type: \"spring\",\n  stiffness: 460,\n  damping: 34,\n  mass: 0.8,\n};\n\nconst SLIDE: Transition = {\n  type: \"spring\",\n  stiffness: 420,\n  damping: 32,\n  mass: 0.8,\n};\n\nconst emptySubscribe = () => () => {};\n\nfunction useReducedFlag() {\n  const hydrated = useSyncExternalStore(\n    emptySubscribe,\n    () => true,\n    () => false,\n  );\n  const reducedMotion = useReducedMotion();\n  return Boolean(reducedMotion && hydrated);\n}\n\nconst faceVariants: Variants = {\n  hidden: (direction: number) => ({\n    opacity: 0,\n    x: direction * 8,\n  }),\n  shown: {\n    opacity: 1,\n    x: 0,\n    transition: {\n      x: SLIDE,\n      opacity: { duration: 0.16, ease: [0.16, 1, 0.3, 1] },\n    },\n  },\n  gone: (direction: number) => ({\n    opacity: 0,\n    x: direction * -6,\n    transition: {\n      duration: 0.09,\n      ease: [0.2, 0, 0, 1],\n    },\n  }),\n};\n\nconst fadeVariants: Variants = {\n  hidden: { opacity: 0 },\n  shown: { opacity: 1, transition: { duration: 0.12 } },\n  gone: { opacity: 0, transition: { duration: 0.08 } },\n};\n\nfunction Face({\n  id,\n  direction,\n  reduced,\n  onSize,\n  children,\n  labelledBy,\n}: {\n  id: ConfirmMorphState;\n  direction: number;\n  reduced: boolean;\n  onSize: (id: ConfirmMorphState, width: number) => void;\n  children: ReactNode;\n  labelledBy?: string;\n}) {\n  const ref = useRef<HTMLDivElement>(null);\n  const present = useIsPresent();\n\n  useLayoutEffect(() => {\n    const node = ref.current;\n    if (!node || !present) return;\n    const report = () => {\n      const flex = node.style.flex;\n      node.style.flex = \"none\";\n      const width =\n        node.scrollWidth || Math.ceil(node.getBoundingClientRect().width);\n      node.style.flex = flex;\n      onSize(id, width);\n    };\n    report();\n    if (typeof ResizeObserver === \"undefined\") return;\n    const observer = new ResizeObserver(report);\n    observer.observe(node);\n    return () => observer.disconnect();\n  }, [id, onSize, present]);\n\n  return (\n    <motion.div\n      ref={ref}\n      className=\"flex h-full items-center justify-center gap-1.5 px-3 whitespace-nowrap text-xs select-none will-change-[transform,opacity]\"\n      data-face={id}\n      custom={direction}\n      role={labelledBy ? \"group\" : undefined}\n      aria-labelledby={labelledBy}\n      variants={reduced ? fadeVariants : faceVariants}\n      initial=\"hidden\"\n      animate=\"shown\"\n      exit=\"gone\"\n      inert={!present || undefined}\n    >\n      {children}\n    </motion.div>\n  );\n}\n\nfunction Check({ reduced }: { reduced: boolean }) {\n  return (\n    <svg\n      className=\"size-4 shrink-0\"\n      viewBox=\"0 0 18 18\"\n      fill=\"none\"\n      aria-hidden=\"true\"\n    >\n      <motion.circle\n        className=\"fill-emerald-500/15 stroke-emerald-500 dark:stroke-emerald-400\"\n        cx=\"9\"\n        cy=\"9\"\n        r=\"7.5\"\n        strokeWidth=\"1.5\"\n        style={{ transformOrigin: \"9px 9px\" }}\n        initial={reduced ? false : { scale: 0.4, opacity: 0 }}\n        animate={{ scale: 1, opacity: 1 }}\n        transition={{\n          scale: { type: \"spring\", stiffness: 450, damping: 28 },\n          opacity: { duration: 0.12 },\n        }}\n      />\n      <motion.path\n        className=\"stroke-emerald-500 dark:stroke-emerald-400\"\n        d=\"M5.6 9.3 7.8 11.4 12.4 6.7\"\n        strokeWidth=\"1.8\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        initial={reduced ? false : { pathLength: 0 }}\n        animate={{ pathLength: 1 }}\n        transition={{ duration: 0.24, ease: [0.16, 1, 0.3, 1], delay: 0.08 }}\n      />\n    </svg>\n  );\n}\n\nexport function ConfirmMorph({\n  label,\n  icon,\n  prompt,\n  confirmLabel = \"Delete\",\n  cancelLabel = \"Cancel\",\n  pendingLabel = \"Deleting\",\n  doneLabel = \"Deleted\",\n  errorLabel = \"Couldn’t finish\",\n  retryLabel = \"Retry\",\n  undoLabel = \"Undo\",\n  undoingLabel = \"Restoring\",\n  tone = \"danger\",\n  onConfirm,\n  onUndo,\n  onCancel,\n  state: stateProp,\n  defaultState = \"idle\",\n  onStateChange,\n  confirmTimeout = 6000,\n  resultTimeout = 5000,\n  cancelOnOutsidePress = true,\n  disabled = false,\n  className,\n  ref,\n}: ConfirmMorphProps) {\n  const reduced = useReducedFlag();\n  const uid = useId();\n  const promptId = `${uid}-prompt`;\n  const rootRef = useRef<HTMLDivElement>(null);\n  useImperativeHandle(ref, () => rootRef.current as HTMLDivElement, []);\n\n  const [inner, setInner] = useState<ConfirmMorphState>(defaultState);\n  const state = stateProp ?? inner;\n  const [direction, setDirection] = useState(1);\n  const [working, setWorking] = useState<\"confirm\" | \"undo\">(\"confirm\");\n  const [announcement, setAnnouncement] = useState(\"\");\n\n  const live = useRef({\n    state,\n    onStateChange,\n    controlled: stateProp !== undefined,\n  });\n  useLayoutEffect(() => {\n    live.current = {\n      state,\n      onStateChange,\n      controlled: stateProp !== undefined,\n    };\n  });\n  const pendingFocus = useRef(false);\n  const run = useRef(0);\n\n  const go = useCallback((next: ConfirmMorphState) => {\n    const current = live.current.state;\n    if (next === current) return;\n    const root = rootRef.current;\n    pendingFocus.current =\n      Boolean(root) &&\n      (root?.contains(document.activeElement) ||\n        document.activeElement === document.body);\n    setDirection(next === \"idle\" ? -1 : 1);\n    if (!live.current.controlled) setInner(next);\n    live.current.state = next;\n    live.current.onStateChange?.(next);\n  }, []);\n\n  const toIdle = useCallback(() => {\n    run.current++;\n    go(\"idle\");\n  }, [go]);\n\n  const perform = useCallback(\n    async (kind: \"confirm\" | \"undo\") => {\n      const handler = kind === \"confirm\" ? onConfirm : onUndo;\n      const token = ++run.current;\n      setWorking(kind);\n      let result: void | Promise<unknown> | undefined;\n      try {\n        result = handler?.();\n      } catch {\n        go(\"error\");\n        setAnnouncement(errorLabel);\n        return;\n      }\n      if (result && typeof (result as Promise<unknown>).then === \"function\") {\n        go(\"pending\");\n        setAnnouncement(kind === \"confirm\" ? pendingLabel : undoingLabel);\n        try {\n          await result;\n        } catch {\n          if (token !== run.current) return;\n          go(\"error\");\n          setAnnouncement(errorLabel);\n          return;\n        }\n        if (token !== run.current) return;\n      }\n      if (kind === \"undo\") {\n        go(\"idle\");\n        setAnnouncement(\"Undone\");\n        return;\n      }\n      go(\"done\");\n      setAnnouncement(\n        onUndo ? `${doneLabel}. ${undoLabel} is available.` : doneLabel,\n      );\n    },\n    [\n      doneLabel,\n      errorLabel,\n      go,\n      onConfirm,\n      onUndo,\n      pendingLabel,\n      undoLabel,\n      undoingLabel,\n    ],\n  );\n\n  const cancel = useCallback(() => {\n    onCancel?.();\n    toIdle();\n    setAnnouncement(\"Cancelled\");\n  }, [onCancel, toIdle]);\n\n  const expire = useRef(() => {});\n  useLayoutEffect(() => {\n    expire.current = () => {\n      if (live.current.state === \"confirming\") cancel();\n      else toIdle();\n    };\n  });\n\n  const width = useMotionValue<number | \"auto\">(\"auto\");\n  const target = useRef(0);\n  const flight = useRef(0);\n\n  const onFaceSize = useCallback(\n    (id: ConfirmMorphState, w: number) => {\n      if (id !== live.current.state || Math.abs(w - target.current) < 0.5)\n        return;\n      const from = target.current;\n      target.current = w;\n      if (!from || reduced) {\n        width.set(w);\n        return;\n      }\n      if (width.get() === \"auto\") width.jump(from);\n      const token = ++flight.current;\n      animate(width, w, w > from ? GROW : SHRINK).then(() => {\n        if (token === flight.current) width.set(w);\n      });\n    },\n    [reduced, width],\n  );\n\n  const drain = useMotionValue(1);\n  const clock = useRef<AnimationPlaybackControls | null>(null);\n  const holds = useRef({ hover: false, hidden: false });\n  const timeout =\n    state === \"confirming\"\n      ? confirmTimeout\n      : state === \"done\" || state === \"error\"\n        ? resultTimeout\n        : 0;\n\n  const sync = useCallback(() => {\n    const control = clock.current;\n    if (!control) return;\n    const held = holds.current.hover || holds.current.hidden;\n    if (held) control.pause();\n    else control.play();\n  }, []);\n\n  useEffect(() => {\n    if (!timeout) return;\n    drain.jump(1);\n    const control = animate(drain, 0, {\n      duration: timeout / 1000,\n      ease: \"linear\",\n    });\n    clock.current = control;\n    control.then(() => {\n      if (clock.current === control) {\n        clock.current = null;\n        expire.current();\n      }\n    });\n    sync();\n    return () => {\n      if (clock.current === control) clock.current = null;\n      control.stop();\n    };\n  }, [drain, state, sync, timeout]);\n\n  useEffect(() => {\n    const onVisibility = () => {\n      holds.current.hidden = document.hidden;\n      sync();\n    };\n    document.addEventListener(\"visibilitychange\", onVisibility);\n    return () => document.removeEventListener(\"visibilitychange\", onVisibility);\n  }, [sync]);\n\n  useEffect(() => {\n    if (state !== \"confirming\" || !cancelOnOutsidePress) return;\n    const down = (event: PointerEvent) => {\n      if (!rootRef.current?.contains(event.target as Node)) cancel();\n    };\n    document.addEventListener(\"pointerdown\", down);\n    return () => document.removeEventListener(\"pointerdown\", down);\n  }, [cancel, cancelOnOutsidePress, state]);\n\n  useLayoutEffect(() => {\n    if (!pendingFocus.current) return;\n    pendingFocus.current = false;\n    const root = rootRef.current;\n    if (!root) return;\n    const faceElement = root.querySelector<HTMLElement>(\n      `[data-face=\"${state}\"]`,\n    );\n    const autofocus = faceElement?.querySelector<HTMLElement>(\n      \"[data-autofocus]:not(:disabled)\",\n    );\n    (autofocus ?? root).focus({ preventScroll: true });\n  }, [state]);\n\n  const onKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>) => {\n    if (event.key !== \"Escape\") return;\n    if (state === \"confirming\") {\n      event.preventDefault();\n      event.stopPropagation();\n      cancel();\n    } else if (state === \"done\" || state === \"error\") {\n      event.preventDefault();\n      event.stopPropagation();\n      toIdle();\n    }\n  };\n\n  const shownPrompt = prompt ?? <>{label}?</>;\n\n  const face = (() => {\n    switch (state) {\n      case \"confirming\":\n        return (\n          <>\n            <span\n              id={promptId}\n              className=\"text-xs font-medium text-zinc-100 pr-1 select-none\"\n            >\n              {shownPrompt}\n            </span>\n            <button\n              type=\"button\"\n              className=\"rounded-full px-2.5 py-1 text-xs font-medium text-zinc-300 hover:text-white bg-zinc-800 hover:bg-zinc-700 transition-colors cursor-pointer outline-none focus-visible:ring-1 focus-visible:ring-zinc-400 active:scale-95\"\n              data-autofocus\n              onClick={cancel}\n            >\n              {cancelLabel}\n            </button>\n            <button\n              type=\"button\"\n              className={cn(\n                \"rounded-full px-2.5 py-1 text-xs font-medium transition-all cursor-pointer outline-none active:scale-95 shadow-xs\",\n                tone === \"danger\"\n                  ? \"bg-rose-600 text-white hover:bg-rose-500 focus-visible:ring-1 focus-visible:ring-rose-400\"\n                  : \"bg-foreground text-background hover:bg-foreground/90 focus-visible:ring-1 focus-visible:ring-foreground/30\",\n              )}\n              disabled={disabled}\n              onClick={() => void perform(\"confirm\")}\n            >\n              {confirmLabel}\n            </button>\n          </>\n        );\n      case \"pending\":\n        return (\n          <span className=\"flex items-center gap-2 text-xs font-medium text-zinc-300 select-none\">\n            <LoaderCircle\n              className=\"size-3.5 shrink-0 animate-spin text-zinc-400\"\n              strokeWidth={2}\n              aria-hidden=\"true\"\n            />\n            <span>{working === \"undo\" ? undoingLabel : pendingLabel}</span>\n          </span>\n        );\n      case \"done\":\n        return (\n          <>\n            <span className=\"flex items-center gap-1.5 text-xs font-medium text-emerald-400 select-none\">\n              <Check reduced={reduced} />\n              <span>{doneLabel}</span>\n            </span>\n            {onUndo && (\n              <button\n                type=\"button\"\n                className=\"rounded-full px-2.5 py-1 text-xs font-medium text-zinc-300 hover:text-white bg-zinc-800 hover:bg-zinc-700 transition-colors cursor-pointer outline-none focus-visible:ring-1 focus-visible:ring-zinc-400 active:scale-95 ml-1\"\n                data-autofocus\n                onClick={() => void perform(\"undo\")}\n              >\n                {undoLabel}\n              </button>\n            )}\n          </>\n        );\n      case \"error\":\n        return (\n          <>\n            <span className=\"flex items-center gap-1.5 text-xs font-medium text-rose-400 select-none\">\n              <CircleAlert\n                className=\"size-3.5 shrink-0 text-rose-400\"\n                strokeWidth={2}\n                aria-hidden=\"true\"\n              />\n              <span>{errorLabel}</span>\n            </span>\n            <button\n              type=\"button\"\n              className=\"rounded-full px-2.5 py-1 text-xs font-medium text-rose-300 hover:text-white bg-rose-500/20 hover:bg-rose-500/30 border border-rose-500/30 transition-colors cursor-pointer outline-none focus-visible:ring-1 focus-visible:ring-rose-400 active:scale-95 ml-1\"\n              data-autofocus\n              onClick={() => void perform(working)}\n            >\n              {retryLabel}\n            </button>\n          </>\n        );\n      default:\n        return (\n          <button\n            type=\"button\"\n            className={cn(\n              \"inline-flex items-center gap-1.5 text-xs font-medium cursor-pointer transition-colors outline-none rounded-full select-none\",\n              tone === \"danger\"\n                ? \"text-rose-400 hover:text-rose-300\"\n                : \"text-zinc-200 hover:text-white\",\n              disabled && \"opacity-50 pointer-events-none cursor-not-allowed\",\n            )}\n            data-autofocus\n            disabled={disabled}\n            onClick={() => {\n              setAnnouncement(\n                typeof shownPrompt === \"string\" ? shownPrompt : \"\",\n              );\n              go(\"confirming\");\n            }}\n          >\n            {icon && (\n              <span\n                className=\"shrink-0 size-3.5 flex items-center justify-center text-current\"\n                aria-hidden=\"true\"\n              >\n                {icon}\n              </span>\n            )}\n            <span>{label}</span>\n          </button>\n        );\n    }\n  })();\n\n  return (\n    <div\n      ref={rootRef}\n      className={cn(\n        \"relative inline-flex items-center select-none\",\n        disabled &&\n          (state === \"idle\" || state === \"confirming\") &&\n          \"opacity-50 pointer-events-none\",\n        className,\n      )}\n      data-state={state}\n      data-tone={tone}\n      data-disabled={disabled || undefined}\n      tabIndex={-1}\n      onKeyDown={onKeyDown}\n      aria-busy={state === \"pending\" || undefined}\n      onPointerEnter={() => {\n        holds.current.hover = true;\n        sync();\n      }}\n      onPointerLeave={() => {\n        holds.current.hover = false;\n        sync();\n      }}\n      onPointerCancel={() => {\n        holds.current.hover = false;\n        sync();\n      }}\n    >\n      <motion.div\n        className={cn(\n          \"box-content relative inline-flex h-8 items-center justify-center overflow-hidden rounded-full border transition-colors duration-150 backdrop-blur-md\",\n          tone === \"danger\" &&\n            state === \"idle\" &&\n            \"border-rose-500/25 bg-rose-500/10 hover:border-rose-500/40 hover:bg-rose-500/15\",\n          tone === \"neutral\" &&\n            state === \"idle\" &&\n            \"border-zinc-700/60 bg-zinc-800/80 hover:border-zinc-600 hover:bg-zinc-800\",\n          state === \"confirming\" && \"border-zinc-700/80 bg-zinc-900 shadow-md\",\n          state === \"pending\" && \"border-zinc-700/80 bg-zinc-900/90 shadow-md\",\n          state === \"done\" && \"border-emerald-500/30 bg-emerald-500/10\",\n          state === \"error\" && \"border-rose-500/30 bg-rose-500/10\",\n        )}\n        style={{ width }}\n      >\n        <AnimatePresence mode=\"popLayout\" initial={false} custom={direction}>\n          <Face\n            key={state}\n            id={state}\n            direction={direction}\n            reduced={reduced}\n            onSize={onFaceSize}\n            labelledBy={state === \"confirming\" ? promptId : undefined}\n          >\n            {face}\n          </Face>\n        </AnimatePresence>\n      </motion.div>\n      <span className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n        {announcement}\n      </span>\n    </div>\n  );\n}\n\nexport default ConfirmMorph;\n",
      "type": "registry:ui",
      "target": "@ui/confirm-morph.tsx"
    },
    {
      "name": "delete-selection.tsx",
      "path": "registry/ui/delete-selection.tsx",
      "code": "\"use client\";\n\nimport { useRef, useState } from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { Check, FileSpreadsheet, FileText, Minus, Trash2 } from \"lucide-react\";\nimport { ConfirmMorph } from \"./confirm-morph\";\nimport { cn } from \"@/lib/utils\";\n\nexport const deleteFiles = async (ids: string[]) => {\n  await new Promise((resolve) => setTimeout(resolve, 900));\n  return ids;\n};\n\nexport const restoreFiles = async (ids: string[]) => {\n  await new Promise((resolve) => setTimeout(resolve, 700));\n  return ids;\n};\n\nexport interface DeleteSelectionProps {\n  ids: string[];\n  onConfirm?: (ids: string[]) => void | Promise<unknown>;\n  onUndo?: (ids: string[]) => void | Promise<unknown>;\n  disabled?: boolean;\n}\n\nexport function DeleteSelection({\n  ids,\n  onConfirm,\n  onUndo,\n  disabled,\n}: DeleteSelectionProps) {\n  const count = ids.length;\n  const promptText = `Delete ${count} ${count === 1 ? \"file\" : \"files\"}?`;\n\n  return (\n    <ConfirmMorph\n      label=\"Delete\"\n      icon={<Trash2 size={16} strokeWidth={1.75} />}\n      prompt={promptText}\n      tone=\"danger\"\n      disabled={disabled || count === 0}\n      onConfirm={() => (onConfirm ? onConfirm(ids) : deleteFiles(ids))}\n      onUndo={() => (onUndo ? onUndo(ids) : restoreFiles(ids))}\n    />\n  );\n}\n\ninterface FileItem {\n  id: string;\n  name: string;\n  author: string;\n  date: string;\n  size: string;\n  type: \"image\" | \"pdf\" | \"sheet\";\n  thumbnail?: string;\n}\n\nconst INITIAL_FILES: FileItem[] = [\n  {\n    id: \"1\",\n    name: \"pookie virat 01.jpg\",\n    author: \"Pookie Virat\",\n    date: \"Sep 21\",\n    size: \"4.2 MB\",\n    type: \"image\",\n    thumbnail:\n      \"https://i.pinimg.com/736x/1d/49/b5/1d49b5da4ebfc59d5d7d4f1c288ecf17.jpg\",\n  },\n  {\n    id: \"2\",\n    name: \"pookie virat 02.jpg\",\n    author: \"Pookie Virat\",\n    date: \"Sep 20\",\n    size: \"3.8 MB\",\n    type: \"image\",\n    thumbnail:\n      \"https://i.pinimg.com/1200x/63/01/21/630121fe689e8d2c43b0b10180d31f84.jpg\",\n  },\n  {\n    id: \"3\",\n    name: \"pookie virat 03.jpg\",\n    author: \"Pookie Virat\",\n    date: \"Sep 19\",\n    size: \"5.1 MB\",\n    type: \"image\",\n    thumbnail:\n      \"https://i.pinimg.com/736x/54/cc/b5/54ccb5138e6452e45dcec18aee7b9f39.jpg\",\n  },\n  {\n    id: \"4\",\n    name: \"pookie virat 04.jpg\",\n    author: \"Pookie Virat\",\n    date: \"Sep 18\",\n    size: \"2.9 MB\",\n    type: \"image\",\n    thumbnail:\n      \"https://i.pinimg.com/736x/25/4b/0b/254b0b03a39fc3736053c28263fa5053.jpg\",\n  },\n  {\n    id: \"5\",\n    name: \"pookie virat 05.jpg\",\n    author: \"Pookie Virat\",\n    date: \"Sep 17\",\n    size: \"4.6 MB\",\n    type: \"image\",\n    thumbnail:\n      \"https://i.pinimg.com/736x/78/68/5e/78685eb464deaa7a7cc9cad4b12e9ef9.jpg\",\n  },\n];\n\nexport function DeleteSelectionShowcase() {\n  const [files, setFiles] = useState<FileItem[]>(INITIAL_FILES);\n  const [selectedIds, setSelectedIds] = useState<string[]>([\"2\", \"3\"]);\n  const [outcome, setOutcome] = useState<\"succeeds\" | \"fails\">(\"succeeds\");\n  const [lastDeleted, setLastDeleted] = useState<{\n    items: FileItem[];\n    ids: string[];\n  } | null>(null);\n\n  const highlightRef = useRef<HTMLDivElement>(null);\n  const itemRefs = useRef<(HTMLDivElement | null)[]>([]);\n\n  const handleMouseEnter = (index: number) => {\n    const el = itemRefs.current[index];\n    const hl = highlightRef.current;\n    if (!el || !hl) return;\n    hl.style.transform = `translate3d(${el.offsetLeft}px, ${el.offsetTop}px, 0)`;\n    hl.style.width = `${el.offsetWidth}px`;\n    hl.style.height = `${el.offsetHeight}px`;\n    hl.style.opacity = \"1\";\n  };\n\n  const handleMouseLeave = () => {\n    const hl = highlightRef.current;\n    if (!hl) return;\n    hl.style.opacity = \"0\";\n  };\n\n  const toggleSelect = (id: string) => {\n    setSelectedIds((prev) =>\n      prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id],\n    );\n  };\n\n  const toggleAll = () => {\n    if (selectedIds.length > 0) {\n      setSelectedIds([]);\n    } else {\n      setSelectedIds(files.map((f) => f.id));\n    }\n  };\n\n  const handleConfirm = async (ids: string[]) => {\n    await new Promise((resolve) => setTimeout(resolve, 900));\n    if (outcome === \"fails\") {\n      throw new Error(\"Failed to delete files\");\n    }\n    const removed = files.filter((f) => ids.includes(f.id));\n    setLastDeleted({ items: removed, ids });\n    setFiles((prev) => prev.filter((f) => !ids.includes(f.id)));\n    setSelectedIds([]);\n    handleMouseLeave();\n  };\n\n  const handleUndo = async () => {\n    await new Promise((resolve) => setTimeout(resolve, 700));\n    if (lastDeleted) {\n      setFiles((prev) => {\n        const merged = [...prev];\n        for (const item of lastDeleted.items) {\n          if (!merged.some((m) => m.id === item.id)) {\n            merged.push(item);\n          }\n        }\n        return merged.sort(\n          (a, b) =>\n            INITIAL_FILES.findIndex((f) => f.id === a.id) -\n            INITIAL_FILES.findIndex((f) => f.id === b.id),\n        );\n      });\n      setSelectedIds(lastDeleted.ids);\n      setLastDeleted(null);\n    }\n  };\n\n  const isAllSelected = files.length > 0 && selectedIds.length === files.length;\n  const isPartiallySelected =\n    selectedIds.length > 0 && selectedIds.length < files.length;\n\n  return (\n    <div className=\"flex flex-col items-center justify-center w-full max-w-lg mx-auto p-4 select-none\">\n      <div className=\"w-full rounded-[26px] bg-[#161618] border border-white/8 p-5 shadow-2xl transition-all\">\n        <div className=\"flex items-center justify-between pb-4\">\n          <div className=\"flex items-center gap-3\">\n            <AnimatePresence mode=\"popLayout\" initial={false}>\n              {files.length > 0 && (\n                <motion.button\n                  key=\"select-all-btn\"\n                  type=\"button\"\n                  role=\"checkbox\"\n                  aria-checked={\n                    isAllSelected ? true : isPartiallySelected ? \"mixed\" : false\n                  }\n                  aria-label={\n                    isAllSelected ? \"Deselect all files\" : \"Select all files\"\n                  }\n                  initial={{ scale: 0, opacity: 0, width: 0 }}\n                  animate={{ scale: 1, opacity: 1, width: 28 }}\n                  exit={{ scale: 0, opacity: 0, width: 0 }}\n                  transition={{ type: \"spring\", stiffness: 450, damping: 32 }}\n                  onClick={toggleAll}\n                  className=\"size-7 shrink-0 rounded-lg bg-zinc-800 border border-zinc-700/60 flex items-center justify-center cursor-pointer transition-colors hover:bg-zinc-700/70 outline-none focus-visible:ring-1 focus-visible:ring-zinc-400 overflow-hidden\"\n                >\n                  <AnimatePresence mode=\"wait\">\n                    {isAllSelected ? (\n                      <motion.div\n                        key=\"check\"\n                        initial={{ scale: 0.5, opacity: 0 }}\n                        animate={{ scale: 1, opacity: 1 }}\n                        exit={{ scale: 0.5, opacity: 0 }}\n                        transition={{ duration: 0.12 }}\n                      >\n                        <Check\n                          size={14}\n                          className=\"text-zinc-200\"\n                          strokeWidth={2.5}\n                        />\n                      </motion.div>\n                    ) : isPartiallySelected ? (\n                      <motion.div\n                        key=\"minus\"\n                        initial={{ scale: 0.5, opacity: 0 }}\n                        animate={{ scale: 1, opacity: 1 }}\n                        exit={{ scale: 0.5, opacity: 0 }}\n                        transition={{ duration: 0.12 }}\n                      >\n                        <Minus\n                          size={14}\n                          className=\"text-zinc-200\"\n                          strokeWidth={2.5}\n                        />\n                      </motion.div>\n                    ) : null}\n                  </AnimatePresence>\n                </motion.button>\n              )}\n            </AnimatePresence>\n\n            <div className=\"flex flex-col text-left\">\n              <span className=\"text-sm font-medium text-zinc-100 leading-tight\">\n                pookie virat\n              </span>\n              <span className=\"text-xs text-zinc-400 leading-normal\">\n                {files.length === 0\n                  ? \"No files remaining\"\n                  : `${selectedIds.length} of ${files.length} selected`}\n              </span>\n            </div>\n          </div>\n\n          <DeleteSelection\n            ids={selectedIds}\n            onConfirm={handleConfirm}\n            onUndo={handleUndo}\n          />\n        </div>\n\n        <div\n          onMouseLeave={handleMouseLeave}\n          className=\"relative flex flex-col gap-1 mt-1 min-h-10\"\n        >\n          {files.length > 0 && (\n            <div\n              ref={highlightRef}\n              aria-hidden=\"true\"\n              className=\"pointer-events-none absolute top-0 left-0 rounded-2xl bg-white/[0.07] dark:bg-white/[0.07] border border-white/5 z-0 opacity-0 transition-[transform,width,height,opacity] duration-200 ease-[cubic-bezier(0.16,1,0.3,1)]\"\n            />\n          )}\n\n          <AnimatePresence mode=\"popLayout\" initial={false}>\n            {files.map((file, idx) => {\n              const isSelected = selectedIds.includes(file.id);\n              return (\n                <motion.div\n                  layout\n                  key={file.id}\n                  ref={(node) => {\n                    itemRefs.current[idx] = node;\n                  }}\n                  initial={{ opacity: 0, y: 10, scale: 0.98 }}\n                  animate={{ opacity: 1, y: 0, scale: 1 }}\n                  exit={{\n                    opacity: 0,\n                    scale: 0.96,\n                    y: -6,\n                    transition: { duration: 0.16, ease: [0.2, 0, 0, 1] },\n                  }}\n                  transition={{\n                    layout: { type: \"spring\", stiffness: 450, damping: 32 },\n                    opacity: { duration: 0.18 },\n                  }}\n                  onMouseEnter={() => handleMouseEnter(idx)}\n                  onClick={() => toggleSelect(file.id)}\n                  className={cn(\n                    \"group relative z-10 flex items-center justify-between px-3 py-2.5 rounded-2xl cursor-pointer transition-colors duration-150\",\n                    isSelected\n                      ? \"bg-zinc-800/80 border border-white/5 shadow-xs\"\n                      : \"border border-transparent\",\n                  )}\n                >\n                  <div className=\"flex items-center gap-3.5 min-w-0\">\n                    <div\n                      className={cn(\n                        \"size-5 rounded-md border flex items-center justify-center shrink-0 transition-colors\",\n                        isSelected\n                          ? \"bg-zinc-200 border-zinc-200 text-zinc-950\"\n                          : \"border-zinc-700 bg-zinc-800/40 group-hover:border-zinc-600\",\n                      )}\n                    >\n                      {isSelected && (\n                        <Check\n                          size={12}\n                          strokeWidth={3}\n                          className=\"text-zinc-950\"\n                        />\n                      )}\n                    </div>\n\n                    <div className=\"size-10 rounded-xl overflow-hidden shrink-0 border border-white/10 flex items-center justify-center bg-zinc-800/90 text-zinc-400\">\n                      {file.type === \"image\" && file.thumbnail ? (\n                        <img\n                          src={file.thumbnail}\n                          alt={file.name}\n                          className=\"size-full object-cover\"\n                          loading=\"lazy\"\n                        />\n                      ) : file.type === \"pdf\" ? (\n                        <FileText size={18} className=\"text-zinc-300\" />\n                      ) : (\n                        <FileSpreadsheet size={18} className=\"text-zinc-300\" />\n                      )}\n                    </div>\n\n                    <div className=\"flex flex-col text-left min-w-0 truncate\">\n                      <span\n                        className={cn(\n                          \"text-sm font-medium leading-snug truncate\",\n                          isSelected ? \"text-zinc-100\" : \"text-zinc-200\",\n                        )}\n                      >\n                        {file.name}\n                      </span>\n                      <span className=\"text-xs text-zinc-400 truncate\">\n                        {file.author} · {file.date}\n                      </span>\n                    </div>\n                  </div>\n\n                  <div className=\"text-xs text-zinc-400 font-mono shrink-0 pl-3\">\n                    {file.size}\n                  </div>\n                </motion.div>\n              );\n            })}\n          </AnimatePresence>\n\n          {files.length === 0 && (\n            <motion.div\n              key=\"empty-state\"\n              initial={{ opacity: 0, scale: 0.96, y: 6 }}\n              animate={{ opacity: 1, scale: 1, y: 0 }}\n              exit={{ opacity: 0, scale: 0.96, y: -6 }}\n              transition={{ type: \"spring\", stiffness: 420, damping: 32 }}\n              className=\"flex flex-col items-center justify-center py-8 px-4 text-center select-none\"\n            >\n              <div className=\"size-10 rounded-2xl bg-zinc-800/60 border border-white/10 flex items-center justify-center text-zinc-400 mb-2.5\">\n                <Trash2 size={18} strokeWidth={1.75} />\n              </div>\n              <span className=\"text-sm font-medium text-zinc-200\">\n                All files deleted\n              </span>\n            </motion.div>\n          )}\n        </div>\n      </div>\n\n      <div className=\"mt-6 flex items-center justify-center\">\n        <div className=\"relative inline-flex items-center p-1 rounded-full bg-[#161618] border border-white/8 shadow-lg\">\n          <button\n            type=\"button\"\n            onClick={() => setOutcome(\"succeeds\")}\n            className={cn(\n              \"relative z-10 px-4 py-1 text-xs font-medium rounded-full cursor-pointer transition-colors duration-200 outline-none focus-visible:ring-1 focus-visible:ring-zinc-400\",\n              outcome === \"succeeds\"\n                ? \"text-white\"\n                : \"text-zinc-400 hover:text-zinc-200\",\n            )}\n          >\n            {outcome === \"succeeds\" && (\n              <motion.div\n                layoutId=\"outcome-slider\"\n                className=\"absolute inset-0 rounded-full bg-zinc-800 border border-white/10 shadow-xs -z-10\"\n                transition={{\n                  type: \"spring\",\n                  stiffness: 480,\n                  damping: 34,\n                  mass: 0.8,\n                }}\n              />\n            )}\n            Succeeds\n          </button>\n          <button\n            type=\"button\"\n            onClick={() => setOutcome(\"fails\")}\n            className={cn(\n              \"relative z-10 px-4 py-1 text-xs font-medium rounded-full cursor-pointer transition-colors duration-200 outline-none focus-visible:ring-1 focus-visible:ring-zinc-400\",\n              outcome === \"fails\"\n                ? \"text-white\"\n                : \"text-zinc-400 hover:text-zinc-200\",\n            )}\n          >\n            {outcome === \"fails\" && (\n              <motion.div\n                layoutId=\"outcome-slider\"\n                className=\"absolute inset-0 rounded-full bg-zinc-800 border border-white/10 shadow-xs -z-10\"\n                transition={{\n                  type: \"spring\",\n                  stiffness: 480,\n                  damping: 34,\n                  mass: 0.8,\n                }}\n              />\n            )}\n            Fails\n          </button>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport default DeleteSelectionShowcase;\n",
      "content": "\"use client\";\n\nimport { useRef, useState } from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { Check, FileSpreadsheet, FileText, Minus, Trash2 } from \"lucide-react\";\nimport { ConfirmMorph } from \"./confirm-morph\";\nimport { cn } from \"@/lib/utils\";\n\nexport const deleteFiles = async (ids: string[]) => {\n  await new Promise((resolve) => setTimeout(resolve, 900));\n  return ids;\n};\n\nexport const restoreFiles = async (ids: string[]) => {\n  await new Promise((resolve) => setTimeout(resolve, 700));\n  return ids;\n};\n\nexport interface DeleteSelectionProps {\n  ids: string[];\n  onConfirm?: (ids: string[]) => void | Promise<unknown>;\n  onUndo?: (ids: string[]) => void | Promise<unknown>;\n  disabled?: boolean;\n}\n\nexport function DeleteSelection({\n  ids,\n  onConfirm,\n  onUndo,\n  disabled,\n}: DeleteSelectionProps) {\n  const count = ids.length;\n  const promptText = `Delete ${count} ${count === 1 ? \"file\" : \"files\"}?`;\n\n  return (\n    <ConfirmMorph\n      label=\"Delete\"\n      icon={<Trash2 size={16} strokeWidth={1.75} />}\n      prompt={promptText}\n      tone=\"danger\"\n      disabled={disabled || count === 0}\n      onConfirm={() => (onConfirm ? onConfirm(ids) : deleteFiles(ids))}\n      onUndo={() => (onUndo ? onUndo(ids) : restoreFiles(ids))}\n    />\n  );\n}\n\ninterface FileItem {\n  id: string;\n  name: string;\n  author: string;\n  date: string;\n  size: string;\n  type: \"image\" | \"pdf\" | \"sheet\";\n  thumbnail?: string;\n}\n\nconst INITIAL_FILES: FileItem[] = [\n  {\n    id: \"1\",\n    name: \"pookie virat 01.jpg\",\n    author: \"Pookie Virat\",\n    date: \"Sep 21\",\n    size: \"4.2 MB\",\n    type: \"image\",\n    thumbnail:\n      \"https://i.pinimg.com/736x/1d/49/b5/1d49b5da4ebfc59d5d7d4f1c288ecf17.jpg\",\n  },\n  {\n    id: \"2\",\n    name: \"pookie virat 02.jpg\",\n    author: \"Pookie Virat\",\n    date: \"Sep 20\",\n    size: \"3.8 MB\",\n    type: \"image\",\n    thumbnail:\n      \"https://i.pinimg.com/1200x/63/01/21/630121fe689e8d2c43b0b10180d31f84.jpg\",\n  },\n  {\n    id: \"3\",\n    name: \"pookie virat 03.jpg\",\n    author: \"Pookie Virat\",\n    date: \"Sep 19\",\n    size: \"5.1 MB\",\n    type: \"image\",\n    thumbnail:\n      \"https://i.pinimg.com/736x/54/cc/b5/54ccb5138e6452e45dcec18aee7b9f39.jpg\",\n  },\n  {\n    id: \"4\",\n    name: \"pookie virat 04.jpg\",\n    author: \"Pookie Virat\",\n    date: \"Sep 18\",\n    size: \"2.9 MB\",\n    type: \"image\",\n    thumbnail:\n      \"https://i.pinimg.com/736x/25/4b/0b/254b0b03a39fc3736053c28263fa5053.jpg\",\n  },\n  {\n    id: \"5\",\n    name: \"pookie virat 05.jpg\",\n    author: \"Pookie Virat\",\n    date: \"Sep 17\",\n    size: \"4.6 MB\",\n    type: \"image\",\n    thumbnail:\n      \"https://i.pinimg.com/736x/78/68/5e/78685eb464deaa7a7cc9cad4b12e9ef9.jpg\",\n  },\n];\n\nexport function DeleteSelectionShowcase() {\n  const [files, setFiles] = useState<FileItem[]>(INITIAL_FILES);\n  const [selectedIds, setSelectedIds] = useState<string[]>([\"2\", \"3\"]);\n  const [outcome, setOutcome] = useState<\"succeeds\" | \"fails\">(\"succeeds\");\n  const [lastDeleted, setLastDeleted] = useState<{\n    items: FileItem[];\n    ids: string[];\n  } | null>(null);\n\n  const highlightRef = useRef<HTMLDivElement>(null);\n  const itemRefs = useRef<(HTMLDivElement | null)[]>([]);\n\n  const handleMouseEnter = (index: number) => {\n    const el = itemRefs.current[index];\n    const hl = highlightRef.current;\n    if (!el || !hl) return;\n    hl.style.transform = `translate3d(${el.offsetLeft}px, ${el.offsetTop}px, 0)`;\n    hl.style.width = `${el.offsetWidth}px`;\n    hl.style.height = `${el.offsetHeight}px`;\n    hl.style.opacity = \"1\";\n  };\n\n  const handleMouseLeave = () => {\n    const hl = highlightRef.current;\n    if (!hl) return;\n    hl.style.opacity = \"0\";\n  };\n\n  const toggleSelect = (id: string) => {\n    setSelectedIds((prev) =>\n      prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id],\n    );\n  };\n\n  const toggleAll = () => {\n    if (selectedIds.length > 0) {\n      setSelectedIds([]);\n    } else {\n      setSelectedIds(files.map((f) => f.id));\n    }\n  };\n\n  const handleConfirm = async (ids: string[]) => {\n    await new Promise((resolve) => setTimeout(resolve, 900));\n    if (outcome === \"fails\") {\n      throw new Error(\"Failed to delete files\");\n    }\n    const removed = files.filter((f) => ids.includes(f.id));\n    setLastDeleted({ items: removed, ids });\n    setFiles((prev) => prev.filter((f) => !ids.includes(f.id)));\n    setSelectedIds([]);\n    handleMouseLeave();\n  };\n\n  const handleUndo = async () => {\n    await new Promise((resolve) => setTimeout(resolve, 700));\n    if (lastDeleted) {\n      setFiles((prev) => {\n        const merged = [...prev];\n        for (const item of lastDeleted.items) {\n          if (!merged.some((m) => m.id === item.id)) {\n            merged.push(item);\n          }\n        }\n        return merged.sort(\n          (a, b) =>\n            INITIAL_FILES.findIndex((f) => f.id === a.id) -\n            INITIAL_FILES.findIndex((f) => f.id === b.id),\n        );\n      });\n      setSelectedIds(lastDeleted.ids);\n      setLastDeleted(null);\n    }\n  };\n\n  const isAllSelected = files.length > 0 && selectedIds.length === files.length;\n  const isPartiallySelected =\n    selectedIds.length > 0 && selectedIds.length < files.length;\n\n  return (\n    <div className=\"flex flex-col items-center justify-center w-full max-w-lg mx-auto p-4 select-none\">\n      <div className=\"w-full rounded-[26px] bg-[#161618] border border-white/8 p-5 shadow-2xl transition-all\">\n        <div className=\"flex items-center justify-between pb-4\">\n          <div className=\"flex items-center gap-3\">\n            <AnimatePresence mode=\"popLayout\" initial={false}>\n              {files.length > 0 && (\n                <motion.button\n                  key=\"select-all-btn\"\n                  type=\"button\"\n                  role=\"checkbox\"\n                  aria-checked={\n                    isAllSelected ? true : isPartiallySelected ? \"mixed\" : false\n                  }\n                  aria-label={\n                    isAllSelected ? \"Deselect all files\" : \"Select all files\"\n                  }\n                  initial={{ scale: 0, opacity: 0, width: 0 }}\n                  animate={{ scale: 1, opacity: 1, width: 28 }}\n                  exit={{ scale: 0, opacity: 0, width: 0 }}\n                  transition={{ type: \"spring\", stiffness: 450, damping: 32 }}\n                  onClick={toggleAll}\n                  className=\"size-7 shrink-0 rounded-lg bg-zinc-800 border border-zinc-700/60 flex items-center justify-center cursor-pointer transition-colors hover:bg-zinc-700/70 outline-none focus-visible:ring-1 focus-visible:ring-zinc-400 overflow-hidden\"\n                >\n                  <AnimatePresence mode=\"wait\">\n                    {isAllSelected ? (\n                      <motion.div\n                        key=\"check\"\n                        initial={{ scale: 0.5, opacity: 0 }}\n                        animate={{ scale: 1, opacity: 1 }}\n                        exit={{ scale: 0.5, opacity: 0 }}\n                        transition={{ duration: 0.12 }}\n                      >\n                        <Check\n                          size={14}\n                          className=\"text-zinc-200\"\n                          strokeWidth={2.5}\n                        />\n                      </motion.div>\n                    ) : isPartiallySelected ? (\n                      <motion.div\n                        key=\"minus\"\n                        initial={{ scale: 0.5, opacity: 0 }}\n                        animate={{ scale: 1, opacity: 1 }}\n                        exit={{ scale: 0.5, opacity: 0 }}\n                        transition={{ duration: 0.12 }}\n                      >\n                        <Minus\n                          size={14}\n                          className=\"text-zinc-200\"\n                          strokeWidth={2.5}\n                        />\n                      </motion.div>\n                    ) : null}\n                  </AnimatePresence>\n                </motion.button>\n              )}\n            </AnimatePresence>\n\n            <div className=\"flex flex-col text-left\">\n              <span className=\"text-sm font-medium text-zinc-100 leading-tight\">\n                pookie virat\n              </span>\n              <span className=\"text-xs text-zinc-400 leading-normal\">\n                {files.length === 0\n                  ? \"No files remaining\"\n                  : `${selectedIds.length} of ${files.length} selected`}\n              </span>\n            </div>\n          </div>\n\n          <DeleteSelection\n            ids={selectedIds}\n            onConfirm={handleConfirm}\n            onUndo={handleUndo}\n          />\n        </div>\n\n        <div\n          onMouseLeave={handleMouseLeave}\n          className=\"relative flex flex-col gap-1 mt-1 min-h-10\"\n        >\n          {files.length > 0 && (\n            <div\n              ref={highlightRef}\n              aria-hidden=\"true\"\n              className=\"pointer-events-none absolute top-0 left-0 rounded-2xl bg-white/[0.07] dark:bg-white/[0.07] border border-white/5 z-0 opacity-0 transition-[transform,width,height,opacity] duration-200 ease-[cubic-bezier(0.16,1,0.3,1)]\"\n            />\n          )}\n\n          <AnimatePresence mode=\"popLayout\" initial={false}>\n            {files.map((file, idx) => {\n              const isSelected = selectedIds.includes(file.id);\n              return (\n                <motion.div\n                  layout\n                  key={file.id}\n                  ref={(node) => {\n                    itemRefs.current[idx] = node;\n                  }}\n                  initial={{ opacity: 0, y: 10, scale: 0.98 }}\n                  animate={{ opacity: 1, y: 0, scale: 1 }}\n                  exit={{\n                    opacity: 0,\n                    scale: 0.96,\n                    y: -6,\n                    transition: { duration: 0.16, ease: [0.2, 0, 0, 1] },\n                  }}\n                  transition={{\n                    layout: { type: \"spring\", stiffness: 450, damping: 32 },\n                    opacity: { duration: 0.18 },\n                  }}\n                  onMouseEnter={() => handleMouseEnter(idx)}\n                  onClick={() => toggleSelect(file.id)}\n                  className={cn(\n                    \"group relative z-10 flex items-center justify-between px-3 py-2.5 rounded-2xl cursor-pointer transition-colors duration-150\",\n                    isSelected\n                      ? \"bg-zinc-800/80 border border-white/5 shadow-xs\"\n                      : \"border border-transparent\",\n                  )}\n                >\n                  <div className=\"flex items-center gap-3.5 min-w-0\">\n                    <div\n                      className={cn(\n                        \"size-5 rounded-md border flex items-center justify-center shrink-0 transition-colors\",\n                        isSelected\n                          ? \"bg-zinc-200 border-zinc-200 text-zinc-950\"\n                          : \"border-zinc-700 bg-zinc-800/40 group-hover:border-zinc-600\",\n                      )}\n                    >\n                      {isSelected && (\n                        <Check\n                          size={12}\n                          strokeWidth={3}\n                          className=\"text-zinc-950\"\n                        />\n                      )}\n                    </div>\n\n                    <div className=\"size-10 rounded-xl overflow-hidden shrink-0 border border-white/10 flex items-center justify-center bg-zinc-800/90 text-zinc-400\">\n                      {file.type === \"image\" && file.thumbnail ? (\n                        <img\n                          src={file.thumbnail}\n                          alt={file.name}\n                          className=\"size-full object-cover\"\n                          loading=\"lazy\"\n                        />\n                      ) : file.type === \"pdf\" ? (\n                        <FileText size={18} className=\"text-zinc-300\" />\n                      ) : (\n                        <FileSpreadsheet size={18} className=\"text-zinc-300\" />\n                      )}\n                    </div>\n\n                    <div className=\"flex flex-col text-left min-w-0 truncate\">\n                      <span\n                        className={cn(\n                          \"text-sm font-medium leading-snug truncate\",\n                          isSelected ? \"text-zinc-100\" : \"text-zinc-200\",\n                        )}\n                      >\n                        {file.name}\n                      </span>\n                      <span className=\"text-xs text-zinc-400 truncate\">\n                        {file.author} · {file.date}\n                      </span>\n                    </div>\n                  </div>\n\n                  <div className=\"text-xs text-zinc-400 font-mono shrink-0 pl-3\">\n                    {file.size}\n                  </div>\n                </motion.div>\n              );\n            })}\n          </AnimatePresence>\n\n          {files.length === 0 && (\n            <motion.div\n              key=\"empty-state\"\n              initial={{ opacity: 0, scale: 0.96, y: 6 }}\n              animate={{ opacity: 1, scale: 1, y: 0 }}\n              exit={{ opacity: 0, scale: 0.96, y: -6 }}\n              transition={{ type: \"spring\", stiffness: 420, damping: 32 }}\n              className=\"flex flex-col items-center justify-center py-8 px-4 text-center select-none\"\n            >\n              <div className=\"size-10 rounded-2xl bg-zinc-800/60 border border-white/10 flex items-center justify-center text-zinc-400 mb-2.5\">\n                <Trash2 size={18} strokeWidth={1.75} />\n              </div>\n              <span className=\"text-sm font-medium text-zinc-200\">\n                All files deleted\n              </span>\n            </motion.div>\n          )}\n        </div>\n      </div>\n\n      <div className=\"mt-6 flex items-center justify-center\">\n        <div className=\"relative inline-flex items-center p-1 rounded-full bg-[#161618] border border-white/8 shadow-lg\">\n          <button\n            type=\"button\"\n            onClick={() => setOutcome(\"succeeds\")}\n            className={cn(\n              \"relative z-10 px-4 py-1 text-xs font-medium rounded-full cursor-pointer transition-colors duration-200 outline-none focus-visible:ring-1 focus-visible:ring-zinc-400\",\n              outcome === \"succeeds\"\n                ? \"text-white\"\n                : \"text-zinc-400 hover:text-zinc-200\",\n            )}\n          >\n            {outcome === \"succeeds\" && (\n              <motion.div\n                layoutId=\"outcome-slider\"\n                className=\"absolute inset-0 rounded-full bg-zinc-800 border border-white/10 shadow-xs -z-10\"\n                transition={{\n                  type: \"spring\",\n                  stiffness: 480,\n                  damping: 34,\n                  mass: 0.8,\n                }}\n              />\n            )}\n            Succeeds\n          </button>\n          <button\n            type=\"button\"\n            onClick={() => setOutcome(\"fails\")}\n            className={cn(\n              \"relative z-10 px-4 py-1 text-xs font-medium rounded-full cursor-pointer transition-colors duration-200 outline-none focus-visible:ring-1 focus-visible:ring-zinc-400\",\n              outcome === \"fails\"\n                ? \"text-white\"\n                : \"text-zinc-400 hover:text-zinc-200\",\n            )}\n          >\n            {outcome === \"fails\" && (\n              <motion.div\n                layoutId=\"outcome-slider\"\n                className=\"absolute inset-0 rounded-full bg-zinc-800 border border-white/10 shadow-xs -z-10\"\n                transition={{\n                  type: \"spring\",\n                  stiffness: 480,\n                  damping: 34,\n                  mass: 0.8,\n                }}\n              />\n            )}\n            Fails\n          </button>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport default DeleteSelectionShowcase;\n",
      "type": "registry:ui",
      "target": "@ui/delete-selection.tsx"
    }
  ],
  "type": "registry:ui",
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "title": "Confirm Morph"
}
