@philiprehberger/react-theme-provider 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # @philiprehberger/react-theme-provider
2
+
3
+ Dark/light/system theme provider for React with localStorage persistence and system preference detection.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @philiprehberger/react-theme-provider
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### ThemeProvider
14
+
15
+ Wrap your app with the provider:
16
+
17
+ ```tsx
18
+ import { ThemeProvider } from '@philiprehberger/react-theme-provider';
19
+
20
+ function App() {
21
+ return (
22
+ <ThemeProvider defaultTheme="system" storageKey="theme">
23
+ <YourApp />
24
+ </ThemeProvider>
25
+ );
26
+ }
27
+ ```
28
+
29
+ ### useTheme
30
+
31
+ Access theme state from any component:
32
+
33
+ ```tsx
34
+ import { useTheme } from '@philiprehberger/react-theme-provider';
35
+
36
+ function MyComponent() {
37
+ const { theme, setTheme, resolvedTheme } = useTheme();
38
+ // theme: 'light' | 'dark' | 'system'
39
+ // resolvedTheme: 'light' | 'dark' (actual applied theme)
40
+ }
41
+ ```
42
+
43
+ ### ThemeToggle
44
+
45
+ Pre-built toggle component with sun/moon/system icons:
46
+
47
+ ```tsx
48
+ import { ThemeToggle } from '@philiprehberger/react-theme-provider';
49
+
50
+ <ThemeToggle />
51
+ ```
52
+
53
+ ## How It Works
54
+
55
+ - Applies `light` or `dark` class to `document.documentElement`
56
+ - Persists user preference to `localStorage`
57
+ - Listens for system `prefers-color-scheme` changes when set to `system`
58
+ - Works with Tailwind CSS `dark:` variant out of the box
59
+
60
+ ## License
61
+
62
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,89 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ var ThemeContext = react.createContext(void 0);
7
+ function ThemeProvider({
8
+ children,
9
+ storageKey = "theme",
10
+ defaultTheme = "system"
11
+ }) {
12
+ const [theme, setTheme] = react.useState(defaultTheme);
13
+ const [resolvedTheme, setResolvedTheme] = react.useState("light");
14
+ const [mounted, setMounted] = react.useState(false);
15
+ react.useEffect(() => {
16
+ setMounted(true);
17
+ const stored = localStorage.getItem(storageKey);
18
+ if (stored && ["light", "dark", "system"].includes(stored)) {
19
+ setTheme(stored);
20
+ }
21
+ }, [storageKey]);
22
+ react.useEffect(() => {
23
+ if (!mounted) return;
24
+ const root = document.documentElement;
25
+ let resolved;
26
+ if (theme === "system") {
27
+ const systemDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
28
+ resolved = systemDark ? "dark" : "light";
29
+ } else {
30
+ resolved = theme;
31
+ }
32
+ setResolvedTheme(resolved);
33
+ root.classList.remove("light", "dark");
34
+ root.classList.add(resolved);
35
+ localStorage.setItem(storageKey, theme);
36
+ }, [theme, mounted, storageKey]);
37
+ react.useEffect(() => {
38
+ if (!mounted || theme !== "system") return;
39
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
40
+ const handleChange = (e) => {
41
+ setResolvedTheme(e.matches ? "dark" : "light");
42
+ document.documentElement.classList.remove("light", "dark");
43
+ document.documentElement.classList.add(e.matches ? "dark" : "light");
44
+ };
45
+ mediaQuery.addEventListener("change", handleChange);
46
+ return () => mediaQuery.removeEventListener("change", handleChange);
47
+ }, [theme, mounted]);
48
+ return /* @__PURE__ */ jsxRuntime.jsx(ThemeContext.Provider, { value: { theme, setTheme, resolvedTheme }, children });
49
+ }
50
+ function useTheme() {
51
+ const context = react.useContext(ThemeContext);
52
+ if (!context) {
53
+ throw new Error("useTheme must be used within a ThemeProvider");
54
+ }
55
+ return context;
56
+ }
57
+ var SunIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "h-5 w-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" }) });
58
+ var MoonIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "h-5 w-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" }) });
59
+ var SystemIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "h-5 w-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" }) });
60
+ function ThemeToggle({
61
+ className = "flex items-center gap-1 rounded-lg bg-gray-100 p-1 dark:bg-gray-800",
62
+ activeClassName = "rounded-md p-2 transition-colors bg-white text-gray-900 shadow-sm dark:bg-gray-700 dark:text-white",
63
+ inactiveClassName = "rounded-md p-2 transition-colors text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
64
+ }) {
65
+ const { theme, setTheme } = useTheme();
66
+ const options = [
67
+ { value: "light", icon: SunIcon, label: "Light" },
68
+ { value: "dark", icon: MoonIcon, label: "Dark" },
69
+ { value: "system", icon: SystemIcon, label: "System" }
70
+ ];
71
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, role: "radiogroup", "aria-label": "Theme selection", children: options.map(({ value, icon: Icon, label }) => /* @__PURE__ */ jsxRuntime.jsx(
72
+ "button",
73
+ {
74
+ onClick: () => setTheme(value),
75
+ role: "radio",
76
+ "aria-checked": theme === value,
77
+ "aria-label": `${label} theme`,
78
+ className: theme === value ? activeClassName : inactiveClassName,
79
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, {})
80
+ },
81
+ value
82
+ )) });
83
+ }
84
+
85
+ exports.ThemeProvider = ThemeProvider;
86
+ exports.ThemeToggle = ThemeToggle;
87
+ exports.useTheme = useTheme;
88
+ //# sourceMappingURL=index.cjs.map
89
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ThemeProvider.tsx","../src/ThemeToggle.tsx"],"names":["createContext","useState","useEffect","jsx","useContext"],"mappings":";;;;;AAWA,IAAM,YAAA,GAAeA,oBAA4C,MAAS,CAAA;AAenE,SAAS,aAAA,CAAc;AAAA,EAC5B,QAAA;AAAA,EACA,UAAA,GAAa,OAAA;AAAA,EACb,YAAA,GAAe;AACjB,CAAA,EAAuB;AACrB,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIC,eAAgB,YAAY,CAAA;AACtD,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAIA,eAAwB,OAAO,CAAA;AACzE,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,eAAS,KAAK,CAAA;AAE5C,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,MAAM,MAAA,GAAS,YAAA,CAAa,OAAA,CAAQ,UAAU,CAAA;AAC9C,IAAA,IAAI,MAAA,IAAU,CAAC,OAAA,EAAS,MAAA,EAAQ,QAAQ,CAAA,CAAE,QAAA,CAAS,MAAM,CAAA,EAAG;AAC1D,MAAA,QAAA,CAAS,MAAM,CAAA;AAAA,IACjB;AAAA,EACF,CAAA,EAAG,CAAC,UAAU,CAAC,CAAA;AAEf,EAAAA,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,OAAA,EAAS;AAEd,IAAA,MAAM,OAAO,QAAA,CAAS,eAAA;AACtB,IAAA,IAAI,QAAA;AAEJ,IAAA,IAAI,UAAU,QAAA,EAAU;AACtB,MAAA,MAAM,UAAA,GAAa,MAAA,CAAO,UAAA,CAAW,8BAA8B,CAAA,CAAE,OAAA;AACrE,MAAA,QAAA,GAAW,aAAa,MAAA,GAAS,OAAA;AAAA,IACnC,CAAA,MAAO;AACL,MAAA,QAAA,GAAW,KAAA;AAAA,IACb;AAEA,IAAA,gBAAA,CAAiB,QAAQ,CAAA;AACzB,IAAA,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,OAAA,EAAS,MAAM,CAAA;AACrC,IAAA,IAAA,CAAK,SAAA,CAAU,IAAI,QAAQ,CAAA;AAC3B,IAAA,YAAA,CAAa,OAAA,CAAQ,YAAY,KAAK,CAAA;AAAA,EACxC,CAAA,EAAG,CAAC,KAAA,EAAO,OAAA,EAAS,UAAU,CAAC,CAAA;AAE/B,EAAAA,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,OAAA,IAAW,KAAA,KAAU,QAAA,EAAU;AAEpC,IAAA,MAAM,UAAA,GAAa,MAAA,CAAO,UAAA,CAAW,8BAA8B,CAAA;AACnE,IAAA,MAAM,YAAA,GAAe,CAAC,CAAA,KAA2B;AAC/C,MAAA,gBAAA,CAAiB,CAAA,CAAE,OAAA,GAAU,MAAA,GAAS,OAAO,CAAA;AAC7C,MAAA,QAAA,CAAS,eAAA,CAAgB,SAAA,CAAU,MAAA,CAAO,OAAA,EAAS,MAAM,CAAA;AACzD,MAAA,QAAA,CAAS,gBAAgB,SAAA,CAAU,GAAA,CAAI,CAAA,CAAE,OAAA,GAAU,SAAS,OAAO,CAAA;AAAA,IACrE,CAAA;AAEA,IAAA,UAAA,CAAW,gBAAA,CAAiB,UAAU,YAAY,CAAA;AAClD,IAAA,OAAO,MAAM,UAAA,CAAW,mBAAA,CAAoB,QAAA,EAAU,YAAY,CAAA;AAAA,EACpE,CAAA,EAAG,CAAC,KAAA,EAAO,OAAO,CAAC,CAAA;AAEnB,EAAA,uBACEC,cAAA,CAAC,YAAA,CAAa,QAAA,EAAb,EAAsB,KAAA,EAAO,EAAE,KAAA,EAAO,QAAA,EAAU,aAAA,EAAc,EAC5D,QAAA,EACH,CAAA;AAEJ;AAMO,SAAS,QAAA,GAA6B;AAC3C,EAAA,MAAM,OAAA,GAAUC,iBAAW,YAAY,CAAA;AACvC,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,IAAI,MAAM,8CAA8C,CAAA;AAAA,EAChE;AACA,EAAA,OAAO,OAAA;AACT;AC1FA,IAAM,OAAA,GAAU,sBACdD,cAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,SAAA,EAAU,IAAA,EAAK,MAAA,EAAO,MAAA,EAAO,cAAA,EAAe,OAAA,EAAQ,WAAA,EAAY,eAAY,MAAA,EACzF,QAAA,kBAAAA,cAAAA,CAAC,MAAA,EAAA,EAAK,aAAA,EAAc,OAAA,EAAQ,cAAA,EAAe,OAAA,EAAQ,WAAA,EAAa,CAAA,EAAG,CAAA,EAAE,uJAAA,EAAwJ,CAAA,EAC/N,CAAA;AAGF,IAAM,QAAA,GAAW,sBACfA,cAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,SAAA,EAAU,IAAA,EAAK,MAAA,EAAO,MAAA,EAAO,cAAA,EAAe,OAAA,EAAQ,WAAA,EAAY,eAAY,MAAA,EACzF,QAAA,kBAAAA,cAAAA,CAAC,MAAA,EAAA,EAAK,aAAA,EAAc,OAAA,EAAQ,cAAA,EAAe,OAAA,EAAQ,WAAA,EAAa,CAAA,EAAG,CAAA,EAAE,uFAAA,EAAwF,CAAA,EAC/J,CAAA;AAGF,IAAM,UAAA,GAAa,sBACjBA,cAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,SAAA,EAAU,IAAA,EAAK,MAAA,EAAO,MAAA,EAAO,cAAA,EAAe,OAAA,EAAQ,WAAA,EAAY,eAAY,MAAA,EACzF,QAAA,kBAAAA,cAAAA,CAAC,MAAA,EAAA,EAAK,aAAA,EAAc,OAAA,EAAQ,cAAA,EAAe,OAAA,EAAQ,WAAA,EAAa,CAAA,EAAG,CAAA,EAAE,2GAAA,EAA4G,CAAA,EACnL,CAAA;AAsBK,SAAS,WAAA,CAAY;AAAA,EAC1B,SAAA,GAAY,qEAAA;AAAA,EACZ,eAAA,GAAkB,oGAAA;AAAA,EAClB,iBAAA,GAAoB;AACtB,CAAA,EAAqB;AACnB,EAAA,MAAM,EAAE,KAAA,EAAO,QAAA,EAAS,GAAI,QAAA,EAAS;AAErC,EAAA,MAAM,OAAA,GAAyB;AAAA,IAC7B,EAAE,KAAA,EAAO,OAAA,EAAS,IAAA,EAAM,OAAA,EAAS,OAAO,OAAA,EAAQ;AAAA,IAChD,EAAE,KAAA,EAAO,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU,OAAO,MAAA,EAAO;AAAA,IAC/C,EAAE,KAAA,EAAO,QAAA,EAAU,IAAA,EAAM,UAAA,EAAY,OAAO,QAAA;AAAS,GACvD;AAEA,EAAA,uBACEA,cAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAsB,IAAA,EAAK,cAAa,YAAA,EAAW,iBAAA,EACrD,QAAA,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAC,EAAE,KAAA,EAAO,MAAM,IAAA,EAAM,KAAA,uBACjCA,cAAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MAEC,OAAA,EAAS,MAAM,QAAA,CAAS,KAAK,CAAA;AAAA,MAC7B,IAAA,EAAK,OAAA;AAAA,MACL,gBAAc,KAAA,KAAU,KAAA;AAAA,MACxB,YAAA,EAAY,GAAG,KAAK,CAAA,MAAA,CAAA;AAAA,MACpB,SAAA,EAAW,KAAA,KAAU,KAAA,GAAQ,eAAA,GAAkB,iBAAA;AAAA,MAE/C,QAAA,kBAAAA,eAAC,IAAA,EAAA,EAAK;AAAA,KAAA;AAAA,IAPD;AAAA,GASR,CAAA,EACH,CAAA;AAEJ","file":"index.cjs","sourcesContent":["import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';\n\nexport type Theme = 'light' | 'dark' | 'system';\nexport type ResolvedTheme = 'light' | 'dark';\n\nexport interface ThemeContextType {\n theme: Theme;\n setTheme: (theme: Theme) => void;\n resolvedTheme: ResolvedTheme;\n}\n\nconst ThemeContext = createContext<ThemeContextType | undefined>(undefined);\n\nexport interface ThemeProviderProps {\n children: ReactNode;\n /** localStorage key for persisting theme (default: \"theme\") */\n storageKey?: string;\n /** Default theme when no stored preference exists (default: \"system\") */\n defaultTheme?: Theme;\n}\n\n/**\n * Theme provider with dark/light/system support.\n * Persists preference to localStorage and syncs with system preference changes.\n * Applies theme by toggling \"light\"/\"dark\" classes on the document element.\n */\nexport function ThemeProvider({\n children,\n storageKey = 'theme',\n defaultTheme = 'system',\n}: ThemeProviderProps) {\n const [theme, setTheme] = useState<Theme>(defaultTheme);\n const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>('light');\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n setMounted(true);\n const stored = localStorage.getItem(storageKey) as Theme | null;\n if (stored && ['light', 'dark', 'system'].includes(stored)) {\n setTheme(stored);\n }\n }, [storageKey]);\n\n useEffect(() => {\n if (!mounted) return;\n\n const root = document.documentElement;\n let resolved: ResolvedTheme;\n\n if (theme === 'system') {\n const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n resolved = systemDark ? 'dark' : 'light';\n } else {\n resolved = theme;\n }\n\n setResolvedTheme(resolved);\n root.classList.remove('light', 'dark');\n root.classList.add(resolved);\n localStorage.setItem(storageKey, theme);\n }, [theme, mounted, storageKey]);\n\n useEffect(() => {\n if (!mounted || theme !== 'system') return;\n\n const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n const handleChange = (e: MediaQueryListEvent) => {\n setResolvedTheme(e.matches ? 'dark' : 'light');\n document.documentElement.classList.remove('light', 'dark');\n document.documentElement.classList.add(e.matches ? 'dark' : 'light');\n };\n\n mediaQuery.addEventListener('change', handleChange);\n return () => mediaQuery.removeEventListener('change', handleChange);\n }, [theme, mounted]);\n\n return (\n <ThemeContext.Provider value={{ theme, setTheme, resolvedTheme }}>\n {children}\n </ThemeContext.Provider>\n );\n}\n\n/**\n * Access the current theme context.\n * Must be used within a ThemeProvider.\n */\nexport function useTheme(): ThemeContextType {\n const context = useContext(ThemeContext);\n if (!context) {\n throw new Error('useTheme must be used within a ThemeProvider');\n }\n return context;\n}\n","import { type ReactNode } from 'react';\nimport { useTheme, type Theme } from './ThemeProvider';\n\nconst SunIcon = () => (\n <svg className=\"h-5 w-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z\" />\n </svg>\n);\n\nconst MoonIcon = () => (\n <svg className=\"h-5 w-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z\" />\n </svg>\n);\n\nconst SystemIcon = () => (\n <svg className=\"h-5 w-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\" />\n </svg>\n);\n\ninterface ThemeOption {\n value: Theme;\n icon: () => ReactNode;\n label: string;\n}\n\nexport interface ThemeToggleProps {\n /** CSS class for the outer container */\n className?: string;\n /** CSS class for the active button */\n activeClassName?: string;\n /** CSS class for inactive buttons */\n inactiveClassName?: string;\n}\n\n/**\n * Three-way theme toggle (light / dark / system).\n * Uses radio group semantics for accessibility.\n */\nexport function ThemeToggle({\n className = 'flex items-center gap-1 rounded-lg bg-gray-100 p-1 dark:bg-gray-800',\n activeClassName = 'rounded-md p-2 transition-colors bg-white text-gray-900 shadow-sm dark:bg-gray-700 dark:text-white',\n inactiveClassName = 'rounded-md p-2 transition-colors text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white',\n}: ThemeToggleProps) {\n const { theme, setTheme } = useTheme();\n\n const options: ThemeOption[] = [\n { value: 'light', icon: SunIcon, label: 'Light' },\n { value: 'dark', icon: MoonIcon, label: 'Dark' },\n { value: 'system', icon: SystemIcon, label: 'System' },\n ];\n\n return (\n <div className={className} role=\"radiogroup\" aria-label=\"Theme selection\">\n {options.map(({ value, icon: Icon, label }) => (\n <button\n key={value}\n onClick={() => setTheme(value)}\n role=\"radio\"\n aria-checked={theme === value}\n aria-label={`${label} theme`}\n className={theme === value ? activeClassName : inactiveClassName}\n >\n <Icon />\n </button>\n ))}\n </div>\n );\n}\n"]}
@@ -0,0 +1,44 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ type Theme = 'light' | 'dark' | 'system';
5
+ type ResolvedTheme = 'light' | 'dark';
6
+ interface ThemeContextType {
7
+ theme: Theme;
8
+ setTheme: (theme: Theme) => void;
9
+ resolvedTheme: ResolvedTheme;
10
+ }
11
+ interface ThemeProviderProps {
12
+ children: ReactNode;
13
+ /** localStorage key for persisting theme (default: "theme") */
14
+ storageKey?: string;
15
+ /** Default theme when no stored preference exists (default: "system") */
16
+ defaultTheme?: Theme;
17
+ }
18
+ /**
19
+ * Theme provider with dark/light/system support.
20
+ * Persists preference to localStorage and syncs with system preference changes.
21
+ * Applies theme by toggling "light"/"dark" classes on the document element.
22
+ */
23
+ declare function ThemeProvider({ children, storageKey, defaultTheme, }: ThemeProviderProps): react_jsx_runtime.JSX.Element;
24
+ /**
25
+ * Access the current theme context.
26
+ * Must be used within a ThemeProvider.
27
+ */
28
+ declare function useTheme(): ThemeContextType;
29
+
30
+ interface ThemeToggleProps {
31
+ /** CSS class for the outer container */
32
+ className?: string;
33
+ /** CSS class for the active button */
34
+ activeClassName?: string;
35
+ /** CSS class for inactive buttons */
36
+ inactiveClassName?: string;
37
+ }
38
+ /**
39
+ * Three-way theme toggle (light / dark / system).
40
+ * Uses radio group semantics for accessibility.
41
+ */
42
+ declare function ThemeToggle({ className, activeClassName, inactiveClassName, }: ThemeToggleProps): react_jsx_runtime.JSX.Element;
43
+
44
+ export { type ResolvedTheme, type Theme, type ThemeContextType, ThemeProvider, type ThemeProviderProps, ThemeToggle, type ThemeToggleProps, useTheme };
@@ -0,0 +1,44 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ type Theme = 'light' | 'dark' | 'system';
5
+ type ResolvedTheme = 'light' | 'dark';
6
+ interface ThemeContextType {
7
+ theme: Theme;
8
+ setTheme: (theme: Theme) => void;
9
+ resolvedTheme: ResolvedTheme;
10
+ }
11
+ interface ThemeProviderProps {
12
+ children: ReactNode;
13
+ /** localStorage key for persisting theme (default: "theme") */
14
+ storageKey?: string;
15
+ /** Default theme when no stored preference exists (default: "system") */
16
+ defaultTheme?: Theme;
17
+ }
18
+ /**
19
+ * Theme provider with dark/light/system support.
20
+ * Persists preference to localStorage and syncs with system preference changes.
21
+ * Applies theme by toggling "light"/"dark" classes on the document element.
22
+ */
23
+ declare function ThemeProvider({ children, storageKey, defaultTheme, }: ThemeProviderProps): react_jsx_runtime.JSX.Element;
24
+ /**
25
+ * Access the current theme context.
26
+ * Must be used within a ThemeProvider.
27
+ */
28
+ declare function useTheme(): ThemeContextType;
29
+
30
+ interface ThemeToggleProps {
31
+ /** CSS class for the outer container */
32
+ className?: string;
33
+ /** CSS class for the active button */
34
+ activeClassName?: string;
35
+ /** CSS class for inactive buttons */
36
+ inactiveClassName?: string;
37
+ }
38
+ /**
39
+ * Three-way theme toggle (light / dark / system).
40
+ * Uses radio group semantics for accessibility.
41
+ */
42
+ declare function ThemeToggle({ className, activeClassName, inactiveClassName, }: ThemeToggleProps): react_jsx_runtime.JSX.Element;
43
+
44
+ export { type ResolvedTheme, type Theme, type ThemeContextType, ThemeProvider, type ThemeProviderProps, ThemeToggle, type ThemeToggleProps, useTheme };
package/dist/index.js ADDED
@@ -0,0 +1,85 @@
1
+ import { createContext, useState, useEffect, useContext } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ var ThemeContext = createContext(void 0);
5
+ function ThemeProvider({
6
+ children,
7
+ storageKey = "theme",
8
+ defaultTheme = "system"
9
+ }) {
10
+ const [theme, setTheme] = useState(defaultTheme);
11
+ const [resolvedTheme, setResolvedTheme] = useState("light");
12
+ const [mounted, setMounted] = useState(false);
13
+ useEffect(() => {
14
+ setMounted(true);
15
+ const stored = localStorage.getItem(storageKey);
16
+ if (stored && ["light", "dark", "system"].includes(stored)) {
17
+ setTheme(stored);
18
+ }
19
+ }, [storageKey]);
20
+ useEffect(() => {
21
+ if (!mounted) return;
22
+ const root = document.documentElement;
23
+ let resolved;
24
+ if (theme === "system") {
25
+ const systemDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
26
+ resolved = systemDark ? "dark" : "light";
27
+ } else {
28
+ resolved = theme;
29
+ }
30
+ setResolvedTheme(resolved);
31
+ root.classList.remove("light", "dark");
32
+ root.classList.add(resolved);
33
+ localStorage.setItem(storageKey, theme);
34
+ }, [theme, mounted, storageKey]);
35
+ useEffect(() => {
36
+ if (!mounted || theme !== "system") return;
37
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
38
+ const handleChange = (e) => {
39
+ setResolvedTheme(e.matches ? "dark" : "light");
40
+ document.documentElement.classList.remove("light", "dark");
41
+ document.documentElement.classList.add(e.matches ? "dark" : "light");
42
+ };
43
+ mediaQuery.addEventListener("change", handleChange);
44
+ return () => mediaQuery.removeEventListener("change", handleChange);
45
+ }, [theme, mounted]);
46
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: { theme, setTheme, resolvedTheme }, children });
47
+ }
48
+ function useTheme() {
49
+ const context = useContext(ThemeContext);
50
+ if (!context) {
51
+ throw new Error("useTheme must be used within a ThemeProvider");
52
+ }
53
+ return context;
54
+ }
55
+ var SunIcon = () => /* @__PURE__ */ jsx("svg", { className: "h-5 w-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" }) });
56
+ var MoonIcon = () => /* @__PURE__ */ jsx("svg", { className: "h-5 w-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" }) });
57
+ var SystemIcon = () => /* @__PURE__ */ jsx("svg", { className: "h-5 w-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" }) });
58
+ function ThemeToggle({
59
+ className = "flex items-center gap-1 rounded-lg bg-gray-100 p-1 dark:bg-gray-800",
60
+ activeClassName = "rounded-md p-2 transition-colors bg-white text-gray-900 shadow-sm dark:bg-gray-700 dark:text-white",
61
+ inactiveClassName = "rounded-md p-2 transition-colors text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
62
+ }) {
63
+ const { theme, setTheme } = useTheme();
64
+ const options = [
65
+ { value: "light", icon: SunIcon, label: "Light" },
66
+ { value: "dark", icon: MoonIcon, label: "Dark" },
67
+ { value: "system", icon: SystemIcon, label: "System" }
68
+ ];
69
+ return /* @__PURE__ */ jsx("div", { className, role: "radiogroup", "aria-label": "Theme selection", children: options.map(({ value, icon: Icon, label }) => /* @__PURE__ */ jsx(
70
+ "button",
71
+ {
72
+ onClick: () => setTheme(value),
73
+ role: "radio",
74
+ "aria-checked": theme === value,
75
+ "aria-label": `${label} theme`,
76
+ className: theme === value ? activeClassName : inactiveClassName,
77
+ children: /* @__PURE__ */ jsx(Icon, {})
78
+ },
79
+ value
80
+ )) });
81
+ }
82
+
83
+ export { ThemeProvider, ThemeToggle, useTheme };
84
+ //# sourceMappingURL=index.js.map
85
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ThemeProvider.tsx","../src/ThemeToggle.tsx"],"names":["jsx"],"mappings":"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","file":"index.js","sourcesContent":["import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';\n\nexport type Theme = 'light' | 'dark' | 'system';\nexport type ResolvedTheme = 'light' | 'dark';\n\nexport interface ThemeContextType {\n theme: Theme;\n setTheme: (theme: Theme) => void;\n resolvedTheme: ResolvedTheme;\n}\n\nconst ThemeContext = createContext<ThemeContextType | undefined>(undefined);\n\nexport interface ThemeProviderProps {\n children: ReactNode;\n /** localStorage key for persisting theme (default: \"theme\") */\n storageKey?: string;\n /** Default theme when no stored preference exists (default: \"system\") */\n defaultTheme?: Theme;\n}\n\n/**\n * Theme provider with dark/light/system support.\n * Persists preference to localStorage and syncs with system preference changes.\n * Applies theme by toggling \"light\"/\"dark\" classes on the document element.\n */\nexport function ThemeProvider({\n children,\n storageKey = 'theme',\n defaultTheme = 'system',\n}: ThemeProviderProps) {\n const [theme, setTheme] = useState<Theme>(defaultTheme);\n const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>('light');\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n setMounted(true);\n const stored = localStorage.getItem(storageKey) as Theme | null;\n if (stored && ['light', 'dark', 'system'].includes(stored)) {\n setTheme(stored);\n }\n }, [storageKey]);\n\n useEffect(() => {\n if (!mounted) return;\n\n const root = document.documentElement;\n let resolved: ResolvedTheme;\n\n if (theme === 'system') {\n const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n resolved = systemDark ? 'dark' : 'light';\n } else {\n resolved = theme;\n }\n\n setResolvedTheme(resolved);\n root.classList.remove('light', 'dark');\n root.classList.add(resolved);\n localStorage.setItem(storageKey, theme);\n }, [theme, mounted, storageKey]);\n\n useEffect(() => {\n if (!mounted || theme !== 'system') return;\n\n const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n const handleChange = (e: MediaQueryListEvent) => {\n setResolvedTheme(e.matches ? 'dark' : 'light');\n document.documentElement.classList.remove('light', 'dark');\n document.documentElement.classList.add(e.matches ? 'dark' : 'light');\n };\n\n mediaQuery.addEventListener('change', handleChange);\n return () => mediaQuery.removeEventListener('change', handleChange);\n }, [theme, mounted]);\n\n return (\n <ThemeContext.Provider value={{ theme, setTheme, resolvedTheme }}>\n {children}\n </ThemeContext.Provider>\n );\n}\n\n/**\n * Access the current theme context.\n * Must be used within a ThemeProvider.\n */\nexport function useTheme(): ThemeContextType {\n const context = useContext(ThemeContext);\n if (!context) {\n throw new Error('useTheme must be used within a ThemeProvider');\n }\n return context;\n}\n","import { type ReactNode } from 'react';\nimport { useTheme, type Theme } from './ThemeProvider';\n\nconst SunIcon = () => (\n <svg className=\"h-5 w-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z\" />\n </svg>\n);\n\nconst MoonIcon = () => (\n <svg className=\"h-5 w-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z\" />\n </svg>\n);\n\nconst SystemIcon = () => (\n <svg className=\"h-5 w-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\" />\n </svg>\n);\n\ninterface ThemeOption {\n value: Theme;\n icon: () => ReactNode;\n label: string;\n}\n\nexport interface ThemeToggleProps {\n /** CSS class for the outer container */\n className?: string;\n /** CSS class for the active button */\n activeClassName?: string;\n /** CSS class for inactive buttons */\n inactiveClassName?: string;\n}\n\n/**\n * Three-way theme toggle (light / dark / system).\n * Uses radio group semantics for accessibility.\n */\nexport function ThemeToggle({\n className = 'flex items-center gap-1 rounded-lg bg-gray-100 p-1 dark:bg-gray-800',\n activeClassName = 'rounded-md p-2 transition-colors bg-white text-gray-900 shadow-sm dark:bg-gray-700 dark:text-white',\n inactiveClassName = 'rounded-md p-2 transition-colors text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white',\n}: ThemeToggleProps) {\n const { theme, setTheme } = useTheme();\n\n const options: ThemeOption[] = [\n { value: 'light', icon: SunIcon, label: 'Light' },\n { value: 'dark', icon: MoonIcon, label: 'Dark' },\n { value: 'system', icon: SystemIcon, label: 'System' },\n ];\n\n return (\n <div className={className} role=\"radiogroup\" aria-label=\"Theme selection\">\n {options.map(({ value, icon: Icon, label }) => (\n <button\n key={value}\n onClick={() => setTheme(value)}\n role=\"radio\"\n aria-checked={theme === value}\n aria-label={`${label} theme`}\n className={theme === value ? activeClassName : inactiveClassName}\n >\n <Icon />\n </button>\n ))}\n </div>\n );\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@philiprehberger/react-theme-provider",
3
+ "version": "0.1.0",
4
+ "description": "Dark/light/system theme provider for React with localStorage persistence and system preference detection",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsup",
26
+ "dev": "tsup --watch",
27
+ "typecheck": "tsc --noEmit",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "peerDependencies": {
31
+ "react": ">=18.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/react": "^19.0.0",
35
+ "react": "^19.0.0",
36
+ "tsup": "^8.0.0",
37
+ "typescript": "^5.0.0"
38
+ },
39
+ "keywords": [
40
+ "react",
41
+ "theme",
42
+ "dark-mode",
43
+ "light-mode",
44
+ "theme-provider",
45
+ "system-preference"
46
+ ],
47
+ "license": "MIT",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/philiprehberger/react-theme-provider"
51
+ },
52
+ "sideEffects": false
53
+ }