{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "slide-to-unlock",
  "title": "Slide to Unlock",
  "author": "nghia.ly <physicalmeans@gmail.com>",
  "description": "Interactive slider inspired by the classic iPhone 'slide to unlock' gesture.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://lyminhnghia.github.io/r/shimmering-text.json"
  ],
  "files": [
    {
      "path": "src/registry/components/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 type { ComponentProps, ComponentPropsWithoutRef, JSX } from \"react\"\nimport { createContext, useCallback, useContext, useRef, useState } 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 = ComponentProps<\"div\"> & {\n  /**\n   * Width of the drag handle in pixels.\n   * @defaultValue 56\n   * */\n  handleWidth?: number\n  /** Called when the handle is dragged fully to the end. */\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-54 rounded-xl bg-muted p-1 shadow-inner ring-1 ring-foreground/10 ring-inset\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    </SlideToUnlockContext.Provider>\n  )\n}\n\nexport type SlideToUnlockTrackProps = 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  /**\n   * Accepts a render function as `children` to react to the dragging state.\n   *\n   * @example\n   * ```tsx\n   * <SlideToUnlockText>\n   *   {({ isDragging }) => <span>{isDragging ? \"Release...\" : \"Slide to unlock\"}</span>}\n   * </SlideToUnlockText>\n   * ```\n   */\n  children: JSX.Element | ((props: { isDragging: boolean }) => JSX.Element)\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      dragConstraints={trackRef}\n      dragElastic={0}\n      dragMomentum={false}\n      onDragStart={onDragStart}\n      onDragEnd={onDragEnd}\n      {...props}\n    >\n      {children ?? (\n        <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" aria-hidden>\n          <path\n            d=\"M24 12 12.75 3v4.696H0v8.608h12.75V21z\"\n            fill=\"currentColor\"\n          />\n        </svg>\n      )}\n    </motion.div>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "docs": "https://lyminhnghia.github.io/components/slide-to-unlock",
  "type": "registry:component"
}