{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "press-depth",
  "type": "registry:ui",
  "title": "Press Depth",
  "description": "Any surface sinks, drops a pixel and flattens its shadow, then springs back.",
  "categories": [
    "motion"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://www.stealth.pm/r/tokens.json",
    "https://www.stealth.pm/r/cn.json",
    "https://www.stealth.pm/r/motion.json"
  ],
  "files": [
    {
      "path": "components/ui/press-depth.tsx",
      "content": "\"use client\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/cn\";\nimport { ease } from \"@/lib/motion\";\n\n/* -------------------------------------------------------------------------------------------------\n * usePress: is this surface being held down right now?\n * -----------------------------------------------------------------------------------------------*/\n\nconst BLOCKED = ':disabled, [aria-disabled=\"true\"], [data-disabled], [data-press-ignore]';\n// What each key activates natively. Enter never presses a checkbox; Space never follows a link.\nconst ON_ENTER = 'button, a[href], summary, input[type=\"button\"], input[type=\"submit\"], input[type=\"reset\"], [role=\"button\"], [role=\"link\"], [role=\"menuitem\"], [role=\"tab\"]';\nconst ON_SPACE = 'button, summary, input[type=\"button\"], input[type=\"submit\"], input[type=\"reset\"], input[type=\"checkbox\"], input[type=\"radio\"], [role=\"button\"], [role=\"checkbox\"], [role=\"radio\"], [role=\"switch\"], [role=\"tab\"], [role=\"menuitem\"], [role=\"option\"]';\n\nexport type UsePressOptions = {\n  /** Ignore every press. */\n  disabled?: boolean;\n  onPressedChange?: (pressed: boolean) => void;\n};\n\n/**\n * Tracks whether a surface is held by a pointer or a key. Dragging off lets go and\n * dragging back on presses again, the same way a native button decides whether\n * the release counts as a click. A touch that becomes a scroll lets go at once.\n */\nexport function usePress(ref: React.RefObject<HTMLElement | null>, { disabled = false, onPressedChange }: UsePressOptions = {}) {\n  const [pressed, setPressed] = useState(false);\n  const notify = useRef(onPressedChange);\n  useEffect(() => {\n    notify.current = onPressedChange;\n  });\n\n  useEffect(() => {\n    const el = ref.current;\n    if (!el || disabled) return;\n\n    let current = false;\n    const set = (next: boolean) => {\n      if (next === current) return;\n      current = next;\n      setPressed(next);\n      notify.current?.(next);\n    };\n    const owns = (target: EventTarget | null) => {\n      if (!(target instanceof Element) || !el.contains(target)) return false;\n      if (target.closest(BLOCKED)) return false;\n      // A pressable surface nested inside this one takes the press for itself.\n      const nearest = target.closest(\"[data-press-depth]\");\n      if (nearest && nearest !== el && el.contains(nearest)) return false;\n      return true;\n    };\n\n    /* Pointer: armed from down to up, pressed only while over the surface. */\n    let armed = false;\n    const disarm = () => {\n      armed = false;\n      set(false);\n      window.removeEventListener(\"pointerup\", disarm);\n      window.removeEventListener(\"pointercancel\", disarm);\n    };\n    const onDown = (e: PointerEvent) => {\n      if (e.button !== 0 || !owns(e.target)) return;\n      armed = true;\n      set(true);\n      window.addEventListener(\"pointerup\", disarm);\n      window.addEventListener(\"pointercancel\", disarm);\n    };\n    const onLeave = () => armed && set(false);\n    const onEnter = (e: PointerEvent) => armed && e.buttons & 1 && set(true);\n\n    /* Keyboard: held from keydown to keyup of the key that activates the focused control. */\n    let key: string | null = null;\n    const onKeyDown = (e: KeyboardEvent) => {\n      if (e.repeat || key || e.metaKey || e.ctrlKey || e.altKey || !owns(e.target)) return;\n      const target = e.target as Element;\n      if ((e.key === \"Enter\" && target.matches(ON_ENTER)) || (e.key === \" \" && target.matches(ON_SPACE))) {\n        key = e.key;\n        set(true);\n      }\n    };\n    const onKeyUp = (e: KeyboardEvent) => {\n      if (e.key !== key) return;\n      key = null;\n      set(false);\n    };\n    const onFocusOut = () => {\n      key = null;\n      if (!armed) set(false);\n    };\n\n    el.addEventListener(\"pointerdown\", onDown);\n    el.addEventListener(\"pointerleave\", onLeave);\n    el.addEventListener(\"pointerenter\", onEnter);\n    el.addEventListener(\"keydown\", onKeyDown);\n    el.addEventListener(\"keyup\", onKeyUp);\n    el.addEventListener(\"focusout\", onFocusOut);\n    return () => {\n      disarm();\n      el.removeEventListener(\"pointerdown\", onDown);\n      el.removeEventListener(\"pointerleave\", onLeave);\n      el.removeEventListener(\"pointerenter\", onEnter);\n      el.removeEventListener(\"keydown\", onKeyDown);\n      el.removeEventListener(\"keyup\", onKeyUp);\n      el.removeEventListener(\"focusout\", onFocusOut);\n    };\n  }, [ref, disabled]);\n\n  return disabled ? false : pressed;\n}\n\n/* -------------------------------------------------------------------------------------------------\n * PressDepth\n * -----------------------------------------------------------------------------------------------*/\n\nexport type PressDepthDepth = \"subtle\" | \"default\" | \"deep\";\n\nexport type PressDepthProps = Omit<\n  React.ComponentProps<\"div\">,\n  \"onDrag\" | \"onDragStart\" | \"onDragEnd\" | \"onAnimationStart\" | \"onAnimationEnd\"\n> & {\n  /** How far the surface's edges travel in: about 1.25px, 2px or 3px, whatever its size. */\n  depth?: PressDepthDepth;\n  /** Draws the surface's shadow on the wrapper so the press can flatten it. Use \"flat\" for surfaces with no shadow. */\n  elevation?: \"raised\" | \"flat\";\n  /** Controls the press: true holds it down (e.g. while its shortcut is held), false holds it up. Leave unset to follow the pointer and keys. */\n  pressed?: boolean;\n  /** Called when a pointer or key presses or releases the surface. */\n  onPressedChange?: (pressed: boolean) => void;\n  /** Stops the press. Targets that are disabled or inside `[data-press-ignore]` never press it either. */\n  disabled?: boolean;\n};\n\nconst travel: Record<PressDepthDepth, { edge: number; drop: number }> = {\n  subtle: { edge: 1.25, drop: 0.5 },\n  default: { edge: 2, drop: 1 },\n  deep: { edge: 3, drop: 1.5 },\n};\n\n// Quick in, sprung out. The release overshoots by well under a pixel: felt, not seen.\nconst pressIn = { duration: 0.09, ease: ease.out };\nconst release = { type: \"spring\", stiffness: 500, damping: 21, mass: 0.6 } as const;\n\n/**\n * Gives whatever it wraps a physical press: it sinks, drops a pixel and its\n * shadow flattens, then springs back on release. Scale is worked out from the\n * surface's size so a chip and a wide card move their edges the same distance.\n */\nexport function PressDepth({\n  depth = \"default\",\n  elevation = \"raised\",\n  pressed: pressedProp,\n  onPressedChange,\n  disabled = false,\n  className,\n  children,\n  ref,\n  ...rest\n}: PressDepthProps) {\n  const el = useRef<HTMLDivElement | null>(null);\n  const reduce = !!useReducedMotion();\n  const [size, setSize] = useState(0);\n  const tracked = usePress(el, { disabled, onPressedChange });\n  // Controlled when `pressed` is set; otherwise it follows the pointer and keys.\n  const pressed = !disabled && (pressedProp ?? tracked);\n\n  const setRef = useCallback(\n    (node: HTMLDivElement | null) => {\n      el.current = node;\n      if (typeof ref === \"function\") return ref(node);\n      if (ref) ref.current = node;\n    },\n    [ref],\n  );\n\n  // The untransformed size, kept current so the press never measures in the hot path.\n  useEffect(() => {\n    const node = el.current;\n    if (!node) return;\n    const ro = new ResizeObserver(([entry]) => {\n      const box = entry.borderBoxSize?.[0];\n      setSize(Math.max(box?.inlineSize ?? node.offsetWidth, box?.blockSize ?? node.offsetHeight));\n    });\n    ro.observe(node);\n    return () => ro.disconnect();\n  }, []);\n\n  const { edge, drop } = travel[depth];\n  const scale = size ? Math.min(0.992, Math.max(0.94, 1 - (edge * 2) / size)) : 0.97;\n  const move = reduce ? { scale: 1, y: 0 } : pressed ? { scale, y: drop } : { scale: 1, y: 0 };\n\n  return (\n    <motion.div\n      ref={setRef}\n      data-press-depth=\"\"\n      data-pressed={pressed ? \"\" : undefined}\n      data-disabled={disabled ? \"\" : undefined}\n      className={cn(\"relative isolate [-webkit-tap-highlight-color:transparent]\", className)}\n      initial={false}\n      animate={move}\n      transition={pressed ? pressIn : release}\n      {...rest}\n    >\n      {elevation === \"raised\" && (\n        <motion.span\n          aria-hidden\n          className=\"pointer-events-none absolute inset-0 -z-10 rounded-[inherit] shadow-[var(--shadow)]\"\n          initial={false}\n          animate={{ opacity: pressed ? 0.3 : 1 }}\n          transition={pressed ? { duration: 0.09, ease: \"linear\" } : { duration: 0.26, ease: ease.out }}\n        />\n      )}\n      {children}\n      {/* Without movement, the press still has to register: a faint wash while held.\n          Always in the tree (hidden unless reduced) so server and client markup match. */}\n      <span\n        aria-hidden\n        className={cn(\n          \"pointer-events-none absolute inset-0 rounded-[inherit] bg-fg/[0.05] transition-opacity\",\n          reduce && pressed ? \"opacity-100 duration-75\" : \"opacity-0 duration-200\",\n        )}\n      />\n    </motion.div>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "docs": "https://www.stealth.pm/ui/press-depth"
}
