{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "native-notch-shadcnui",
  "type": "registry:component",
  "title": "Native Notch",
  "description": "Dynamic Island-inspired notch component with smooth spring animations, draggable physics, and expandable content area",
  "dependencies": [
    "framer-motion",
    "react"
  ],
  "files": [
    {
      "path": "@uitripled/react-shadcn/src/components/native/native-notch-shadcnui.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport {\n  AnimatePresence,\n  animate,\n  motion,\n  MotionConfig,\n  useMotionValue,\n  useReducedMotion,\n  useSpring,\n  useTransform,\n} from \"framer-motion\";\nimport { X } from \"lucide-react\";\nimport type React from \"react\";\nimport { useEffect, useRef, useState } from \"react\";\n\n/**\n * Size variants for the notch component\n */\nexport type NotchSize = \"sm\" | \"md\" | \"lg\";\n\n/**\n * Position configuration for initial placement\n */\nexport interface NotchPosition {\n  /**\n   * Top offset in pixels\n   */\n  top?: number;\n  /**\n    * Bottom offset in pixels\n    */\n  bottom?: number;\n  /**\n   * Horizontal alignment\n   */\n  align?: \"left\" | \"center\" | \"right\";\n}\n\nexport interface NativeNotchProps {\n  /**\n   * Content to display when the notch is open\n   */\n  children?: React.ReactNode;\n  /**\n   * Custom content/icon to display when the notch is closed\n   */\n  collapsedIcon?: React.ReactNode;\n  /**\n   * Size variant\n   * @default \"md\"\n   */\n  size?: NotchSize;\n  /**\n   * Initial position configuration\n   */\n  position?: NotchPosition;\n  /**\n   * Whether the notch is draggable\n   * @default true\n   */\n  draggable?: boolean;\n  /**\n   * Default expanded state\n   * @default false\n   */\n  defaultExpanded?: boolean;\n  /**\n   * Controlled expanded state\n   */\n  expanded?: boolean;\n  /**\n   * Callback when expanded state changes\n   */\n  onExpandedChange?: (expanded: boolean) => void;\n  /**\n   * Callback when notch is clicked\n   */\n  onClick?: (e: React.MouseEvent) => void;\n  /**\n   * Additional CSS classes\n   */\n  className?: string;\n}\n\nconst sizeVariants = {\n  sm: {\n    collapsed: { width: 40, height: 40, radius: 20 },\n    expanded: { width: 280, height: 160, radius: 24 },\n  },\n  md: {\n    collapsed: { width: 48, height: 48, radius: 24 },\n    expanded: { width: 340, height: 200, radius: 28 },\n  },\n  lg: {\n    collapsed: { width: 56, height: 56, radius: 28 },\n    expanded: { width: 400, height: 240, radius: 32 },\n  },\n};\n\nconst positionStyles = {\n  left: \"left-8\",\n  center: \"left-1/2 -translate-x-1/2\",\n  right: \"right-8\",\n};\n\nexport function NativeNotch({\n  children,\n  collapsedIcon,\n  size = \"md\",\n  position = { top: 32, align: \"center\" },\n  draggable = true,\n  defaultExpanded = false,\n  expanded: controlledExpanded,\n  onExpandedChange,\n  onClick,\n  className,\n}: NativeNotchProps) {\n  const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);\n  const [isDragging, setIsDragging] = useState(false);\n  const notchRef = useRef<HTMLDivElement>(null);\n  const closeButtonRef = useRef<HTMLButtonElement>(null);\n  const wasExpanded = useRef(false);\n  const reduce = useReducedMotion() ?? false;\n\n  const isControlled = controlledExpanded !== undefined;\n  const isExpanded = isControlled ? controlledExpanded : internalExpanded;\n\n  const setExpanded = (value: boolean) => {\n    if (!isControlled) {\n      setInternalExpanded(value);\n    }\n    onExpandedChange?.(value);\n  };\n\n  const sizeConfig = sizeVariants[size];\n\n  // Motion values for drag physics\n  const x = useMotionValue(0);\n  const y = useMotionValue(0);\n\n  const springConfig = { stiffness: 150, damping: 20, mass: 0.8 };\n  const springX = useSpring(x, springConfig);\n  const springY = useSpring(y, springConfig);\n\n  // Rotation and scale based on drag velocity/position\n  const rotate = useTransform(springX, [-200, 200], [-5, 5]);\n  const scale = useTransform(\n    [springX, springY],\n    ([latestX, latestY]: number[]) => {\n      const distance = Math.sqrt(latestX * latestX + latestY * latestY);\n      return 1 - Math.min(distance / 1500, 0.08);\n    }\n  );\n\n  // Motion values for dimensions\n  const notchWidth = useMotionValue(sizeConfig.collapsed.width);\n  const notchHeight = useMotionValue(sizeConfig.collapsed.height);\n  const notchRadius = useMotionValue(sizeConfig.collapsed.radius);\n\n  const springWidth = useSpring(notchWidth, { stiffness: 400, damping: 35, mass: 0.6 });\n  const springHeight = useSpring(notchHeight, { stiffness: 300, damping: 30, mass: 0.8 });\n  const springRadius = useSpring(notchRadius, { stiffness: 350, damping: 30, mass: 0.6 });\n\n  useEffect(() => {\n    // Reduced motion: snap dimensions instantly, no staged choreography.\n    if (reduce) {\n      const target = isExpanded ? sizeConfig.expanded : sizeConfig.collapsed;\n      notchWidth.jump(target.width);\n      notchHeight.jump(target.height);\n      notchRadius.jump(target.radius);\n      return;\n    }\n    if (isExpanded) {\n      animate(notchWidth, sizeConfig.expanded.width, { type: \"spring\", stiffness: 400, damping: 35, mass: 0.5 });\n      animate(notchRadius, sizeConfig.expanded.radius, { type: \"spring\", stiffness: 350, damping: 30, mass: 0.5 });\n      const timeout = setTimeout(() => {\n        animate(notchHeight, sizeConfig.expanded.height, { type: \"spring\", stiffness: 300, damping: 28, mass: 0.6 });\n      }, 80);\n      return () => clearTimeout(timeout);\n    } else {\n      animate(notchHeight, sizeConfig.collapsed.height, { type: \"spring\", stiffness: 350, damping: 30, mass: 0.5 });\n      const timeout = setTimeout(() => {\n        animate(notchWidth, sizeConfig.collapsed.width, { type: \"spring\", stiffness: 400, damping: 35, mass: 0.5 });\n        animate(notchRadius, sizeConfig.collapsed.radius, { type: \"spring\", stiffness: 350, damping: 30, mass: 0.5 });\n      }, 60);\n      return () => clearTimeout(timeout);\n    }\n  }, [isExpanded, notchWidth, notchHeight, notchRadius, sizeConfig, reduce]);\n\n  // Move focus into the panel on expand, back to the notch on collapse.\n  useEffect(() => {\n    if (isExpanded) {\n      wasExpanded.current = true;\n      closeButtonRef.current?.focus();\n    } else if (wasExpanded.current) {\n      notchRef.current?.focus();\n    }\n  }, [isExpanded]);\n\n  const handlePointerDown = (e: React.PointerEvent) => {\n    if (isExpanded || !draggable) return;\n    setIsDragging(true);\n    (e.target as HTMLElement).setPointerCapture(e.pointerId);\n  };\n\n  const handlePointerMove = (e: React.PointerEvent) => {\n    if (!isDragging || isExpanded || !draggable) return;\n\n    const centerX = window.innerWidth / 2;\n    const centerY = (position.top || 32) + 24;\n\n    const newX = e.clientX - centerX;\n    const newY = e.clientY - centerY;\n\n    const maxX = window.innerWidth / 2 - 40;\n    const maxY = window.innerHeight - 80;\n    const minY = -20;\n\n    x.set(Math.max(-maxX, Math.min(maxX, newX)));\n    y.set(Math.max(minY, Math.min(maxY, newY)));\n  };\n\n  const handlePointerUp = (e: React.PointerEvent) => {\n    if (!isDragging) return;\n    setIsDragging(false);\n    (e.target as HTMLElement).releasePointerCapture(e.pointerId);\n\n    // Snap back\n    animate(x, 0, { type: \"spring\", stiffness: 200, damping: 25 });\n    animate(y, 0, { type: \"spring\", stiffness: 200, damping: 25 });\n  };\n\n  const handleClick = (e: React.MouseEvent) => {\n    if (isDragging) return;\n    if (Math.abs(x.get()) > 5 || Math.abs(y.get()) > 5) return;\n\n    onClick?.(e);\n    if (!isExpanded) {\n      setExpanded(true);\n    }\n  };\n\n  return (\n    <MotionConfig reducedMotion=\"user\">\n    <motion.div\n      ref={notchRef}\n      role={!isExpanded ? \"button\" : undefined}\n      tabIndex={!isExpanded ? 0 : -1}\n      aria-expanded={isExpanded}\n      aria-label={!isExpanded ? \"Open notch\" : undefined}\n      className={cn(\n        \"fixed z-50 touch-none\",\n        !isExpanded && \"cursor-pointer\",\n        \"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background rounded-full\",\n        positionStyles[position.align || \"center\"],\n        className\n      )}\n      style={{\n        top: position.top,\n        bottom: position.bottom,\n        x: springX,\n        y: springY,\n        rotate: isExpanded || reduce ? 0 : rotate,\n        scale: isExpanded || reduce ? 1 : scale,\n      }}\n      initial={false}\n      onPointerDown={handlePointerDown}\n      onPointerMove={handlePointerMove}\n      onPointerUp={handlePointerUp}\n      onPointerCancel={handlePointerUp}\n      onClick={handleClick}\n      onKeyDown={(e) => {\n        if (!isExpanded && (e.key === \"Enter\" || e.key === \" \")) {\n          e.preventDefault();\n          setExpanded(true);\n        }\n        if (isExpanded && e.key === \"Escape\") {\n          setExpanded(false);\n        }\n      }}\n    >\n      <motion.div\n        className=\"relative bg-background text-foreground border border-accent/50 shadow-2xl overflow-hidden\"\n        style={{\n          width: springWidth,\n          height: springHeight,\n          borderRadius: springRadius,\n        }}\n      >\n        <AnimatePresence mode=\"wait\">\n          {!isExpanded ? (\n            <motion.div\n              key=\"collapsed\"\n              initial={{ opacity: 0, scale: 0.8 }}\n              animate={{ opacity: 1, scale: 1 }}\n              exit={{ opacity: 0, scale: 0.8 }}\n              transition={{ duration: 0.2 }}\n              className=\"absolute inset-0 flex items-center justify-center p-1\"\n            >\n              {collapsedIcon || <div className=\"w-1.5 h-1.5 rounded-full bg-foreground/40\" />}\n            </motion.div>\n          ) : (\n            <motion.div\n              key=\"expanded\"\n              initial={false}\n              animate={{ opacity: 1, filter: \"blur(0px)\" }}\n              exit={{ opacity: 0, filter: \"blur(10px)\" }}\n              transition={{ duration: 0.2 }}\n              className=\"absolute inset-0 p-4 flex flex-col\"\n            >\n              <div className=\"absolute top-2 right-2 z-10\">\n                <motion.button\n                  ref={closeButtonRef}\n                  type=\"button\"\n                  aria-label=\"Close notch\"\n                  initial={{ scale: 0.5, opacity: 0, rotate: -90 }}\n                  animate={{ scale: 1, opacity: 1, rotate: 0 }}\n                  exit={{ scale: 0.5, opacity: 0, rotate: -90 }}\n                  transition={{ type: \"spring\", stiffness: 300, damping: 20 }}\n                  onClick={(e) => {\n                    e.stopPropagation();\n                    setExpanded(false);\n                  }}\n                  className=\"relative p-1 rounded-full hover:bg-accent/10 transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring after:absolute after:-inset-2\"\n                >\n                  <X\n                    aria-hidden=\"true\"\n                    className=\"w-4 h-4 text-muted-foreground hover:text-foreground\"\n                  />\n                </motion.button>\n              </div>\n              <motion.div\n                className=\"w-full h-full mt-2\"\n                initial={{ opacity: 0, y: 10 }}\n                animate={{ opacity: 1, y: 0 }}\n                transition={{ delay: 0.15 }}\n              >\n                {children}\n              </motion.div>\n            </motion.div>\n          )}\n        </AnimatePresence>\n      </motion.div>\n    </motion.div>\n    </MotionConfig>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/uitripled/native-notch-shadcnui.tsx"
    }
  ]
}