{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "theme-provider",
  "title": "Theme Provider",
  "description": "A theme provider with system detection, storage sync, and a keyboard shortcut.",
  "files": [
    {
      "path": "registry/ui/theme-provider.tsx",
      "content": "/* eslint-disable react-refresh/only-export-components */\nimport * as React from \"react\"\n\ntype Theme = \"dark\" | \"light\" | \"system\"\ntype ResolvedTheme = \"dark\" | \"light\"\n\ntype ThemeProviderProps = {\n  children: React.ReactNode\n  defaultTheme?: Theme\n  storageKey?: string\n  disableTransitionOnChange?: boolean\n}\n\ntype ThemeProviderState = {\n  theme: Theme\n  setTheme: (theme: Theme) => void\n}\n\nconst COLOR_SCHEME_QUERY = \"(prefers-color-scheme: dark)\"\nconst THEME_VALUES: Theme[] = [\"dark\", \"light\", \"system\"]\n\nconst ThemeProviderContext = React.createContext<\n  ThemeProviderState | undefined\n>(undefined)\n\nfunction isTheme(value: string | null): value is Theme {\n  if (value === null) {\n    return false\n  }\n\n  return THEME_VALUES.includes(value as Theme)\n}\n\nfunction getSystemTheme(): ResolvedTheme {\n  if (window.matchMedia(COLOR_SCHEME_QUERY).matches) {\n    return \"dark\"\n  }\n\n  return \"light\"\n}\n\nfunction disableTransitionsTemporarily() {\n  const style = document.createElement(\"style\")\n  style.appendChild(\n    document.createTextNode(\n      \"*,*::before,*::after{-webkit-transition:none!important;transition:none!important}\"\n    )\n  )\n  document.head.appendChild(style)\n\n  return () => {\n    window.getComputedStyle(document.body)\n    requestAnimationFrame(() => {\n      requestAnimationFrame(() => {\n        style.remove()\n      })\n    })\n  }\n}\n\nfunction isEditableTarget(target: EventTarget | null) {\n  if (!(target instanceof HTMLElement)) {\n    return false\n  }\n\n  if (target.isContentEditable) {\n    return true\n  }\n\n  const editableParent = target.closest(\n    \"input, textarea, select, [contenteditable='true']\"\n  )\n  if (editableParent) {\n    return true\n  }\n\n  return false\n}\n\nexport function ThemeProvider({\n  children,\n  defaultTheme = \"system\",\n  storageKey = \"theme\",\n  disableTransitionOnChange = true,\n  ...props\n}: ThemeProviderProps) {\n  const [theme, setThemeState] = React.useState<Theme>(() => {\n    const storedTheme = localStorage.getItem(storageKey)\n    if (isTheme(storedTheme)) {\n      return storedTheme\n    }\n\n    return defaultTheme\n  })\n\n  const setTheme = React.useCallback(\n    (nextTheme: Theme) => {\n      localStorage.setItem(storageKey, nextTheme)\n      setThemeState(nextTheme)\n    },\n    [storageKey]\n  )\n\n  const applyTheme = React.useCallback(\n    (nextTheme: Theme) => {\n      const root = document.documentElement\n      const resolvedTheme =\n        nextTheme === \"system\" ? getSystemTheme() : nextTheme\n      const restoreTransitions = disableTransitionOnChange\n        ? disableTransitionsTemporarily()\n        : null\n\n      root.classList.remove(\"light\", \"dark\")\n      root.classList.add(resolvedTheme)\n\n      if (restoreTransitions) {\n        restoreTransitions()\n      }\n    },\n    [disableTransitionOnChange]\n  )\n\n  React.useEffect(() => {\n    applyTheme(theme)\n\n    if (theme !== \"system\") {\n      return undefined\n    }\n\n    const mediaQuery = window.matchMedia(COLOR_SCHEME_QUERY)\n    const handleChange = () => {\n      applyTheme(\"system\")\n    }\n\n    mediaQuery.addEventListener(\"change\", handleChange)\n\n    return () => {\n      mediaQuery.removeEventListener(\"change\", handleChange)\n    }\n  }, [theme, applyTheme])\n\n  React.useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (event.repeat) {\n        return\n      }\n\n      if (event.metaKey || event.ctrlKey || event.altKey) {\n        return\n      }\n\n      if (isEditableTarget(event.target)) {\n        return\n      }\n\n      if (event.key.toLowerCase() !== \"d\") {\n        return\n      }\n\n      setThemeState((currentTheme) => {\n        const nextTheme =\n          currentTheme === \"dark\"\n            ? \"light\"\n            : currentTheme === \"light\"\n              ? \"dark\"\n              : getSystemTheme() === \"dark\"\n                ? \"light\"\n                : \"dark\"\n\n        localStorage.setItem(storageKey, nextTheme)\n        return nextTheme\n      })\n    }\n\n    window.addEventListener(\"keydown\", handleKeyDown)\n\n    return () => {\n      window.removeEventListener(\"keydown\", handleKeyDown)\n    }\n  }, [storageKey])\n\n  React.useEffect(() => {\n    const handleStorageChange = (event: StorageEvent) => {\n      if (event.storageArea !== localStorage) {\n        return\n      }\n\n      if (event.key !== storageKey) {\n        return\n      }\n\n      if (isTheme(event.newValue)) {\n        setThemeState(event.newValue)\n        return\n      }\n\n      setThemeState(defaultTheme)\n    }\n\n    window.addEventListener(\"storage\", handleStorageChange)\n\n    return () => {\n      window.removeEventListener(\"storage\", handleStorageChange)\n    }\n  }, [defaultTheme, storageKey])\n\n  const value = React.useMemo(\n    () => ({\n      theme,\n      setTheme,\n    }),\n    [theme, setTheme]\n  )\n\n  return (\n    <ThemeProviderContext.Provider {...props} value={value}>\n      {children}\n    </ThemeProviderContext.Provider>\n  )\n}\n\nexport const useTheme = () => {\n  const context = React.useContext(ThemeProviderContext)\n\n  if (context === undefined) {\n    throw new Error(\"useTheme must be used within a ThemeProvider\")\n  }\n\n  return context\n}\n",
      "type": "registry:ui",
      "target": "hoogin/ui/theme-provider.tsx"
    }
  ],
  "type": "registry:ui"
}