UNPKG

react-theme-snap

Version:

A lightweight React package to easily add dark mode support with theme persistence, custom tokens. Plug-and-play with built-in context, hook — fully customizable for your UI.

256 lines (240 loc) 7.86 kB
// src/components/ThemeToggleBtn.tsx import clsx from "clsx"; // ../node_modules/lucide-react/dist/esm/createLucideIcon.js import { forwardRef as forwardRef2, createElement as createElement2 } from "react"; // ../node_modules/lucide-react/dist/esm/shared/src/utils.js var toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(); var toCamelCase = (string) => string.replace( /^([A-Z])|[\s-_]+(\w)/g, (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase() ); var toPascalCase = (string) => { const camelCase = toCamelCase(string); return camelCase.charAt(0).toUpperCase() + camelCase.slice(1); }; var mergeClasses = (...classes) => classes.filter((className, index, array) => { return Boolean(className) && className.trim() !== "" && array.indexOf(className) === index; }).join(" ").trim(); var hasA11yProp = (props) => { for (const prop in props) { if (prop.startsWith("aria-") || prop === "role" || prop === "title") { return true; } } }; // ../node_modules/lucide-react/dist/esm/Icon.js import { forwardRef, createElement } from "react"; // ../node_modules/lucide-react/dist/esm/defaultAttributes.js var defaultAttributes = { xmlns: "http://www.w3.org/2000/svg", width: 24, height: 24, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }; // ../node_modules/lucide-react/dist/esm/Icon.js var Icon = forwardRef( ({ color = "currentColor", size = 24, strokeWidth = 2, absoluteStrokeWidth, className = "", children, iconNode, ...rest }, ref) => createElement( "svg", { ref, ...defaultAttributes, width: size, height: size, stroke: color, strokeWidth: absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth, className: mergeClasses("lucide", className), ...!children && !hasA11yProp(rest) && { "aria-hidden": "true" }, ...rest }, [ ...iconNode.map(([tag, attrs]) => createElement(tag, attrs)), ...Array.isArray(children) ? children : [children] ] ) ); // ../node_modules/lucide-react/dist/esm/createLucideIcon.js var createLucideIcon = (iconName, iconNode) => { const Component = forwardRef2( ({ className, ...props }, ref) => createElement2(Icon, { ref, iconNode, className: mergeClasses( `lucide-${toKebabCase(toPascalCase(iconName))}`, `lucide-${iconName}`, className ), ...props }) ); Component.displayName = toPascalCase(iconName); return Component; }; // ../node_modules/lucide-react/dist/esm/icons/moon.js var __iconNode = [ [ "path", { d: "M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401", key: "kfwtm" } ] ]; var Moon = createLucideIcon("moon", __iconNode); // ../node_modules/lucide-react/dist/esm/icons/sun.js var __iconNode2 = [ ["circle", { cx: "12", cy: "12", r: "4", key: "4exip2" }], ["path", { d: "M12 2v2", key: "tus03m" }], ["path", { d: "M12 20v2", key: "1lh1kg" }], ["path", { d: "m4.93 4.93 1.41 1.41", key: "149t6j" }], ["path", { d: "m17.66 17.66 1.41 1.41", key: "ptbguv" }], ["path", { d: "M2 12h2", key: "1t8f8n" }], ["path", { d: "M20 12h2", key: "1q8mjw" }], ["path", { d: "m6.34 17.66-1.41 1.41", key: "1m8zz5" }], ["path", { d: "m19.07 4.93-1.41 1.41", key: "1shlcs" }] ]; var Sun = createLucideIcon("sun", __iconNode2); // src/components/ThemeToggleBtn.tsx import { jsx } from "react/jsx-runtime"; var ThemeToggle = ({ isDark, toggleTheme, iconSize, className }) => /* @__PURE__ */ jsx( "button", { onClick: toggleTheme, className: clsx("p-2 rounded-md transition cursor-pointer", className), "aria-label": "Toggle theme", children: isDark ? /* @__PURE__ */ jsx(Sun, { size: iconSize }) : /* @__PURE__ */ jsx(Moon, { size: iconSize }) } ); // src/components/ThemedComponent.tsx import { useState } from "react"; import { Fragment, jsx as jsx2 } from "react/jsx-runtime"; var ThemedComponent = ({ defaultMode = "inherit", tokens, children }) => { const [isDark, setIsDark] = useState(() => { if (defaultMode === "inherit") return document.documentElement.classList.contains("dark"); return defaultMode === "dark"; }); const toggleTheme = () => setIsDark((prev) => !prev); const themeClasses = isDark ? tokens?.dark || {} : tokens?.light || {}; return /* @__PURE__ */ jsx2(Fragment, { children: children({ isDark, toggleTheme, themeClasses }) }); }; // src/components/ThemedComponentSync.tsx import { useState as useState2 } from "react"; // src/hooks/useDarkTheme.tsx import { createContext, useContext } from "react"; var ThemeContext = createContext(null); var useDarkTheme = () => { const context = useContext(ThemeContext); if (!context) throw new Error("UseDarkTheme Hook was used outside of the ThemeProvider"); return context; }; // src/components/ThemedComponentSync.tsx import { Fragment as Fragment2, jsx as jsx3 } from "react/jsx-runtime"; var ThemedComponentSync = ({ defaultMode = "inherit", tokens, children }) => { const globalTheme = useDarkTheme(); const [isDark, setIsDark] = useState2(() => { if (defaultMode === "inherit") return globalTheme.isDark; if (defaultMode === "inherit-reverse") return !globalTheme.isDark; return defaultMode === "dark"; }); const toggleTheme = () => setIsDark((prev) => !prev); const themeClasses = isDark ? tokens?.dark || globalTheme.themeClasses : tokens?.light || globalTheme.themeClasses; return /* @__PURE__ */ jsx3(Fragment2, { children: children({ isDark, toggleTheme, themeClasses }) }); }; // src/context/ThemeContext.tsx import { useEffect, useState as useState3 } from "react"; import { Fragment as Fragment3, jsx as jsx4 } from "react/jsx-runtime"; var ThemeProvider = ({ children, tokens, storageKey = "is-dark" }) => { const [isDark, setIsDark] = useState3(() => { const saved = localStorage.getItem(storageKey); return saved ? JSON.parse(saved) : false; }); useEffect(() => { const systemDefaultTheme = window.matchMedia( "(prefers-color-scheme: dark)" ).matches; if (localStorage.getItem(storageKey) === null) { setIsDark(systemDefaultTheme); } document.documentElement.classList.toggle("dark", isDark); localStorage.setItem(storageKey, JSON.stringify(isDark)); document.documentElement.classList.add("theme-transition"); const timeout = setTimeout(() => { document.documentElement.classList.remove("theme-transition"); }, 300); return () => clearTimeout(timeout); }, [isDark, storageKey]); const toggleTheme = () => setIsDark((prev) => !prev); if (!tokens.dark) { throw new Error("Dark object not definded"); } if (!tokens.light) { throw new Error("Light object not definded"); } const themeClasses = isDark ? tokens.dark : tokens.light; return /* @__PURE__ */ jsx4( ThemeContext.Provider, { value: { isDark, toggleTheme, themeClasses }, children: /* @__PURE__ */ jsx4(Fragment3, { children }) } ); }; export { ThemeContext, ThemeProvider, ThemeToggle, ThemedComponent, ThemedComponentSync, useDarkTheme }; /*! Bundled license information: lucide-react/dist/esm/shared/src/utils.js: lucide-react/dist/esm/defaultAttributes.js: lucide-react/dist/esm/Icon.js: lucide-react/dist/esm/createLucideIcon.js: lucide-react/dist/esm/icons/moon.js: lucide-react/dist/esm/icons/sun.js: lucide-react/dist/esm/lucide-react.js: (** * @license lucide-react v0.536.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. *) */