{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "components-theme-switch",
  "title": "Theme Switch",
  "description": "A small theme toggle button with motion animations.",
  "dependencies": [
    "class-variance-authority",
    "motion",
    "@tabler/icons-react",
    "next-themes"
  ],
  "registryDependencies": [
    "@azemmur/primitives-button"
  ],
  "files": [
    {
      "path": "registry/components/azemmur/theme-switch/index.tsx",
      "content": "// Copyright (c) 2026 raouf.codes - Azemmur\n\n'use client';\nimport { IconSunLow, IconMoon } from '@tabler/icons-react';\nimport { cn } from '@/lib/utils';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { motion, AnimatePresence } from 'motion/react';\nimport { useTheme } from 'next-themes';\nimport { useCallback, useSyncExternalStore } from 'react';\nimport { Button as ButtonPrimitive } from '@/components/azemmur/components/primitives/button';\n\nconst emptySubscribe = () => () => {};\nconst getSnapshot = () => true;\nconst getServerSnapshot = () => false;\n\nconst themeSwitchVariants = cva(\n  [\n    'inline-flex items-center justify-center',\n    'select-none touch-manipulation',\n    'transition-colors',\n    '[&_svg]:pointer-events-none [&_svg]:shrink-0',\n    'disabled:pointer-events-none disabled:opacity-50',\n  ].join(' '),\n  {\n    variants: {\n      intent: {\n        primary: 'bg-primary text-primary border-primary',\n        accent: 'bg-accent text-accent border-accent',\n        secondary: 'bg-secondary text-secondary border-secondary',\n      },\n      size: {\n        sm: 'h-6 w-6 [&_svg]:size-4',\n        md: 'h-8 w-8 [&_svg]:size-5',\n        lg: 'h-10 w-10 [&_svg]:size-6',\n      },\n      styling: {\n        ghost: 'bg-transparent',\n        solid: 'border-none',\n        outline: 'border border-2 bg-transparent hover:bg-current/20',\n      },\n      shape: {\n        rounded: 'rounded-md',\n        pill: 'rounded-full',\n        sharp: 'rounded-none',\n      },\n      elevation: {\n        raised: 'shadow-sm hover:shadow-md active:shadow-none',\n        floating: 'shadow-md hover:shadow-lg active:shadow-sm',\n      },\n    },\n    compoundVariants: [\n      {\n        intent: 'primary',\n        styling: 'solid',\n        className: 'text-primary-foreground',\n      },\n      {\n        intent: 'accent',\n        styling: 'solid',\n        className: 'text-accent-foreground',\n      },\n      {\n        intent: 'secondary',\n        styling: 'solid',\n        className: 'text-secondary-foreground',\n      },\n      {\n        styling: 'ghost',\n        className: 'shadow-none hover:shadow-none active:shadow-none',\n      },\n    ],\n    defaultVariants: {\n      intent: 'primary',\n      size: 'md',\n      styling: 'ghost',\n      shape: 'pill',\n      elevation: 'raised',\n    },\n  },\n);\n\ntype ThemeSwitchProps = VariantProps<typeof themeSwitchVariants> & {\n  className?: string;\n};\n\nfunction ThemeSwitch({\n  className,\n  size,\n  styling,\n  intent,\n  shape,\n  elevation,\n  ...props\n}: ThemeSwitchProps) {\n  const mounted = useSyncExternalStore(\n    emptySubscribe,\n    getSnapshot,\n    getServerSnapshot,\n  );\n  const { theme, setTheme, resolvedTheme } = useTheme();\n\n  const currentTheme = resolvedTheme ?? theme;\n  const isDark = currentTheme === 'dark';\n\n  const handleToggle = useCallback(() => {\n    setTheme(isDark ? 'light' : 'dark');\n  }, [setTheme, isDark]);\n\n  const iconVariants = {\n    initial: { scale: 0, rotate: -360 },\n    animate: { scale: 1, rotate: 0 },\n    exit: { scale: 0, rotate: 360 },\n    hover: { rotate: [0, 30, -30, 0], transition: { duration: 0.4 } },\n  };\n\n  return (\n    <ButtonPrimitive\n      aria-label={\n        mounted\n          ? isDark\n            ? 'Switch to Light Mode'\n            : 'Switch to Dark Mode'\n          : 'Toggle theme'\n      }\n      aria-pressed={mounted ? isDark : undefined}\n      className={cn(\n        themeSwitchVariants({ intent, size, styling, shape, elevation }),\n        className,\n      )}\n      onClick={handleToggle}\n      disabled={!mounted}\n      {...props}\n    >\n      {!mounted ? (\n        <div\n          className={cn(\n            themeSwitchVariants({ size }),\n            'animate-pulse rounded-full bg-current opacity-20',\n          )}\n        />\n      ) : (\n        <AnimatePresence mode=\"wait\" initial={false}>\n          <motion.div\n            key={isDark ? 'sun-icon' : 'moon-icon'}\n            initial=\"initial\"\n            animate=\"animate\"\n            exit=\"exit\"\n            whileHover=\"hover\"\n            variants={iconVariants}\n            transition={{\n              duration: 0.4,\n              rotate: { duration: 0.6, delay: 0.12 },\n            }}\n          >\n            {isDark ? <IconSunLow /> : <IconMoon />}\n          </motion.div>\n        </AnimatePresence>\n      )}\n    </ButtonPrimitive>\n  );\n}\n\nexport { ThemeSwitch, themeSwitchVariants, type ThemeSwitchProps };\n",
      "type": "registry:ui",
      "target": "components/azemmur/theme-switch.tsx"
    }
  ],
  "type": "registry:ui"
}