feat: redesign responsive control room ui
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
import { computed, reactive } from "vue";
|
||||
|
||||
type ThemeMode = "light" | "dark" | "system";
|
||||
type DensityMode = "comfortable" | "compact";
|
||||
|
||||
const STORAGE_KEYS = {
|
||||
themeMode: "live-recorder-ui-theme",
|
||||
density: "live-recorder-ui-density",
|
||||
sidebarCollapsed: "live-recorder-ui-sidebar-collapsed"
|
||||
} as const;
|
||||
|
||||
const state = reactive({
|
||||
themeMode: "system" as ThemeMode,
|
||||
density: "comfortable" as DensityMode,
|
||||
sidebarCollapsed: false,
|
||||
systemTheme: "light" as "light" | "dark",
|
||||
initialized: false
|
||||
});
|
||||
|
||||
let mediaQueryList: MediaQueryList | null = null;
|
||||
|
||||
function readStoredThemeMode(): ThemeMode {
|
||||
const stored = window.localStorage.getItem(STORAGE_KEYS.themeMode);
|
||||
return stored === "light" || stored === "dark" || stored === "system" ? stored : "system";
|
||||
}
|
||||
|
||||
function readStoredDensity(): DensityMode {
|
||||
return window.localStorage.getItem(STORAGE_KEYS.density) === "compact" ? "compact" : "comfortable";
|
||||
}
|
||||
|
||||
function readStoredSidebarCollapsed() {
|
||||
return window.localStorage.getItem(STORAGE_KEYS.sidebarCollapsed) === "true";
|
||||
}
|
||||
|
||||
function resolveTheme(): "light" | "dark" {
|
||||
return state.themeMode === "system" ? state.systemTheme : state.themeMode;
|
||||
}
|
||||
|
||||
function applyDocumentPreferences() {
|
||||
if (typeof document === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
const resolvedTheme = resolveTheme();
|
||||
document.documentElement.dataset.theme = resolvedTheme;
|
||||
document.documentElement.dataset.themeMode = state.themeMode;
|
||||
document.documentElement.dataset.density = state.density;
|
||||
}
|
||||
|
||||
function syncSystemTheme() {
|
||||
state.systemTheme = mediaQueryList?.matches ? "dark" : "light";
|
||||
applyDocumentPreferences();
|
||||
}
|
||||
|
||||
function ensureInitialized() {
|
||||
if (state.initialized || typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
state.themeMode = readStoredThemeMode();
|
||||
state.density = readStoredDensity();
|
||||
state.sidebarCollapsed = readStoredSidebarCollapsed();
|
||||
mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
|
||||
if (typeof mediaQueryList.addEventListener === "function") {
|
||||
mediaQueryList.addEventListener("change", syncSystemTheme);
|
||||
} else {
|
||||
mediaQueryList.addListener(syncSystemTheme);
|
||||
}
|
||||
|
||||
syncSystemTheme();
|
||||
state.initialized = true;
|
||||
}
|
||||
|
||||
function persistPreferences() {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
window.localStorage.setItem(STORAGE_KEYS.themeMode, state.themeMode);
|
||||
window.localStorage.setItem(STORAGE_KEYS.density, state.density);
|
||||
window.localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, String(state.sidebarCollapsed));
|
||||
}
|
||||
|
||||
export function useUiPreferences() {
|
||||
ensureInitialized();
|
||||
|
||||
const resolvedTheme = computed(() => resolveTheme());
|
||||
|
||||
function setThemeMode(value: ThemeMode) {
|
||||
state.themeMode = value;
|
||||
persistPreferences();
|
||||
applyDocumentPreferences();
|
||||
}
|
||||
|
||||
function cycleThemeMode() {
|
||||
const nextValue =
|
||||
state.themeMode === "light" ? "dark" : state.themeMode === "dark" ? "system" : "light";
|
||||
setThemeMode(nextValue);
|
||||
}
|
||||
|
||||
function setDensity(value: DensityMode) {
|
||||
state.density = value;
|
||||
persistPreferences();
|
||||
applyDocumentPreferences();
|
||||
}
|
||||
|
||||
function setSidebarCollapsed(value: boolean) {
|
||||
state.sidebarCollapsed = value;
|
||||
persistPreferences();
|
||||
}
|
||||
|
||||
function toggleSidebarCollapsed() {
|
||||
setSidebarCollapsed(!state.sidebarCollapsed);
|
||||
}
|
||||
|
||||
return {
|
||||
themeMode: computed({
|
||||
get: () => state.themeMode,
|
||||
set: (value: ThemeMode) => setThemeMode(value)
|
||||
}),
|
||||
resolvedTheme,
|
||||
density: computed({
|
||||
get: () => state.density,
|
||||
set: (value: DensityMode) => setDensity(value)
|
||||
}),
|
||||
sidebarCollapsed: computed({
|
||||
get: () => state.sidebarCollapsed,
|
||||
set: (value: boolean) => setSidebarCollapsed(value)
|
||||
}),
|
||||
cycleThemeMode,
|
||||
toggleSidebarCollapsed
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user