home page #1
Generated
+10
@@ -12,6 +12,7 @@
|
||||
"axios": "^1.17.0",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6",
|
||||
"react-icons": "^5.6.0",
|
||||
"react-router-dom": "^7.17.0",
|
||||
"tailwindcss": "^4.3.0"
|
||||
},
|
||||
@@ -2958,6 +2959,15 @@
|
||||
"react": "^19.2.7"
|
||||
}
|
||||
},
|
||||
"node_modules/react-icons": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.6.0.tgz",
|
||||
"integrity": "sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.17.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.17.0.tgz",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"axios": "^1.17.0",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6",
|
||||
"react-icons": "^5.6.0",
|
||||
"react-router-dom": "^7.17.0",
|
||||
"tailwindcss": "^4.3.0"
|
||||
},
|
||||
|
||||
+4
-1
@@ -1,9 +1,12 @@
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import App from "./app/App.tsx";
|
||||
import { ThemeInitialProvider } from "@/modules/theme-changer";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
<ThemeInitialProvider>
|
||||
<App />
|
||||
</ThemeInitialProvider>
|
||||
</BrowserRouter>,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
export const themes = [
|
||||
{
|
||||
id: "light",
|
||||
name: "Светлая",
|
||||
description: "Чистая светлая тема",
|
||||
type: "light",
|
||||
colors: {
|
||||
primary: "#4f46e5",
|
||||
background: "#ffffff",
|
||||
surface: "#f8fafc",
|
||||
text: "#1f2937",
|
||||
border: "#e5e7eb",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "dark",
|
||||
name: "Темная",
|
||||
description: "Элегантная темная тема",
|
||||
type: "dark",
|
||||
colors: {
|
||||
primary: "#6366f1",
|
||||
background: "#0f172a",
|
||||
surface: "#1e293b",
|
||||
text: "#f1f5f9",
|
||||
border: "#334155",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "nightowl",
|
||||
name: "Night Owl",
|
||||
description: "Тема вдохновленная редактором кода",
|
||||
type: "dark",
|
||||
colors: {
|
||||
primary: "#7dd3fc",
|
||||
background: "#011627",
|
||||
surface: "#011e3c",
|
||||
text: "#d6deeb",
|
||||
border: "#1d3b53",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "sunset",
|
||||
name: "Закат",
|
||||
description: "Теплые оранжевые тона",
|
||||
type: "dark",
|
||||
colors: {
|
||||
primary: "#f97316",
|
||||
background: "#1c1917",
|
||||
surface: "#292524",
|
||||
text: "#fafaf9",
|
||||
border: "#57534e",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "forest",
|
||||
name: "Лес",
|
||||
description: "Успокаивающая зеленая тема",
|
||||
type: "dark",
|
||||
colors: {
|
||||
primary: "#22c55e",
|
||||
background: "#052e16",
|
||||
surface: "#14532d",
|
||||
text: "#f0fdf4",
|
||||
border: "#166534",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "ocean",
|
||||
name: "Океан",
|
||||
description: "Глубокие синие тона",
|
||||
type: "dark",
|
||||
colors: {
|
||||
primary: "#06b6d4",
|
||||
background: "#164e63",
|
||||
surface: "#0e7490",
|
||||
text: "#f0fdfd",
|
||||
border: "#0891b2",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "lavender",
|
||||
name: "Лаванда",
|
||||
description: "Нежная фиолетовая тема",
|
||||
type: "light",
|
||||
colors: {
|
||||
primary: "#a855f7",
|
||||
background: "#faf5ff",
|
||||
surface: "#f3e8ff",
|
||||
text: "#581c87",
|
||||
border: "#e9d5ff",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "coffee",
|
||||
name: "Кофе",
|
||||
description: "Уютная коричневая тема",
|
||||
type: "dark",
|
||||
colors: {
|
||||
primary: "#d97706",
|
||||
background: "#292524",
|
||||
surface: "#44403c",
|
||||
text: "#f5f5f4",
|
||||
border: "#57534e",
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,2 @@
|
||||
export { ThemeInitialProvider } from "./provider/theme.initial.provider";
|
||||
export { ThemeToggle } from "./ui/Theme.toggle";
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useLayoutEffect } from "react";
|
||||
import { applyTheme, initializeTheme } from "../utils/apply.theme";
|
||||
|
||||
export const ThemeInitialProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
useLayoutEffect(() => {
|
||||
const theme = initializeTheme();
|
||||
applyTheme(theme);
|
||||
}, []);
|
||||
|
||||
return children;
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
export interface ITheme {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
type: string;
|
||||
colors: {
|
||||
primary: string;
|
||||
background: string;
|
||||
surface: string;
|
||||
text: string;
|
||||
border: string;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { FiSun, FiMoon } from "react-icons/fi";
|
||||
import {
|
||||
getCurrentTheme,
|
||||
toggleDarkLight,
|
||||
getSavedTheme,
|
||||
} from "../../theme-changer/utils/apply.theme";
|
||||
import { themes } from "../../theme-changer/config/theme.config";
|
||||
|
||||
export const ThemeToggle: React.FC = () => {
|
||||
const [currentTheme, setCurrentTheme] = useState<string>(() =>
|
||||
getSavedTheme(),
|
||||
);
|
||||
|
||||
const currentThemeData = themes.find((t) => t.id === currentTheme);
|
||||
const isDark = currentThemeData?.type === "dark";
|
||||
|
||||
const handleClick = () => {
|
||||
const newTheme = toggleDarkLight();
|
||||
setCurrentTheme(newTheme);
|
||||
};
|
||||
|
||||
// Инициализация при монтировании
|
||||
useEffect(() => {
|
||||
const saved = getSavedTheme();
|
||||
const current = getCurrentTheme() || saved;
|
||||
setCurrentTheme(current);
|
||||
}, []);
|
||||
|
||||
// Слушаем изменения темы из других компонентов
|
||||
useEffect(() => {
|
||||
const handleThemeChange = (e: Event) => {
|
||||
const event = e as CustomEvent;
|
||||
setCurrentTheme(event.detail.theme);
|
||||
};
|
||||
window.addEventListener("themechange", handleThemeChange);
|
||||
return () => window.removeEventListener("themechange", handleThemeChange);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={handleClick}
|
||||
className="p-2 rounded-lg transition-colors duration-200"
|
||||
style={{
|
||||
backgroundColor: "var(--bg-secondary)",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = "var(--bg-tertiary)";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = "var(--bg-secondary)";
|
||||
}}
|
||||
aria-label="Переключить тему"
|
||||
title={
|
||||
isDark ? "Переключить на светлую тему" : "Переключить на тёмную тему"
|
||||
}
|
||||
>
|
||||
{isDark ? (
|
||||
<FiSun className="w-5 h-5" style={{ color: "var(--accent)" }} />
|
||||
) : (
|
||||
<FiMoon
|
||||
className="w-5 h-5"
|
||||
style={{ color: "var(--text-secondary)" }}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
import { themes } from "../config/theme.config";
|
||||
|
||||
export const applyTheme = (themeId: string) => {
|
||||
const theme = themes.find((t) => t.id === themeId);
|
||||
const root = document.documentElement;
|
||||
|
||||
if (theme) {
|
||||
try {
|
||||
root.setAttribute("data-theme", themeId);
|
||||
localStorage.setItem("theme", themeId);
|
||||
localStorage.setItem("theme-type", theme.type);
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("themechange", {
|
||||
detail: { theme: themeId, type: theme.type },
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("❌ Error applying theme:", error);
|
||||
}
|
||||
} else {
|
||||
console.warn(`⚠️ Theme not found: ${themeId}, falling back to light theme`);
|
||||
applyTheme("light");
|
||||
}
|
||||
};
|
||||
|
||||
export const getSavedTheme = () => {
|
||||
try {
|
||||
return localStorage.getItem("theme") || "light";
|
||||
} catch (error) {
|
||||
console.error("Error reading theme from localStorage:", error);
|
||||
return "light";
|
||||
}
|
||||
};
|
||||
|
||||
export const initializeTheme = () => {
|
||||
const savedTheme = getSavedTheme();
|
||||
|
||||
const themeExists = themes.some((t) => t.id === savedTheme);
|
||||
const themeToApply = themeExists ? savedTheme : "light";
|
||||
|
||||
applyTheme(themeToApply);
|
||||
return themeToApply;
|
||||
};
|
||||
|
||||
export const getCurrentTheme = () => {
|
||||
return document.documentElement.getAttribute("data-theme") || "light";
|
||||
};
|
||||
|
||||
export const getCurrentThemeType = () => {
|
||||
const currentTheme = getCurrentTheme();
|
||||
const theme = themes.find((t) => t.id === currentTheme);
|
||||
return theme ? theme.type : "light";
|
||||
};
|
||||
|
||||
export const toggleDarkLight = () => {
|
||||
const currentTheme = getCurrentTheme();
|
||||
const currentThemeData = themes.find((t) => t.id === currentTheme);
|
||||
|
||||
if (currentThemeData) {
|
||||
const oppositeThemes = themes.filter(
|
||||
(t) => t.type !== currentThemeData.type,
|
||||
);
|
||||
if (oppositeThemes.length > 0) {
|
||||
applyTheme(oppositeThemes[0].id);
|
||||
return oppositeThemes[0].id;
|
||||
}
|
||||
}
|
||||
|
||||
const newTheme = currentTheme === "light" ? "dark" : "light";
|
||||
applyTheme(newTheme);
|
||||
return newTheme;
|
||||
};
|
||||
|
||||
export const getNextTheme = () => {
|
||||
const currentTheme = getCurrentTheme();
|
||||
const currentIndex = themes.findIndex((t) => t.id === currentTheme);
|
||||
const nextIndex = (currentIndex + 1) % themes.length;
|
||||
return themes[nextIndex].id;
|
||||
};
|
||||
|
||||
export const applySystemTheme = () => {
|
||||
if (
|
||||
window.matchMedia &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
) {
|
||||
applyTheme("dark");
|
||||
} else {
|
||||
applyTheme("light");
|
||||
}
|
||||
};
|
||||
|
||||
export const watchSystemTheme = () => {
|
||||
if (window.matchMedia) {
|
||||
window
|
||||
.matchMedia("(prefers-color-scheme: dark)")
|
||||
.addEventListener("change", (e) => {
|
||||
if (e.matches) {
|
||||
applyTheme("dark");
|
||||
} else {
|
||||
applyTheme("light");
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
+11
-1
@@ -1,3 +1,13 @@
|
||||
import { ThemeToggle } from "@/modules/theme-changer/ui/Theme.toggle";
|
||||
|
||||
export const HomePage = () => {
|
||||
return <div className="bg-red-500">Домашняя</div>;
|
||||
return (
|
||||
<div
|
||||
style={{ backgroundColor: "var(--bg-primary)" }}
|
||||
className="min-h-screen flex items-center justify-center p-4"
|
||||
>
|
||||
<ThemeToggle />
|
||||
Домашняя
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user