Feat: Build out server shell
This commit is contained in:
@@ -2,10 +2,31 @@
|
||||
|
||||
import type { ThemeDefinition } from "./schema";
|
||||
|
||||
export const defaultThemePresetPath = "/themes/moku-default.json";
|
||||
export const themePresetMetas = [
|
||||
{
|
||||
id: "moku-default",
|
||||
name: "Moku Default",
|
||||
description: "The baseline Moku theme preset, matching the original shell styling tokens.",
|
||||
path: "/themes/moku-default.json",
|
||||
},
|
||||
{
|
||||
id: "moku-midnight",
|
||||
name: "Moku Midnight",
|
||||
description: "The active warm, low-light Moku theme preset inspired by the Midnight Discord palette direction.",
|
||||
path: "/themes/moku-midnight.json",
|
||||
},
|
||||
] as const satisfies readonly (Pick<ThemeDefinition, "id" | "name" | "description"> & { path: string })[];
|
||||
|
||||
export const defaultThemePresetPath = "/themes/moku-midnight.json";
|
||||
|
||||
export const defaultThemePresetMeta = {
|
||||
id: "moku-default",
|
||||
name: "Moku Default",
|
||||
description: "The baseline Moku theme preset, matching the current shell styling tokens.",
|
||||
id: "moku-midnight",
|
||||
name: "Moku Midnight",
|
||||
description: "The active warm, low-light Moku theme preset inspired by the Midnight Discord palette direction.",
|
||||
} satisfies Pick<ThemeDefinition, "id" | "name" | "description">;
|
||||
|
||||
export const resolveThemePresetPath = (presetId: string): string | null => {
|
||||
const match = themePresetMetas.find((preset) => preset.id === presetId);
|
||||
|
||||
return match?.path ?? null;
|
||||
};
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// Path: Frontend/src/theme/runtime.ts
|
||||
|
||||
import { defaultThemePresetPath } from "./presets";
|
||||
import { defaultThemePresetMeta, defaultThemePresetPath, resolveThemePresetPath } from "./presets";
|
||||
import { createCssVariableMap, isThemeModeName, validateThemeDefinition, type ThemeDefinition, type ThemeModeName } from "./schema";
|
||||
|
||||
export type Theme = ThemeModeName;
|
||||
|
||||
export const THEME_STORAGE_KEY = "theme";
|
||||
export const THEME_PRESET_STORAGE_KEY = "theme-preset";
|
||||
export const DEFAULT_THEME: Theme = "light";
|
||||
|
||||
let activeThemeDefinition: ThemeDefinition | null = null;
|
||||
@@ -21,6 +22,14 @@ const getRootElement = (): HTMLElement | null => {
|
||||
return canUseDom() ? document.documentElement : null;
|
||||
};
|
||||
|
||||
const persistThemePreset = (themeDefinition: ThemeDefinition): void => {
|
||||
if (!canUseStorage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem(THEME_PRESET_STORAGE_KEY, themeDefinition.id);
|
||||
};
|
||||
|
||||
const setDocumentThemeMode = (theme: Theme): void => {
|
||||
const rootElement = getRootElement();
|
||||
|
||||
@@ -71,10 +80,31 @@ export const getDocumentTheme = (): Theme => {
|
||||
|
||||
export const applyThemeDefinition = (themeDefinition: ThemeDefinition, theme: Theme): void => {
|
||||
activeThemeDefinition = themeDefinition;
|
||||
persistThemePreset(themeDefinition);
|
||||
setDocumentThemeMode(theme);
|
||||
applyThemeVariables(themeDefinition, theme);
|
||||
};
|
||||
|
||||
export const resolvePreferredThemePresetId = (): string => {
|
||||
if (!canUseStorage()) {
|
||||
return defaultThemePresetMeta.id;
|
||||
}
|
||||
|
||||
const stored = localStorage.getItem(THEME_PRESET_STORAGE_KEY);
|
||||
|
||||
if (stored && resolveThemePresetPath(stored)) {
|
||||
return stored;
|
||||
}
|
||||
|
||||
return defaultThemePresetMeta.id;
|
||||
};
|
||||
|
||||
export const resolvePreferredThemePresetPath = (): string => {
|
||||
const presetId = resolvePreferredThemePresetId();
|
||||
|
||||
return resolveThemePresetPath(presetId) ?? defaultThemePresetPath;
|
||||
};
|
||||
|
||||
export const initializeThemeRuntime = async (): Promise<ThemeDefinition | null> => {
|
||||
if (typeof window === "undefined") {
|
||||
return null;
|
||||
@@ -88,7 +118,7 @@ export const initializeThemeRuntime = async (): Promise<ThemeDefinition | null>
|
||||
if (!themeInitializationPromise) {
|
||||
themeInitializationPromise = (async (): Promise<ThemeDefinition | null> => {
|
||||
try {
|
||||
const response = await fetch(defaultThemePresetPath, {
|
||||
const response = await fetch(resolvePreferredThemePresetPath(), {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@ export const THEME_Z_INDEX_KEYS = ["base", "dropdown", "sticky", "overlay", "mod
|
||||
export const THEME_MOTION_KEYS = ["durationFast", "durationBase", "durationSlow", "easeStandard"] as const;
|
||||
export const THEME_TYPE_SCALE_KEYS = ["caption", "label", "body", "title", "heading", "display"] as const;
|
||||
export const THEME_FONT_FAMILY_KEYS = ["sans", "heading", "display", "serif", "mono"] as const;
|
||||
export const THEME_MODE_COLOR_KEYS = ["canvas", "surface", "surfaceMuted", "surfaceHover", "border", "borderStrong", "text", "textMuted", "accent", "accentStrong", "accentSoft", "accentContrast", "success", "danger", "warning", "focusRing"] as const;
|
||||
export const THEME_MODE_COLOR_KEYS = ["canvas", "surface", "surfaceMuted", "surfaceHover", "border", "borderStrong", "text", "textMuted", "accent", "accentStrong", "accentSoft", "accentContrast", "primaryOne", "primaryTwo", "primaryThree", "success", "danger", "warning", "focusRing"] as const;
|
||||
|
||||
export type ThemeModeName = (typeof THEME_MODE_NAMES)[number];
|
||||
|
||||
@@ -334,6 +334,9 @@ export const createCssVariableMap = (theme: ThemeDefinition, mode: ThemeModeName
|
||||
"--color-accent-strong": modeTokens.colors.accentStrong,
|
||||
"--color-accent-soft": modeTokens.colors.accentSoft,
|
||||
"--color-accent-contrast": modeTokens.colors.accentContrast,
|
||||
"--color-primary-1": modeTokens.colors.primaryOne,
|
||||
"--color-primary-2": modeTokens.colors.primaryTwo,
|
||||
"--color-primary-3": modeTokens.colors.primaryThree,
|
||||
"--color-success": modeTokens.colors.success,
|
||||
"--color-danger": modeTokens.colors.danger,
|
||||
"--color-warning": modeTokens.colors.warning,
|
||||
|
||||
Reference in New Issue
Block a user