-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added theme setup and persistence
- Loading branch information
Showing
6 changed files
with
158 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { beforeEach, describe, expect, it } from "vitest"; | ||
|
||
import { getCurrentTheme, setBestGuessTheme, setTheme } from "../useTheme"; // Assuming this is the correct path to your module | ||
|
||
// We need to get jest to mock the window.matchMedia function | ||
|
||
describe("Theme functionality", () => { | ||
beforeEach(() => { | ||
// Reset localStorage and document theme attribute before each test | ||
localStorage.clear(); | ||
document.documentElement.removeAttribute("data-theme"); | ||
}); | ||
|
||
it("should set and get dark mode correctly", () => { | ||
setTheme("dark"); | ||
expect(document.documentElement.getAttribute("data-theme")).toEqual("dark"); | ||
expect(localStorage.getItem("theme")).toEqual("dark"); | ||
expect(getCurrentTheme()).toEqual("dark"); | ||
|
||
setTheme("light"); | ||
expect(document.documentElement.getAttribute("data-theme")).toEqual( | ||
"light" | ||
); | ||
expect(localStorage.getItem("theme")).toEqual("light"); | ||
expect(getCurrentTheme()).toEqual("light"); | ||
|
||
setTheme("system"); | ||
// data-theme should be removed when set to system | ||
expect(document.documentElement.getAttribute("data-theme")).toBeNull(); | ||
expect(localStorage.getItem("theme")).toEqual("system"); | ||
expect(getCurrentTheme()).toEqual("system"); | ||
}); | ||
|
||
it("should set best guess theme correctly", () => { | ||
// Ensure localStorage is empty | ||
expect(localStorage.getItem("theme")).toBeNull(); | ||
|
||
// Simulate no localStorage or document theme attribute set | ||
setBestGuessTheme(); | ||
expect(document.documentElement.getAttribute("data-theme")).toBeNull(); | ||
expect(localStorage.getItem("theme")).toEqual("system"); | ||
|
||
// Simulate localStorage set | ||
localStorage.setItem("theme", "dark"); | ||
setBestGuessTheme(); | ||
expect(document.documentElement.getAttribute("data-theme")).toEqual("dark"); | ||
expect(localStorage.getItem("theme")).toEqual("dark"); | ||
|
||
// Simulate document theme attribute set | ||
// Remove localStorage | ||
localStorage.removeItem("theme"); | ||
document.documentElement.setAttribute("data-theme", "light"); | ||
setBestGuessTheme(); | ||
expect(document.documentElement.getAttribute("data-theme")).toEqual( | ||
"light" | ||
); | ||
expect(localStorage.getItem("theme")).toEqual("light"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import $bus, { eventTypes } from "@/eventBus/events"; | ||
import { ref } from "vue"; | ||
|
||
/** | ||
* The supported themes. Dark, light, and system. | ||
* | ||
*/ | ||
export const SUPPORT_THEMES = ["system", "dark", "light"]; | ||
|
||
export const getBestGuessTheme = () => { | ||
return ( | ||
localStorage.getItem("theme") ?? | ||
// Read the data-theme attribute from the html element | ||
document.documentElement.getAttribute("data-theme") ?? | ||
// Use the first supported theme | ||
SUPPORT_THEMES[0] | ||
); | ||
}; | ||
|
||
const currentTheme = ref(getBestGuessTheme()); | ||
|
||
export const setTheme = (value: string, emit = true) => { | ||
if (value === "system") { | ||
document.documentElement.removeAttribute("data-theme"); | ||
} else { | ||
document.documentElement.setAttribute("data-theme", value); | ||
} | ||
localStorage.setItem("theme", value); | ||
currentTheme.value = value; | ||
if (emit) { | ||
$bus.$emit(eventTypes.changed_theme, value); | ||
} | ||
}; | ||
|
||
export const getCurrentTheme = () => { | ||
return document.documentElement.getAttribute("data-theme") ?? "system"; | ||
}; | ||
|
||
/** | ||
* Set the current app theme to the best-guessed theme | ||
*/ | ||
export function setBestGuessTheme() { | ||
let theme = getBestGuessTheme(); | ||
// If the theme is not supported, fallback to system | ||
if (!SUPPORT_THEMES.includes(theme)) { | ||
theme = SUPPORT_THEMES[0]; | ||
} | ||
|
||
setTheme(theme); | ||
} | ||
|
||
/** | ||
* Setup theme | ||
*/ | ||
export function setupTheme() { | ||
setBestGuessTheme(); | ||
|
||
window | ||
.matchMedia("(prefers-color-scheme: dark)") | ||
.addEventListener("change", (e: MediaQueryListEvent) => { | ||
setTheme(e.matches ? "dark" : "light", false); | ||
}); | ||
} | ||
|
||
/** | ||
* Export as Vue3 plugin | ||
* | ||
*/ | ||
export const ThemePlugin = { | ||
install: () => { | ||
// Install by running the setup function | ||
setupTheme(); | ||
}, | ||
}; | ||
|
||
export default currentTheme; |