138 lines
3.7 KiB
TypeScript
Executable File
138 lines
3.7 KiB
TypeScript
Executable File
// composables/composSystemTheme.ts
|
|
import type { Ref } from "vue";
|
|
import { ref, computed, watch, onMounted } from "vue";
|
|
import { useState } from "#imports";
|
|
|
|
type Theme = {
|
|
name: string;
|
|
title: string;
|
|
subTitle: string;
|
|
logo: {
|
|
light: string;
|
|
dark: string;
|
|
};
|
|
font: string;
|
|
fontFiles: Array<{ weight: string; style: string; src: string }>;
|
|
colors: {
|
|
primary: Record<
|
|
| "50"
|
|
| "100"
|
|
| "200"
|
|
| "300"
|
|
| "400"
|
|
| "500"
|
|
| "600"
|
|
| "700"
|
|
| "800"
|
|
| "900"
|
|
| "950",
|
|
string
|
|
>;
|
|
};
|
|
};
|
|
|
|
export function composSystemTheme() {
|
|
const currentTheme = useState<Theme | null>("system-theme", () => null);
|
|
const isDark = useState<boolean>("is-dark", () => false);
|
|
const isReady = ref(false);
|
|
|
|
onMounted(() => {
|
|
const saved = localStorage.getItem("theme-mode");
|
|
const shouldBeDark = saved === "dark";
|
|
if (isDark.value !== shouldBeDark) {
|
|
isDark.value = shouldBeDark;
|
|
}
|
|
isReady.value = true;
|
|
});
|
|
|
|
// فقط هنگامی که کاربر دستی تم را تغییر داد، ذخیره کن
|
|
watch(
|
|
() => isDark.value,
|
|
(val) => {
|
|
if (!isReady.value || process.server) return;
|
|
|
|
localStorage.setItem("theme-mode", val ? "dark" : "light");
|
|
const root = document.documentElement;
|
|
val ? root.classList.add("dark") : root.classList.remove("dark");
|
|
},
|
|
{ immediate: false }
|
|
);
|
|
|
|
const toggleDarkMode = () => {
|
|
isDark.value = !isDark.value;
|
|
};
|
|
|
|
const applyTheme = async () => {
|
|
const system = useRuntimeConfig().public.system as string;
|
|
if (!system) {
|
|
console.warn("No system specified in runtime config");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 🔹 دقت: مسیر دقیقاً ~/assets/${system}/theme.json
|
|
const themeModule = await import(`~/assets/${system}/theme.json`);
|
|
const theme = themeModule.default || themeModule;
|
|
|
|
if (process.client) {
|
|
const root = document.documentElement;
|
|
|
|
// رنگها
|
|
Object.entries(theme.colors.primary).forEach(([key, value]) => {
|
|
root.style.setProperty(`--color-primary-${key}`, value);
|
|
});
|
|
|
|
// فونت
|
|
if (theme.font && theme.fontFiles) {
|
|
root.style.setProperty("--app-font", theme.font);
|
|
|
|
const fontId = `font-${theme.font}`;
|
|
if (!document.getElementById(fontId)) {
|
|
const style = document.createElement("style");
|
|
style.id = fontId;
|
|
let rules = "";
|
|
theme.fontFiles.forEach((file) => {
|
|
rules += `
|
|
@font-face {
|
|
font-family: "${theme.font}";
|
|
src: url("${file.src}") format("woff2");
|
|
font-weight: ${file.weight};
|
|
font-style: ${file.style};
|
|
font-display: swap;
|
|
}
|
|
`;
|
|
});
|
|
style.textContent = rules;
|
|
document.head.appendChild(style);
|
|
}
|
|
}
|
|
}
|
|
|
|
currentTheme.value = theme;
|
|
return theme;
|
|
} catch (error) {
|
|
console.error(`Failed to load theme for system: ${system}`, error);
|
|
}
|
|
};
|
|
|
|
const logo = computed(() => {
|
|
if (!currentTheme.value) return "";
|
|
return isDark.values
|
|
? currentTheme.value.logo.dark
|
|
: currentTheme.value.logo.light;
|
|
});
|
|
|
|
const primaryColor = computed(() => {
|
|
return currentTheme.value?.colors.primary["500"] || "#3b82f6";
|
|
});
|
|
|
|
return {
|
|
applyTheme,
|
|
currentTheme: currentTheme as Ref<Theme | null>,
|
|
isDark,
|
|
toggleDarkMode,
|
|
logo,
|
|
primaryColor,
|
|
};
|
|
}
|