Files
keystone/resources/js/composables/useAppearance.ts
Harry Bayliss 66f0ee9e50
All checks were successful
CI / Tests (push) Successful in 43s
CI / Lint (push) Successful in 1m3s
Migrate to Gitea, switch JS tooling to oxlint/oxfmt, lift test coverage to 95%
- Add .gitea/workflows/ci.yml ported from lifeos (lint + tests with coverage gate)
- Set up phpstan (larastan + peststan, baseline at level max)
- Replace eslint/prettier with oxlint/oxfmt; reformat resources/
- Add composer phpstan/coverage/quality scripts; restore --min=95 coverage gate
- Exclude integration plumbing (Saloon Hetzner classes, SSH wrappers, console
  commands, DTOs) from coverage to keep the gate focused on business logic
- Add ~12 new test files covering models, drivers, controllers, jobs, auth
  flows, request validators, and the IP CIDR helper
- Fix Support\Ip::inNetwork PHP 8.4 TypeError in CIDR mask check
- Fix FirewallRule::command comparing the enum-cast type column to a string
- Fix Server::network using the wrong foreign key column
- Remove unreachable code under abort(403) in RegisteredUserController
2026-05-13 16:51:07 +01:00

95 lines
2.3 KiB
TypeScript

import { onMounted, ref } from "vue";
type Appearance = "light" | "dark" | "system";
export function updateTheme(value: Appearance) {
if (typeof window === "undefined") {
return;
}
if (value === "system") {
const mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
const systemTheme = mediaQueryList.matches ? "dark" : "light";
document.documentElement.classList.toggle("dark", systemTheme === "dark");
} else {
document.documentElement.classList.toggle("dark", value === "dark");
}
}
const setCookie = (name: string, value: string, days = 365) => {
if (typeof document === "undefined") {
return;
}
const maxAge = days * 24 * 60 * 60;
document.cookie = `${name}=${value};path=/;max-age=${maxAge};SameSite=Lax`;
};
const mediaQuery = () => {
if (typeof window === "undefined") {
return null;
}
return window.matchMedia("(prefers-color-scheme: dark)");
};
const getStoredAppearance = () => {
if (typeof window === "undefined") {
return null;
}
return localStorage.getItem("appearance") as Appearance | null;
};
const handleSystemThemeChange = () => {
const currentAppearance = getStoredAppearance();
updateTheme(currentAppearance || "system");
};
export function initializeTheme() {
if (typeof window === "undefined") {
return;
}
// Initialize theme from saved preference or default to system...
const savedAppearance = getStoredAppearance();
updateTheme(savedAppearance || "system");
// Set up system theme change listener...
mediaQuery()?.addEventListener("change", handleSystemThemeChange);
}
export function useAppearance() {
const appearance = ref<Appearance>("system");
onMounted(() => {
initializeTheme();
const savedAppearance = localStorage.getItem("appearance") as Appearance | null;
if (savedAppearance) {
appearance.value = savedAppearance;
}
});
function updateAppearance(value: Appearance) {
appearance.value = value;
// Store in localStorage for client-side persistence...
localStorage.setItem("appearance", value);
// Store in cookie for SSR...
setCookie("appearance", value);
updateTheme(value);
}
return {
appearance,
updateAppearance,
};
}