use-theme-toggle
Version:
A lightweight theme toggler with animated transitions and customizable strategies.
238 lines (235 loc) • 6.63 kB
JavaScript
// src/transitions/base.ts
function BaseTransition() {
return (toggle, { root }, _e) => {
const transition = document.startViewTransition(() => {
toggle();
root.classList.add("base-transition");
});
transition.finished.finally(() => {
root.classList.remove("base-transition");
});
};
}
// src/transitions/diffusion.ts
function ensureStaticStyleInjected(darkSelector) {
const id = "diffusion-transition-style";
if (document.getElementById(id))
return;
const style = document.createElement("style");
style.id = id;
style.textContent = `
::view-transition-old(*),
::view-transition-new(*) {
animation: none;
mix-blend-mode: normal;
}
::view-transition-new(root) {
z-index: 999;
}
::view-transition-old(root) {
z-index: 1;
}
${darkSelector}::view-transition-new(root) {
z-index: 1;
}
${darkSelector}::view-transition-old(root) {
z-index: 999;
}
`;
document.head.appendChild(style);
}
function Diffusion() {
return (toggle, options, e) => {
const x = e.clientX;
const y = e.clientY;
const radius = Math.hypot(
Math.max(x, window.innerWidth),
Math.max(y, window.innerHeight)
);
let isDark = false;
ensureStaticStyleInjected(options.darkSelector);
const transition = document.startViewTransition(() => {
const current = toggle();
isDark = current === options.dark;
options.root.classList.add("diffusion-transition");
});
transition.ready.then(() => {
const clipPath = [
`circle(0px at ${x}px ${y}px)`,
`circle(${radius}px at ${x}px ${y}px)`
];
options.root.animate({
clipPath: isDark ? [...clipPath].reverse() : [...clipPath]
}, {
duration: options.duration,
easing: options.easing,
pseudoElement: isDark ? "::view-transition-old(root)" : "::view-transition-new(root)"
});
});
transition.finished.then(() => {
options.root.classList.remove("diffusion-transition");
});
};
}
// src/transitions/slide.ts
function ensureStaticStyleInjected2(darkSelector) {
const id = "slide-transition-style";
if (document.getElementById(id))
return;
const style = document.createElement("style");
style.id = id;
style.textContent = `
::view-transition-old(*),
::view-transition-new(*) {
animation: none;
mix-blend-mode: normal;
}
::view-transition-new(root) {
z-index: 999;
}
::view-transition-old(root) {
z-index: 1;
}
${darkSelector}::view-transition-new(root) {
z-index: 1;
}
${darkSelector}::view-transition-old(root) {
z-index: 999;
}
`;
document.head.appendChild(style);
}
function Slide() {
return (toggle, options, _e) => {
ensureStaticStyleInjected2(options.darkSelector);
let isDark = false;
const transition = document.startViewTransition(() => {
const next = toggle();
isDark = next === options.dark;
document.documentElement.classList.add("slide-transition");
});
transition.ready.then(() => {
const duration = options.duration;
const easing = options.easing;
const newPseudo = isDark ? "::view-transition-new(root)" : "::view-transition-new(root)";
const oldPseudo = isDark ? "::view-transition-old(root)" : "::view-transition-old(root)";
options.root.animate([
{ transform: `translateX(${isDark ? "100%" : "-100%"})` },
{ transform: "translateX(0)" }
], {
duration,
easing,
pseudoElement: newPseudo
});
options.root.animate([
{ transform: "translateX(0)" },
{ transform: `translateX(${isDark ? "-100%" : "100%"})` }
], {
duration,
easing,
pseudoElement: oldPseudo
});
});
transition.finished.then(() => {
document.documentElement.classList.remove("slide-transition");
});
};
}
// src/themeToggle.ts
function useThemeToggle(_arg1, _arg2) {
if (typeof window === "undefined" || typeof document === "undefined" || typeof localStorage === "undefined") {
return { toggle() {
}, onThemeToggled() {
} };
}
let loader;
let options;
const defaultOptions = {
mode: "class",
key: "theme",
light: "light",
dark: "dark",
easing: "ease-out",
duration: 500
};
if (typeof _arg1 === "function") {
const maybeLoader = _arg1;
options = { ...defaultOptions, ..._arg2 };
if (maybeLoader.length <= 1) {
loader = maybeLoader();
} else {
loader = maybeLoader;
}
} else {
loader = null;
options = { ...defaultOptions, ..._arg1 };
}
const { mode, light, dark, key } = options;
let attributeName = "data-theme";
if (mode === "attribute") {
attributeName = options.attribute || attributeName;
}
const root = document.documentElement;
let toggledCallback = (_currentTheme) => {
};
let saved = localStorage.getItem(key);
if (!saved) {
localStorage.setItem(key, light);
saved = light;
}
let current = saved === dark ? dark : light;
const resumeTheme = {
class: () => root.classList.add(current),
attribute: () => root.setAttribute(attributeName, current),
both: () => {
root.classList.add(current);
root.setAttribute(attributeName, current);
}
};
resumeTheme[mode]?.();
const darkSelector = options.mode === "class" ? `.${options.dark}` : `[${attributeName}=${options.dark}]`;
const toggleClassOrAttribute = () => {
const next = current === dark ? light : dark;
const toggleMap = {
class: () => {
root.classList.remove(current);
root.classList.add(next);
},
attribute: () => {
root.setAttribute(attributeName, next);
},
both: () => {
root.classList.remove(current);
root.classList.add(next);
root.setAttribute(attributeName, next);
}
};
toggleMap[mode]?.();
current = next;
localStorage.setItem(key, next);
toggledCallback(next);
return next;
};
return {
toggle(e) {
if (!loader || typeof loader !== "function") {
const loader2 = BaseTransition();
return loader2(toggleClassOrAttribute, { ...options, root, darkSelector, previousTheme: current }, e);
}
return loader?.(toggleClassOrAttribute, { ...options, root, darkSelector, previousTheme: current }, e);
},
onThemeToggled(cb) {
if (typeof cb === "function") {
toggledCallback = cb;
toggledCallback(current);
}
}
};
}
export {
BaseTransition,
Diffusion,
Slide,
useThemeToggle
};
//# sourceMappingURL=index.js.map