diff --git a/package-lock.json b/package-lock.json
index fcb1eb8..3b83fa7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,8 +12,10 @@
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/react-fontawesome": "^0.2.0",
"classnames": "^2.5.1",
+ "prop-types": "^15.8.1",
"react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react-dom": "^18.2.0",
+ "styled-components": "^6.1.19"
},
"devDependencies": {
"@types/react": "^18.2.55",
@@ -313,6 +315,27 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
+ "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/memoize": "^0.8.1"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
+ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
+ "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==",
+ "license": "MIT"
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@@ -1600,6 +1623,12 @@
"@types/react": "*"
}
},
+ "node_modules/@types/stylis": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz",
+ "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==",
+ "license": "MIT"
+ },
"node_modules/@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
@@ -1948,6 +1977,15 @@
"node": ">=6"
}
},
+ "node_modules/camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/caniuse-lite": {
"version": "1.0.30001684",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz",
@@ -2057,11 +2095,30 @@
"node": ">= 8"
}
},
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "dev": true,
"license": "MIT"
},
"node_modules/data-view-buffer": {
@@ -3742,7 +3799,6 @@
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -3996,7 +4052,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
@@ -4027,7 +4082,6 @@
"version": "8.4.49",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -4052,6 +4106,12 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "license": "MIT"
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -4429,6 +4489,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -4475,7 +4541,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -4597,6 +4662,40 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/styled-components": {
+ "version": "6.1.19",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.19.tgz",
+ "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/is-prop-valid": "1.2.2",
+ "@emotion/unitless": "0.8.1",
+ "@types/stylis": "4.2.5",
+ "css-to-react-native": "3.2.0",
+ "csstype": "3.1.3",
+ "postcss": "8.4.49",
+ "shallowequal": "1.1.0",
+ "stylis": "4.3.2",
+ "tslib": "2.6.2"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/styled-components"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0",
+ "react-dom": ">= 16.8.0"
+ }
+ },
+ "node_modules/stylis": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
+ "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==",
+ "license": "MIT"
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -4644,6 +4743,12 @@
"node": ">=8.0"
}
},
+ "node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
+ "license": "0BSD"
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
diff --git a/package.json b/package.json
index 222c1fd..14f861e 100644
--- a/package.json
+++ b/package.json
@@ -14,8 +14,10 @@
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/react-fontawesome": "^0.2.0",
"classnames": "^2.5.1",
+ "prop-types": "^15.8.1",
"react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react-dom": "^18.2.0",
+ "styled-components": "^6.1.19"
},
"devDependencies": {
"@types/react": "^18.2.55",
diff --git a/src/App.jsx b/src/App.jsx
index cba7ad0..a23d815 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,14 +1,20 @@
-import React from 'react'
+import React from "react";
import { library } from "@fortawesome/fontawesome-svg-core";
import { fas } from "@fortawesome/free-solid-svg-icons";
import Sidebar from "./components/Sidebar";
+import { ThemeProvider } from "./theme/ThemeContext";
+import ThemeToggle from "./components/ThemeToggle";
library.add(fas);
-export default class App extends React.Component{
- render () {
- return (
-
- )
+export default class App extends React.Component {
+ render() {
+ return (
+
+
+ {/* В зависимости от prop color */}
+
+
+ );
}
}
diff --git a/src/components/Sidebar/CollapseButton.jsx b/src/components/Sidebar/CollapseButton.jsx
new file mode 100644
index 0000000..0d389e4
--- /dev/null
+++ b/src/components/Sidebar/CollapseButton.jsx
@@ -0,0 +1,46 @@
+import styled from "styled-components";
+import PropTypes from "prop-types";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+const CollapseButtonStyled = styled.button`
+ position: absolute;
+ right: ${({ $collapsed }) => ($collapsed ? "-48px" : "-18px")};
+ top: 16px;
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ border: none;
+ background: ${({ theme }) => theme.buttonBackground};
+ color: ${({ theme }) => theme.textDefault};
+ cursor: pointer;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+ transition: right 0.3s ease, background 0.3s, color 0.3s;
+ z-index: 2;
+ &:hover {
+ background: ${({ theme }) => theme.buttonBackgroundActive};
+ color: ${({ theme }) => theme.textHover};
+ }
+`;
+
+const CollapseButton = ({ $collapsed, theme, onClick }) => (
+
+ {$collapsed ? (
+
+ ) : (
+
+ )}
+
+);
+
+CollapseButton.propTypes = {
+ $collapsed: PropTypes.bool.isRequired,
+ theme: PropTypes.object.isRequired,
+ onClick: PropTypes.func.isRequired,
+};
+
+export default CollapseButton;
\ No newline at end of file
diff --git a/src/components/Sidebar/LogoSection.jsx b/src/components/Sidebar/LogoSection.jsx
new file mode 100644
index 0000000..2a15d77
--- /dev/null
+++ b/src/components/Sidebar/LogoSection.jsx
@@ -0,0 +1,34 @@
+import styled from "styled-components";
+import PropTypes from "prop-types";
+import logo from "../../assets/logo.png";
+
+const LogoSectionStyled = styled.div`
+ display: flex;
+ align-items: center;
+ padding-top: 24px;
+ img {
+ width: 32px;
+ margin-right: 12px;
+ }
+ span {
+ color: ${({ theme }) => theme.logoText};
+ font-weight: bold;
+ font-size: 1.2rem;
+ opacity: ${({ $collapsed }) => ($collapsed ? "0" : "1")};
+ transition: opacity 0.3s;
+ }
+`;
+
+const LogoSection = ({ theme, $collapsed }) => (
+
+
+ TensorFlow
+
+);
+
+LogoSection.propTypes = {
+ theme: PropTypes.object.isRequired,
+ $collapsed: PropTypes.bool.isRequired,
+};
+
+export default LogoSection;
diff --git a/src/components/Sidebar/NavItem.jsx b/src/components/Sidebar/NavItem.jsx
new file mode 100644
index 0000000..5d991e0
--- /dev/null
+++ b/src/components/Sidebar/NavItem.jsx
@@ -0,0 +1,74 @@
+import styled from "styled-components";
+import PropTypes from "prop-types";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+const NavItem = ({
+ title,
+ icon,
+ path,
+ onClick,
+ collapsed,
+ isActive = false,
+ theme,
+}) => (
+ onClick(path)}
+ theme={theme}
+ $isActive={isActive}
+ $collapsed={collapsed}
+ >
+
+ {title}
+
+);
+
+NavItem.propTypes = {
+ title: PropTypes.string.isRequired,
+ icon: PropTypes.string.isRequired,
+ path: PropTypes.string.isRequired,
+ onClick: PropTypes.func.isRequired,
+ collapsed: PropTypes.bool,
+ isActive: PropTypes.bool,
+ theme: PropTypes.object,
+};
+const RouteItem = styled.div`
+ min-width: 18px;
+ display: flex;
+ align-items: center;
+ justify-content: start;
+ overflow: hidden;
+ padding: 8px 12px;
+ border-radius: 14px;
+ background: ${({ $isActive, theme }) =>
+ $isActive ? theme.sidebarBackgroundActive : "transparent"};
+ cursor: pointer;
+ color: ${({ $isActive, theme }) =>
+ $isActive ? theme.textActive : theme.textDefault};
+ transition: background 0.3s, color 0.3s;
+ font-weight: bold;
+ &:hover {
+ background: ${({ theme }) => theme.sidebarBackgroundHover};
+ color: ${({ theme }) => theme.textHover};
+
+ svg {
+ color: ${({ theme }) => theme.textHover};
+ }
+ span {
+ color: ${({ theme }) => theme.textHover};
+ }
+ }
+ svg {
+ margin-right: 20px;
+ color: ${({ $isActive, theme }) =>
+ $isActive ? theme.textActive : theme.textDefault};
+ transition: color 0.3s;
+ }
+ span {
+ opacity: ${({ $collapsed }) => ($collapsed ? 0 : 1)};
+ transition: opacity 0.2s, color 0.3s;
+ color: ${({ $isActive, theme }) =>
+ $isActive ? theme.textActive : theme.textDefault};
+ }
+`;
+
+export default NavItem;
diff --git a/src/components/Sidebar/Sidebar.jsx b/src/components/Sidebar/Sidebar.jsx
index 974966e..e538926 100644
--- a/src/components/Sidebar/Sidebar.jsx
+++ b/src/components/Sidebar/Sidebar.jsx
@@ -1,81 +1,106 @@
-import { useState } from 'react';
-import classnames from 'classnames';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import logo from '../../assets/logo.png';
-import PropTypes from 'prop-types';
+import PropTypes from "prop-types";
+import { useMemo, useState } from "react";
+import styled from "styled-components";
+import { BOTTOM_ROUTES, TOP_ROUTES } from "../../constants/sidebarRoutes";
+import { useTheme } from "../../theme/ThemeContext";
+import { darkTheme, lightTheme } from "../../theme/theme";
+import CollapseButton from "./CollapseButton";
+import LogoSection from "./LogoSection";
+import NavItem from "./NavItem";
-const routes = [
- { title: 'Home', icon: 'fas-solid fa-house', path: '/' },
- { title: 'Sales', icon: 'chart-line', path: '/sales' },
- { title: 'Costs', icon: 'chart-column', path: '/costs' },
- { title: 'Payments', icon: 'wallet', path: '/payments' },
- { title: 'Finances', icon: 'chart-pie', path: '/finances' },
- { title: 'Messages', icon: 'envelope', path: '/messages' },
-];
+const Sidebar = ({ color }) => {
+ const { theme } = useTheme();
+ const currentPath = window.location.pathname; // location.pathname from "react-router-dom";
+ const [isCollapsed, setIsCollapsed] = useState(true);
+ const localTheme = useMemo(() => {
+ if (color === "light") return lightTheme;
+ if (color === "dark") return darkTheme;
+ return theme;
+ }, [color, theme]);
-const bottomRoutes = [
- { title: 'Settings', icon: 'sliders', path: '/settings' },
- { title: 'Support', icon: 'phone-volume', path: '/support' },
-];
+ const goToRoute = (path) => {
+ console.log(`going to "${path}"`);
+ };
-const Sidebar = (props) => {
- const { color } = props;
- const [isOpened, setIsOpened] = useState(false);
- const containerClassnames = classnames('sidebar', { opened: isOpened });
-
- const goToRoute = (path) => {
- console.log(`going to "${path}"`);
- };
-
- const toggleSidebar = () => {
- setIsOpened(v => !v);
- };
-
- return (
-
-
-

-
TensorFlow
-
-
-
-
-
- {
- routes.map(route => (
-
{
- goToRoute(route.path);
- }}
- >
-
- { route.title }
-
- ))
- }
-
-
- {
- bottomRoutes.map(route => (
-
{
- goToRoute(route.path);
- }}
- >
-
- { route.title }
-
- ))
- }
-
+ return (
+
+
+
+ setIsCollapsed((prev) => !prev)}
+ />
+
+
+ {TOP_ROUTES?.map((route) => (
+ goToRoute(route.path)}
+ theme={localTheme}
+ isActive={currentPath === route.path}
+ collapsed={isCollapsed}
+ />
+ ))}
+
- );
+
+
+
+ {BOTTOM_ROUTES.map((route) => (
+ goToRoute(route.path)}
+ theme={localTheme}
+ collapsed={isCollapsed}
+ />
+ ))}
+
+
+
+
+ );
};
Sidebar.propTypes = {
- color: PropTypes.string,
+ color: PropTypes.string,
};
+const SidebarContainer = styled.div`
+ width: ${({ $collapsed }) => ($collapsed ? "38px" : "260px")};
+ background: ${({ theme }) => theme.sidebarBackground};
+ color: ${({ theme }) => theme.textDefault};
+ padding: 0px 24px;
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+ transition: width 0.3s ease, background 0.3s, color 0.3s;
+ position: relative;
+`;
+
+const SidebarContent = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ height: 100%;
+`;
+
+const RouteSection = styled.div`
+ margin-top: 32px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+`;
+const BottomSection = styled.div`
+ padding-bottom: 32px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+`;
export default Sidebar;
diff --git a/src/components/ThemeToggle.jsx b/src/components/ThemeToggle.jsx
new file mode 100644
index 0000000..8a4f74a
--- /dev/null
+++ b/src/components/ThemeToggle.jsx
@@ -0,0 +1,36 @@
+import styled from "styled-components";
+import { useTheme } from "../theme/ThemeContext";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faMoon, faSun } from "@fortawesome/free-solid-svg-icons";
+
+const ToggleButton = styled.button`
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ top: 20px;
+ background: ${({ theme }) => theme.buttonBackground};
+ color: ${({ theme }) => theme.textDefault};
+ border: none;
+ border-radius: 6px;
+ padding: 10px 20px;
+ font-size: 1rem;
+ cursor: pointer;
+ margin: 0 auto;
+ display: block;
+ transition: background 0.3s, color 0.3s;
+ &:hover {
+ background: ${({ theme }) => theme.buttonBackgroundActive};
+ color: ${({ theme }) => theme.textHover};
+ }
+`;
+
+const ThemeToggle = () => {
+ const { themeName, toggleTheme, theme } = useTheme();
+ return (
+
+
+
+ );
+};
+
+export default ThemeToggle;
diff --git a/src/constants/sidebarRoutes.js b/src/constants/sidebarRoutes.js
new file mode 100644
index 0000000..caaadf1
--- /dev/null
+++ b/src/constants/sidebarRoutes.js
@@ -0,0 +1,13 @@
+export const TOP_ROUTES = [
+ { title: "Home", icon: "fas-solid fa-house", path: "/" },
+ { title: "Sales", icon: "chart-line", path: "/sales" },
+ { title: "Costs", icon: "chart-column", path: "/costs" },
+ { title: "Payments", icon: "wallet", path: "/payments" },
+ { title: "Finances", icon: "chart-pie", path: "/finances" },
+ { title: "Messages", icon: "envelope", path: "/messages" },
+];
+
+export const BOTTOM_ROUTES = [
+ { title: "Settings", icon: "sliders", path: "/settings" },
+ { title: "Support", icon: "phone-volume", path: "/support" },
+];
diff --git a/src/index.scss b/src/index.scss
index 01632cd..e5c5f40 100644
--- a/src/index.scss
+++ b/src/index.scss
@@ -1,25 +1,25 @@
:root {
- // dark
- --color-sidebar-background-dark-default: #202127;
- --color-sidebar-background-dark-hover: #2D2E34;
- --color-sidebar-background-dark-active: #393A3F;
- --color-text-dark-default: #f0f2ff;
- --color-text-dark-hover: #f0f2ff;
- --color-text-dark-active: #f0f2ff;
- --color-text-logo-dark-default: #3B82F6;
- --color-button-background-dark-default: #202127;
- --color-button-background-dark-active: #4B5966;
+ // dark
+ --color-sidebar-background-dark-default: #202127;
+ --color-sidebar-background-dark-hover: #2d2e34;
+ --color-sidebar-background-dark-active: #393a3f;
+ --color-text-dark-default: #f0f2ff;
+ --color-text-dark-hover: #f0f2ff;
+ --color-text-dark-active: #f0f2ff;
+ --color-text-logo-dark-default: #3b82f6;
+ --color-button-background-dark-default: #202127;
+ --color-button-background-dark-active: #4b5966;
- // light
- --color-sidebar-background-light-default: #fff;
- --color-sidebar-background-light-hover: #f0f2ff;
- --color-sidebar-background-light-active: #f0f2ff;
- --color-text-light-default: #97a5b9;
- --color-text-light-hover: #091b31;
- --color-text-light-active: #0000b5;
- --color-text-logo-light-default: #0000b5;
- --color-button-background-light-default: #fff;
- --color-button-background-light-active: #e2e8f0;
+ // light
+ --color-sidebar-background-light-default: #fff;
+ --color-sidebar-background-light-hover: #f0f2ff;
+ --color-sidebar-background-light-active: #f0f2ff;
+ --color-text-light-default: #97a5b9;
+ --color-text-light-hover: #091b31;
+ --color-text-light-active: #0000b5;
+ --color-text-logo-light-default: #0000b5;
+ --color-button-background-light-default: #fff;
+ --color-button-background-light-active: #e2e8f0;
}
html {
@@ -29,3 +29,7 @@ html {
background-color: #e2e8f0;
color: rgba(255, 255, 255, 0.87);
}
+
+body {
+ margin: 0px;
+}
diff --git a/src/theme/ThemeContext.jsx b/src/theme/ThemeContext.jsx
new file mode 100644
index 0000000..726b826
--- /dev/null
+++ b/src/theme/ThemeContext.jsx
@@ -0,0 +1,39 @@
+import { createContext, useContext, useState, useMemo } from "react";
+import { lightTheme, darkTheme } from "./theme";
+import PropTypes from "prop-types";
+import { getLocalStorage, setLocalStorage } from "../utils/localStorage";
+
+const ThemeContext = createContext();
+
+export const ThemeProvider = ({ children }) => {
+ const getDefaultTheme = () => {
+ const stored = getLocalStorage("themeName");
+ if (stored) return stored;
+ if (
+ window.matchMedia &&
+ window.matchMedia("(prefers-color-scheme: dark)").matches
+ ) {
+ return "dark";
+ }
+ return "light";
+ };
+ const [themeName, setThemeName] = useState(getDefaultTheme);
+ const theme = useMemo(
+ () => (themeName === "light" ? lightTheme : darkTheme),
+ [themeName]
+ );
+ const toggleTheme = () => {
+ const otherTheme = themeName === "light" ? "dark" : "light";
+ setThemeName(otherTheme);
+ setLocalStorage("themeName", otherTheme);
+ };
+ return (
+
+ {children}
+
+ );
+};
+ThemeProvider.propTypes = {
+ children: PropTypes.node.isRequired,
+};
+export const useTheme = () => useContext(ThemeContext);
diff --git a/src/theme/theme.js b/src/theme/theme.js
new file mode 100644
index 0000000..072b64c
--- /dev/null
+++ b/src/theme/theme.js
@@ -0,0 +1,23 @@
+export const lightTheme = {
+ sidebarBackground: "#fff",
+ sidebarBackgroundHover: "#f0f2ff",
+ sidebarBackgroundActive: "#f0f2ff",
+ textDefault: "#97a5b9",
+ textHover: "#091b31",
+ textActive: "#0000b5",
+ logoText: "#0000b5",
+ buttonBackground: "#fff",
+ buttonBackgroundActive: "#e2e8f0",
+};
+
+export const darkTheme = {
+ sidebarBackground: "#202127",
+ sidebarBackgroundHover: "#2D2E34",
+ sidebarBackgroundActive: "#393A3F",
+ textDefault: "#f0f2ff",
+ textHover: "#f0f2ff",
+ textActive: "#f0f2ff",
+ logoText: "#3B82F6",
+ buttonBackground: "#202127",
+ buttonBackgroundActive: "#4B5966",
+};
diff --git a/src/utils/localStorage.js b/src/utils/localStorage.js
new file mode 100644
index 0000000..a747a1d
--- /dev/null
+++ b/src/utils/localStorage.js
@@ -0,0 +1,18 @@
+export const getLocalStorage = (key, defaultValue) => {
+ if (typeof window === "undefined") return defaultValue;
+ try {
+ const stored = window.localStorage.getItem(key);
+ return stored !== null ? JSON.parse(stored) : defaultValue;
+ } catch {
+ return defaultValue;
+ }
+};
+
+export const setLocalStorage = (key, value) => {
+ if (typeof window === "undefined") return;
+ try {
+ window.localStorage.setItem(key, JSON.stringify(value));
+ } catch (error) {
+ console.error("Failed to save to localStorage:", error);
+ }
+};