home page #1
Generated
+10
@@ -12,6 +12,7 @@
|
|||||||
"axios": "^1.17.0",
|
"axios": "^1.17.0",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.6",
|
"react-dom": "^19.2.6",
|
||||||
|
"react-icons": "^5.6.0",
|
||||||
"react-router-dom": "^7.17.0",
|
"react-router-dom": "^7.17.0",
|
||||||
"tailwindcss": "^4.3.0"
|
"tailwindcss": "^4.3.0"
|
||||||
},
|
},
|
||||||
@@ -2958,6 +2959,15 @@
|
|||||||
"react": "^19.2.7"
|
"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": {
|
"node_modules/react-router": {
|
||||||
"version": "7.17.0",
|
"version": "7.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.17.0.tgz",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"axios": "^1.17.0",
|
"axios": "^1.17.0",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.6",
|
"react-dom": "^19.2.6",
|
||||||
|
"react-icons": "^5.6.0",
|
||||||
"react-router-dom": "^7.17.0",
|
"react-router-dom": "^7.17.0",
|
||||||
"tailwindcss": "^4.3.0"
|
"tailwindcss": "^4.3.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import App from "./app/App.tsx";
|
import App from "./app/App.tsx";
|
||||||
|
import { ThemeInitialProvider } from "@/modules/theme-changer";
|
||||||
|
|
||||||
createRoot(document.getElementById("root")!).render(
|
createRoot(document.getElementById("root")!).render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
|
<ThemeInitialProvider>
|
||||||
<App />
|
<App />
|
||||||
|
</ThemeInitialProvider>
|
||||||
</BrowserRouter>,
|
</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 = () => {
|
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