{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "slide-to-unlock",
  "type": "registry:component",
  "title": "Slide to Unlock",
  "author": "ncdai <dai@chanhdai.com>",
  "description": "A sleek, interactive slider inspired by the classic iPhone OS 'slide to unlock' gesture.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "@ncdai/utils",
    "@ncdai/shimmering-text"
  ],
  "files": [
    {
      "path": "src/registry/slide-to-unlock/slide-to-unlock.tsx",
      "content": "\"use client\";\n\nimport {\n  animate,\n  motion,\n  type MotionValue,\n  useMotionValue,\n  useTransform,\n} from \"motion/react\";\nimport {\n  type ComponentPropsWithoutRef,\n  createContext,\n  useCallback,\n  useContext,\n  useRef,\n  useState,\n} from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype SlideToUnlockContextValue = {\n  x: MotionValue<number>;\n  trackRef: React.RefObject<HTMLDivElement | null>;\n  isDragging: boolean;\n  handleWidth: number;\n  textOpacity: MotionValue<number>;\n  onDragStart: () => void;\n  onDragEnd: () => void;\n};\n\nconst SlideToUnlockContext = createContext<SlideToUnlockContextValue | null>(\n  null\n);\n\nfunction useSlideToUnlock() {\n  const context = useContext(SlideToUnlockContext);\n  if (!context) {\n    throw new Error(\n      `SlideToUnlock components must be used within SlideToUnlock`\n    );\n  }\n  return context;\n}\n\nexport type SlideToUnlockRootProps = React.ComponentProps<\"div\"> & {\n  handleWidth?: number;\n  onUnlock?: () => void;\n};\n\nexport function SlideToUnlock({\n  className,\n  handleWidth = 56,\n  children,\n  onUnlock,\n  ...props\n}: SlideToUnlockRootProps) {\n  const trackRef = useRef<HTMLDivElement>(null);\n  const [isDragging, setIsDragging] = useState(false);\n  const x = useMotionValue(0);\n\n  const fadeDistance = handleWidth;\n  const textOpacity = useTransform(x, [0, fadeDistance], [1, 0]);\n\n  const handleDragStart = useCallback(() => {\n    setIsDragging(true);\n  }, []);\n\n  const handleDragEnd = useCallback(() => {\n    setIsDragging(false);\n\n    const trackWidth = trackRef.current?.offsetWidth || 0;\n    const maxX = trackWidth - handleWidth;\n\n    if (x.get() >= maxX) {\n      onUnlock?.();\n    } else {\n      animate(x, 0, { type: \"spring\", bounce: 0, duration: 0.25 });\n    }\n  }, [x, onUnlock, handleWidth]);\n\n  return (\n    <SlideToUnlockContext.Provider\n      value={{\n        x,\n        trackRef,\n        isDragging,\n        handleWidth,\n        textOpacity,\n        onDragStart: handleDragStart,\n        onDragEnd: handleDragEnd,\n      }}\n    >\n      <div\n        data-slot=\"slide-to-unlock\"\n        className={cn(\n          \"w-[216px] rounded-xl bg-zinc-100 p-1 shadow-inner ring ring-black/5 ring-inset dark:bg-zinc-900 dark:ring-white/10\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    </SlideToUnlockContext.Provider>\n  );\n}\n\nexport type SlideToUnlockTrackProps = React.ComponentProps<\"div\">;\n\nexport function SlideToUnlockTrack({\n  className,\n  children,\n  ...props\n}: SlideToUnlockTrackProps) {\n  const { trackRef } = useSlideToUnlock();\n\n  return (\n    <div\n      ref={trackRef}\n      data-slot=\"track\"\n      className={cn(\n        \"relative flex h-10 items-center justify-center\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </div>\n  );\n}\n\nexport type SlideToUnlockTextProps = Omit<\n  ComponentPropsWithoutRef<typeof motion.div>,\n  \"children\"\n> & {\n  children:\n    | React.ReactNode\n    | ((props: { isDragging: boolean }) => React.ReactNode);\n};\n\nexport function SlideToUnlockText({\n  className,\n  children,\n  style,\n  ...props\n}: SlideToUnlockTextProps) {\n  const { handleWidth, textOpacity, isDragging } = useSlideToUnlock();\n\n  return (\n    <motion.div\n      data-slot=\"text\"\n      data-dragging={isDragging}\n      className={cn(\"pl-1 text-lg font-medium\", className)}\n      style={{ marginLeft: handleWidth, opacity: textOpacity, ...style }}\n      {...props}\n    >\n      {typeof children === \"function\" ? children({ isDragging }) : children}\n    </motion.div>\n  );\n}\n\nexport type SlideToUnlockHandleProps = ComponentPropsWithoutRef<\n  typeof motion.div\n>;\n\nexport function SlideToUnlockHandle({\n  className,\n  children,\n  style,\n  ...props\n}: SlideToUnlockHandleProps) {\n  const {\n    x,\n    trackRef,\n    onDragStart,\n    onDragEnd,\n    handleWidth: width,\n  } = useSlideToUnlock();\n\n  return (\n    <motion.div\n      data-slot=\"handle\"\n      className={cn(\n        \"absolute top-0 left-0 flex h-10 cursor-grab items-center justify-center rounded-lg bg-white text-zinc-400 shadow-sm active:cursor-grabbing\",\n        \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-6\",\n        className\n      )}\n      style={{ width, x, ...style }}\n      drag=\"x\"\n      dragDirectionLock\n      dragConstraints={trackRef}\n      dragElastic={0}\n      dragMomentum={false}\n      onDragStart={onDragStart}\n      onDragEnd={onDragEnd}\n      {...props}\n    >\n      {children ?? (\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          viewBox=\"0 0 256 256\"\n          aria-hidden\n        >\n          <path\n            d=\"M237.66,133.66l-96,96A8,8,0,0,1,128,224V184H48a16,16,0,0,1-16-16V88A16,16,0,0,1,48,72h80V32a8,8,0,0,1,13.66-5.66l96,96A8,8,0,0,1,237.66,133.66Z\"\n            fill=\"currentColor\"\n          />\n        </svg>\n      )}\n    </motion.div>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "docs": "https://chanhdai.com/components/slide-to-unlock"
}