38 lines
1.3 KiB
TypeScript
38 lines
1.3 KiB
TypeScript
import type { ThemeManifest } from "./types";
|
|
|
|
// Themes are auto-discovered: each src/themes/<id>/manifest.ts default-export
|
|
// is registered here. Keep this module CSS-free so server fns can import it;
|
|
// theme stylesheets are aggregated separately in ./styles.ts.
|
|
const modules = import.meta.glob<{ default: ThemeManifest }>("./*/manifest.ts", {
|
|
eager: true,
|
|
});
|
|
|
|
export const THEMES: Record<string, ThemeManifest> = {};
|
|
|
|
for (const [path, mod] of Object.entries(modules)) {
|
|
const folder = path.split("/")[1];
|
|
const manifest = mod.default;
|
|
if (!manifest || manifest.id !== folder) {
|
|
console.warn(`[themes] skipping ${path}: manifest id must equal folder name`);
|
|
continue;
|
|
}
|
|
THEMES[manifest.id] = manifest;
|
|
}
|
|
|
|
export const DEFAULT_THEME_ID = "default";
|
|
|
|
if (!THEMES[DEFAULT_THEME_ID]) {
|
|
throw new Error("[themes] default theme manifest is missing or invalid");
|
|
}
|
|
|
|
export const THEME_IDS = Object.keys(THEMES);
|
|
|
|
// Base order: default first, rest by id. Pickers re-sort by LOCALIZED title
|
|
// at render time (labels are i18n descriptors, unresolvable here).
|
|
export const THEME_LIST: ThemeManifest[] = Object.values(THEMES).sort(
|
|
(a, b) => (a.order ?? 100) - (b.order ?? 100) || a.id.localeCompare(b.id)
|
|
);
|
|
|
|
export const getTheme = (id?: string | null): ThemeManifest =>
|
|
(id ? THEMES[id] : undefined) ?? THEMES[DEFAULT_THEME_ID];
|