Formatted, fixed some ttp stuff
This commit is contained in:
parent
c0102df54f
commit
d77271e4b7
27 changed files with 1089 additions and 371 deletions
|
|
@ -1,79 +1,79 @@
|
|||
import * as React from "react"
|
||||
import * as React from "react";
|
||||
|
||||
type Theme = "dark" | "light" | "system"
|
||||
type ResolvedTheme = "dark" | "light"
|
||||
type Theme = "dark" | "light" | "system";
|
||||
type ResolvedTheme = "dark" | "light";
|
||||
|
||||
type ThemeProviderProps = {
|
||||
children: React.ReactNode
|
||||
defaultTheme?: Theme
|
||||
storageKey?: string
|
||||
disableTransitionOnChange?: boolean
|
||||
}
|
||||
children: React.ReactNode;
|
||||
defaultTheme?: Theme;
|
||||
storageKey?: string;
|
||||
disableTransitionOnChange?: boolean;
|
||||
};
|
||||
|
||||
type ThemeProviderState = {
|
||||
theme: Theme
|
||||
setTheme: (theme: Theme) => void
|
||||
}
|
||||
theme: Theme;
|
||||
setTheme: (theme: Theme) => void;
|
||||
};
|
||||
|
||||
const COLOR_SCHEME_QUERY = "(prefers-color-scheme: dark)"
|
||||
const THEME_VALUES: Theme[] = ["dark", "light", "system"]
|
||||
const COLOR_SCHEME_QUERY = "(prefers-color-scheme: dark)";
|
||||
const THEME_VALUES: Theme[] = ["dark", "light", "system"];
|
||||
|
||||
const ThemeProviderContext = React.createContext<
|
||||
ThemeProviderState | undefined
|
||||
>(undefined)
|
||||
>(undefined);
|
||||
|
||||
function isTheme(value: string | null): value is Theme {
|
||||
if (value === null) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
return THEME_VALUES.includes(value as Theme)
|
||||
return THEME_VALUES.includes(value as Theme);
|
||||
}
|
||||
|
||||
function getSystemTheme(): ResolvedTheme {
|
||||
if (window.matchMedia(COLOR_SCHEME_QUERY).matches) {
|
||||
return "dark"
|
||||
return "dark";
|
||||
}
|
||||
|
||||
return "light"
|
||||
return "light";
|
||||
}
|
||||
|
||||
function disableTransitionsTemporarily() {
|
||||
const style = document.createElement("style")
|
||||
const style = document.createElement("style");
|
||||
style.appendChild(
|
||||
document.createTextNode(
|
||||
"*,*::before,*::after{-webkit-transition:none!important;transition:none!important}"
|
||||
)
|
||||
)
|
||||
document.head.appendChild(style)
|
||||
"*,*::before,*::after{-webkit-transition:none!important;transition:none!important}",
|
||||
),
|
||||
);
|
||||
document.head.appendChild(style);
|
||||
|
||||
return () => {
|
||||
window.getComputedStyle(document.body)
|
||||
window.getComputedStyle(document.body);
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
style.remove()
|
||||
})
|
||||
})
|
||||
}
|
||||
style.remove();
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function isEditableTarget(target: EventTarget | null) {
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target.isContentEditable) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
const editableParent = target.closest(
|
||||
"input, textarea, select, [contenteditable='true']"
|
||||
)
|
||||
"input, textarea, select, [contenteditable='true']",
|
||||
);
|
||||
if (editableParent) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
export function ThemeProvider({
|
||||
|
|
@ -84,76 +84,76 @@ export function ThemeProvider({
|
|||
...props
|
||||
}: ThemeProviderProps) {
|
||||
const [theme, setThemeState] = React.useState<Theme>(() => {
|
||||
const storedTheme = localStorage.getItem(storageKey)
|
||||
const storedTheme = localStorage.getItem(storageKey);
|
||||
if (isTheme(storedTheme)) {
|
||||
return storedTheme
|
||||
return storedTheme;
|
||||
}
|
||||
|
||||
return defaultTheme
|
||||
})
|
||||
return defaultTheme;
|
||||
});
|
||||
|
||||
const setTheme = React.useCallback(
|
||||
(nextTheme: Theme) => {
|
||||
localStorage.setItem(storageKey, nextTheme)
|
||||
setThemeState(nextTheme)
|
||||
localStorage.setItem(storageKey, nextTheme);
|
||||
setThemeState(nextTheme);
|
||||
},
|
||||
[storageKey]
|
||||
)
|
||||
[storageKey],
|
||||
);
|
||||
|
||||
const applyTheme = React.useCallback(
|
||||
(nextTheme: Theme) => {
|
||||
const root = document.documentElement
|
||||
const root = document.documentElement;
|
||||
const resolvedTheme =
|
||||
nextTheme === "system" ? getSystemTheme() : nextTheme
|
||||
nextTheme === "system" ? getSystemTheme() : nextTheme;
|
||||
const restoreTransitions = disableTransitionOnChange
|
||||
? disableTransitionsTemporarily()
|
||||
: null
|
||||
: null;
|
||||
|
||||
root.classList.remove("light", "dark")
|
||||
root.classList.add(resolvedTheme)
|
||||
root.classList.remove("light", "dark");
|
||||
root.classList.add(resolvedTheme);
|
||||
|
||||
if (restoreTransitions) {
|
||||
restoreTransitions()
|
||||
restoreTransitions();
|
||||
}
|
||||
},
|
||||
[disableTransitionOnChange]
|
||||
)
|
||||
[disableTransitionOnChange],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
applyTheme(theme)
|
||||
applyTheme(theme);
|
||||
|
||||
if (theme !== "system") {
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const mediaQuery = window.matchMedia(COLOR_SCHEME_QUERY)
|
||||
const mediaQuery = window.matchMedia(COLOR_SCHEME_QUERY);
|
||||
const handleChange = () => {
|
||||
applyTheme("system")
|
||||
}
|
||||
applyTheme("system");
|
||||
};
|
||||
|
||||
mediaQuery.addEventListener("change", handleChange)
|
||||
mediaQuery.addEventListener("change", handleChange);
|
||||
|
||||
return () => {
|
||||
mediaQuery.removeEventListener("change", handleChange)
|
||||
}
|
||||
}, [theme, applyTheme])
|
||||
mediaQuery.removeEventListener("change", handleChange);
|
||||
};
|
||||
}, [theme, applyTheme]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.repeat) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.metaKey || event.ctrlKey || event.altKey) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEditableTarget(event.target)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key.toLowerCase() !== "d") {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
setThemeState((currentTheme) => {
|
||||
|
|
@ -164,66 +164,66 @@ export function ThemeProvider({
|
|||
? "dark"
|
||||
: getSystemTheme() === "dark"
|
||||
? "light"
|
||||
: "dark"
|
||||
: "dark";
|
||||
|
||||
localStorage.setItem(storageKey, nextTheme)
|
||||
return nextTheme
|
||||
})
|
||||
}
|
||||
localStorage.setItem(storageKey, nextTheme);
|
||||
return nextTheme;
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown)
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown)
|
||||
}
|
||||
}, [storageKey])
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [storageKey]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleStorageChange = (event: StorageEvent) => {
|
||||
if (event.storageArea !== localStorage) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key !== storageKey) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTheme(event.newValue)) {
|
||||
setThemeState(event.newValue)
|
||||
return
|
||||
setThemeState(event.newValue);
|
||||
return;
|
||||
}
|
||||
|
||||
setThemeState(defaultTheme)
|
||||
}
|
||||
setThemeState(defaultTheme);
|
||||
};
|
||||
|
||||
window.addEventListener("storage", handleStorageChange)
|
||||
window.addEventListener("storage", handleStorageChange);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("storage", handleStorageChange)
|
||||
}
|
||||
}, [defaultTheme, storageKey])
|
||||
window.removeEventListener("storage", handleStorageChange);
|
||||
};
|
||||
}, [defaultTheme, storageKey]);
|
||||
|
||||
const value = React.useMemo(
|
||||
() => ({
|
||||
theme,
|
||||
setTheme,
|
||||
}),
|
||||
[theme, setTheme]
|
||||
)
|
||||
[theme, setTheme],
|
||||
);
|
||||
|
||||
return (
|
||||
<ThemeProviderContext.Provider {...props} value={value}>
|
||||
{children}
|
||||
</ThemeProviderContext.Provider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export const useTheme = () => {
|
||||
const context = React.useContext(ThemeProviderContext)
|
||||
const context = React.useContext(ThemeProviderContext);
|
||||
|
||||
if (context === undefined) {
|
||||
throw new Error("useTheme must be used within a ThemeProvider")
|
||||
throw new Error("useTheme must be used within a ThemeProvider");
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue