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
JavaScript
// 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.
*)
*/