conflict-nuxt-4/app/composables/composSystemTheme.ts
2026-02-12 11:24:27 +03:30

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,
};
}