{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "theme-customizer",
  "type": "registry:ui",
  "title": "Theme Customizer",
  "description": "Floating theme and color picker with 5 different variants.",
  "dependencies": [
    "next-themes",
    "lucide-react"
  ],
  "files": [
    {
      "path": "registry/new-york/ui/theme-customizer.tsx",
      "content": "\"use client\"\n\nimport { ArrowLeft, Check, Monitor, Moon, Paintbrush, Sun } from \"lucide-react\"\nimport { MotionConfig, motion } from \"motion/react\"\nimport { useTheme } from \"next-themes\"\nimport * as React from \"react\"\nimport { BrushCleaning } from \"@/components/animate-ui/icons/brush-cleaning\"\nimport { ThemeToggle } from \"@/components/ui/theme-toggle\"\nimport { useClickOutside } from \"@/hooks/use-click-outside\"\nimport { cn } from \"@/lib/utils\"\n\nconst colors = [\n  { name: \"zinc\", class: \"bg-zinc-500\" },\n  { name: \"red\", class: \"bg-red-500\" },\n  { name: \"orange\", class: \"bg-orange-500\" },\n  { name: \"green\", class: \"bg-green-500\" },\n  { name: \"blue\", class: \"bg-blue-500\" },\n  { name: \"violet\", class: \"bg-violet-500\" },\n  { name: \"pink\", class: \"bg-pink-500\" },\n]\n\nfunction useThemeColor() {\n  const [activeColor, setActiveColor] = React.useState(\"green\")\n  const [mounted, setMounted] = React.useState(false)\n\n  React.useEffect(() => {\n    setMounted(true)\n    const saved = localStorage.getItem(\"theme-color\")\n    if (saved) setActiveColor(saved)\n  }, [])\n\n  const setColor = (colorName: string) => {\n    setActiveColor(colorName)\n    localStorage.setItem(\"theme-color\", colorName)\n    document.documentElement.setAttribute(\"data-theme-color\", colorName)\n  }\n\n  return { activeColor, setColor, mounted }\n}\n\n// Variant 1: Floating Pill with Popup\nexport function ThemeCustomizerPill({\n  className,\n  inline,\n}: {\n  className?: string\n  inline?: boolean\n}) {\n  const { activeColor, setColor, mounted } = useThemeColor()\n  const [showColors, setShowColors] = React.useState(false)\n  const containerRef = React.useRef<HTMLDivElement>(null)\n\n  useClickOutside(containerRef, () => {\n    setShowColors(false)\n  })\n\n  // Detect if using static positioning (inline mode)\n  const isInline = inline || className?.includes(\"static\")\n\n  if (!mounted) {\n    return (\n      <nav\n        className={cn(\n          \"fixed bottom-5 left-1/2 z-50 flex -translate-x-1/2 gap-1 rounded-full border bg-background/70 p-1 shadow-lg backdrop-blur-xl\",\n          className,\n        )}\n      >\n        <div className=\"size-9 animate-pulse rounded-full bg-muted\" />\n        <div className=\"size-9 animate-pulse rounded-full bg-muted\" />\n      </nav>\n    )\n  }\n\n  return (\n    <div ref={containerRef} className={isInline ? \"relative\" : undefined}>\n      <nav\n        className={cn(\n          \"fixed bottom-5 left-1/2 z-50 flex -translate-x-1/2 items-center gap-1 rounded-full border bg-background/70 p-1 shadow-lg backdrop-blur-xl transition-all\",\n          className,\n        )}\n      >\n        <ThemeToggle size={20} className=\"size-9 rounded-full p-2\" />\n        <button\n          className=\"flex size-9 items-center justify-center rounded-full transition-colors hover:bg-accent cursor-pointer\"\n          onClick={() => setShowColors(!showColors)}\n        >\n          <BrushCleaning size={20} animateOnHover />\n        </button>\n      </nav>\n\n      {showColors && (\n        <div\n          className={cn(\n            \"z-50 flex justify-center gap-1.5 rounded-xl border bg-background/70 p-2 shadow-lg backdrop-blur-xl animate-in fade-in slide-in-from-bottom-2\",\n            isInline\n              ? \"absolute bottom-full left-0 sm:left-auto sm:right-0 mb-2\"\n              : \"fixed bottom-20 left-1/2 -translate-x-1/2 gap-2 rounded-full px-3 py-2\",\n          )}\n        >\n          {colors.map((c) => (\n            <button\n              key={c.name}\n              onClick={() => setColor(c.name)}\n              className={cn(\n                \"relative rounded-full transition-transform hover:scale-110 cursor-pointer\",\n                isInline ? \"size-6\" : \"size-7\",\n                c.class,\n              )}\n            >\n              {activeColor === c.name && (\n                <Check\n                  className={cn(\n                    \"absolute inset-0 m-auto text-white\",\n                    isInline ? \"size-3\" : \"size-4\",\n                  )}\n                />\n              )}\n            </button>\n          ))}\n        </div>\n      )}\n    </div>\n  )\n}\n\n// Variant 2: Inline Bar with All Options\nexport function ThemeCustomizerBar({ className }: { className?: string }) {\n  const { theme, setTheme } = useTheme()\n  const { activeColor, setColor, mounted } = useThemeColor()\n\n  if (!mounted) {\n    return (\n      <div\n        className={cn(\n          \"flex items-center gap-4 rounded-full border bg-background/70 px-4 py-2 shadow-lg backdrop-blur-xl\",\n          className,\n        )}\n      >\n        <div className=\"flex gap-1\">\n          {[1, 2, 3].map((i) => (\n            <div key={i} className=\"size-8 animate-pulse rounded-md bg-muted\" />\n          ))}\n        </div>\n        <div className=\"h-6 w-px bg-border\" />\n        <div className=\"flex gap-1.5\">\n          {[1, 2, 3, 4, 5].map((i) => (\n            <div\n              key={i}\n              className=\"size-6 animate-pulse rounded-full bg-muted\"\n            />\n          ))}\n        </div>\n      </div>\n    )\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center gap-4 rounded-full border bg-background/70 px-4 py-2 shadow-lg backdrop-blur-xl\",\n        className,\n      )}\n    >\n      <div className=\"flex gap-1\">\n        {[\n          { value: \"light\", icon: Sun },\n          { value: \"dark\", icon: Moon },\n          { value: \"system\", icon: Monitor },\n        ].map(({ value, icon: Icon }) => (\n          <button\n            key={value}\n            onClick={() => setTheme(value)}\n            className={cn(\n              \"flex size-8 items-center justify-center rounded-md transition-colors\",\n              theme === value ? \"bg-primary text-white\" : \"hover:bg-accent\",\n            )}\n          >\n            <Icon className=\"size-4\" />\n          </button>\n        ))}\n      </div>\n\n      <div className=\"h-6 w-px bg-border\" />\n\n      <div className=\"flex gap-1.5\">\n        {colors.map((c) => (\n          <button\n            key={c.name}\n            onClick={() => setColor(c.name)}\n            className={cn(\n              \"size-6 rounded-full transition-all hover:scale-110\",\n              c.class,\n              activeColor === c.name &&\n                \"ring-2 ring-ring ring-offset-2 ring-offset-background\",\n            )}\n          />\n        ))}\n      </div>\n    </div>\n  )\n}\n\n// Variant 3: Vertical Sidebar\nexport function ThemeCustomizerSidebar({ className }: { className?: string }) {\n  const { activeColor, setColor, mounted } = useThemeColor()\n\n  if (!mounted) {\n    return (\n      <div\n        className={cn(\n          \"fixed right-4 top-1/2 z-50 flex -translate-y-1/2 flex-col gap-2 rounded-2xl border bg-background/70 p-2 shadow-lg backdrop-blur-xl\",\n          className,\n        )}\n      >\n        {[1, 2, 3, 4, 5, 6, 7, 8, 9].map((i) => (\n          <div key={i} className=\"size-8 animate-pulse rounded-full bg-muted\" />\n        ))}\n      </div>\n    )\n  }\n\n  return (\n    <div\n      className={cn(\n        \"fixed right-4 top-1/2 z-50 flex -translate-y-1/2 flex-col items-center gap-2 rounded-2xl border bg-background/70 p-2 shadow-lg backdrop-blur-xl\",\n        className,\n      )}\n    >\n      <ThemeToggle size={16} className=\"size-8 rounded-full p-2\" />\n\n      <div className=\"mx-auto h-px w-6 bg-border\" />\n\n      {colors.map((c) => (\n        <button\n          key={c.name}\n          onClick={() => setColor(c.name)}\n          className={cn(\n            \"relative size-8 rounded-full transition-transform hover:scale-110 cursor-pointer\",\n            c.class,\n          )}\n        >\n          {activeColor === c.name && (\n            <Check className=\"absolute inset-0 m-auto size-4 text-white\" />\n          )}\n        </button>\n      ))}\n    </div>\n  )\n}\n\n// Variant 4: Bottom Dock (macOS style)\nexport function ThemeCustomizerDock({ className }: { className?: string }) {\n  const { activeColor, setColor, mounted } = useThemeColor()\n\n  if (!mounted) {\n    return (\n      <div\n        className={cn(\n          \"fixed bottom-4 left-1/2 z-50 flex -translate-x-1/2 gap-1 rounded-2xl border bg-background/80 p-1.5 shadow-2xl backdrop-blur-xl\",\n          className,\n        )}\n      >\n        {[1, 2, 3, 4, 5, 6, 7, 8].map((i) => (\n          <div key={i} className=\"size-10 animate-pulse rounded-xl bg-muted\" />\n        ))}\n      </div>\n    )\n  }\n\n  return (\n    <div\n      className={cn(\n        \"fixed bottom-4 left-1/2 z-50 flex -translate-x-1/2 items-center gap-1 rounded-2xl border bg-background/80 p-1.5 shadow-2xl backdrop-blur-xl\",\n        className,\n      )}\n    >\n      <ThemeToggle\n        size={20}\n        className=\"size-10 rounded-xl bg-muted/50 p-2 transition-all hover:-translate-y-1\"\n      />\n\n      <div className=\"mx-1 w-px bg-border\" />\n\n      {colors.map((c) => (\n        <button\n          key={c.name}\n          onClick={() => setColor(c.name)}\n          className={cn(\n            \"relative size-10 rounded-xl transition-all hover:-translate-y-1 cursor-pointer\",\n            c.class,\n            activeColor === c.name &&\n              \"ring-2 ring-primary ring-offset-2 ring-offset-background\",\n          )}\n        >\n          {activeColor === c.name && (\n            <Check className=\"absolute inset-0 m-auto size-5 text-white drop-shadow-md dark:text-black\" />\n          )}\n        </button>\n      ))}\n    </div>\n  )\n}\n\n// Variant 5: Minimal Corner\nexport function ThemeCustomizerCorner({ className }: { className?: string }) {\n  const { activeColor, setColor, mounted } = useThemeColor()\n  const [expanded, setExpanded] = React.useState(false)\n\n  if (!mounted) {\n    return (\n      <div className={cn(\"fixed bottom-4 right-4 z-50\", className)}>\n        <div className=\"size-12 animate-pulse rounded-full bg-muted shadow-lg\" />\n      </div>\n    )\n  }\n\n  return (\n    <div\n      className={cn(\n        \"fixed bottom-4 right-4 z-50 flex flex-col-reverse items-end gap-2\",\n        className,\n      )}\n    >\n      <button\n        onClick={() => setExpanded(!expanded)}\n        className=\"flex size-12 items-center justify-center rounded-full border bg-background shadow-lg transition-transform hover:scale-105 cursor-pointer\"\n      >\n        <Paintbrush\n          className={cn(\"size-5 transition-transform\", expanded && \"rotate-45\")}\n        />\n      </button>\n\n      {expanded && (\n        <div className=\"flex flex-col items-center gap-2 rounded-2xl border bg-background/90 p-2 shadow-lg backdrop-blur-xl animate-in fade-in slide-in-from-bottom-2\">\n          <ThemeToggle size={20} className=\"size-10 rounded-xl p-2\" />\n\n          <div className=\"h-px w-full bg-border\" />\n\n          {colors.map((c) => (\n            <button\n              key={c.name}\n              onClick={() => setColor(c.name)}\n              className={cn(\n                \"relative size-10 rounded-xl transition-transform hover:scale-105 cursor-pointer\",\n                c.class,\n              )}\n            >\n              {activeColor === c.name && (\n                <Check className=\"absolute inset-0 m-auto size-5 text-white\" />\n              )}\n            </button>\n          ))}\n        </div>\n      )}\n    </div>\n  )\n}\n\n// Variant 6: Dynamic Toolbar with animation\nconst toolbarTransition = {\n  type: \"spring\" as const,\n  bounce: 0.1,\n  duration: 0.2,\n}\n\nfunction ToolbarButton({\n  children,\n  onClick,\n  disabled,\n  ariaLabel,\n}: {\n  children: React.ReactNode\n  onClick?: () => void\n  disabled?: boolean\n  ariaLabel?: string\n}) {\n  return (\n    <button\n      type=\"button\"\n      onClick={onClick}\n      disabled={disabled}\n      aria-label={ariaLabel}\n      className=\"relative flex size-9 shrink-0 cursor-pointer select-none appearance-none items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:ring-2 active:scale-[0.98] disabled:pointer-events-none disabled:opacity-50\"\n    >\n      {children}\n    </button>\n  )\n}\n\nexport function ThemeCustomizerToolbar({ className }: { className?: string }) {\n  const { setTheme, resolvedTheme } = useTheme()\n  const { activeColor, setColor, mounted } = useThemeColor()\n  const [isOpen, setIsOpen] = React.useState(false)\n  const containerRef = React.useRef<HTMLDivElement>(null)\n\n  useClickOutside(containerRef, () => {\n    setIsOpen(false)\n  })\n\n  if (!mounted) {\n    return (\n      <div\n        className={cn(\n          \"fixed bottom-4 left-1/2 z-50 hidden -translate-x-1/2 sm:block\",\n          className,\n        )}\n      >\n        <div className=\"h-[52px] w-[100px] animate-pulse rounded-xl border bg-background\" />\n      </div>\n    )\n  }\n\n  return (\n    <MotionConfig transition={toolbarTransition}>\n      <div\n        className={cn(\n          \"fixed bottom-4 left-1/2 z-50 hidden -translate-x-1/2 sm:block\",\n          className,\n        )}\n        ref={containerRef}\n      >\n        <div className=\"rounded-xl border bg-background/80 shadow-lg backdrop-blur-xl\">\n          <motion.div\n            animate={{\n              width: isOpen ? \"320px\" : \"100px\",\n            }}\n            initial={false}\n          >\n            <div className=\"overflow-hidden p-2\">\n              {!isOpen ? (\n                <div className=\"flex items-center space-x-1\">\n                  <ThemeToggle size={24} className=\"size-9 rounded-lg p-1.5\" />\n                  <ToolbarButton\n                    onClick={() => setIsOpen(true)}\n                    ariaLabel=\"Open color picker\"\n                  >\n                    <BrushCleaning size={20} animateOnHover />\n                  </ToolbarButton>\n                </div>\n              ) : (\n                <div className=\"flex items-center space-x-2\">\n                  <ToolbarButton\n                    onClick={() => setIsOpen(false)}\n                    ariaLabel=\"Back\"\n                  >\n                    <ArrowLeft className=\"size-5\" />\n                  </ToolbarButton>\n                  <div className=\"flex flex-1 items-center justify-center gap-2\">\n                    {colors.map((c) => (\n                      <button\n                        key={c.name}\n                        onClick={() => setColor(c.name)}\n                        className={cn(\n                          \"relative size-7 cursor-pointer rounded-full transition-transform hover:scale-110\",\n                          c.class,\n                          activeColor === c.name &&\n                            \"ring-2 ring-ring ring-offset-2 ring-offset-background\",\n                        )}\n                      >\n                        {activeColor === c.name && (\n                          <Check className=\"absolute inset-0 m-auto size-4 text-white\" />\n                        )}\n                      </button>\n                    ))}\n                  </div>\n                </div>\n              )}\n            </div>\n          </motion.div>\n        </div>\n      </div>\n    </MotionConfig>\n  )\n}\n\n// Default export - the pill variant\nexport { ThemeCustomizerPill as ThemeCustomizer }\n",
      "type": "registry:ui"
    }
  ]
}