diff --git a/commitlint.config.ts b/commitlint.config.ts index 05556c9..7e26a14 100644 --- a/commitlint.config.ts +++ b/commitlint.config.ts @@ -38,6 +38,7 @@ const Configuration: UserConfig = { 'build', // Build scripts or configuration 'ci', // Continuous integration 'release', // Release related changes + 'theme', // Theme changes 'other', // Other changes ], ], diff --git a/src/components/project/NewProject/NewProject.module.scss b/src/components/project/NewProject/NewProject.module.scss index 6108965..00a076e 100644 --- a/src/components/project/NewProject/NewProject.module.scss +++ b/src/components/project/NewProject/NewProject.module.scss @@ -56,10 +56,7 @@ [class*='ant-radio-button-checked'] { border: solid 1px transparent; - background-image: linear-gradient(rgb(20, 20, 20), rgb(20, 20, 20)), - var(--primary-gradient); - background-origin: border-box; - background-clip: content-box, border-box; + background: var(--primary); border-radius: var(--border-radius); &::before { @@ -72,6 +69,9 @@ background: #000; z-index: -1; border-radius: inherit; + [data-theme='light'] & { + background: var(--primary); + } } } } diff --git a/src/components/shared/LogView/LogView.module.scss b/src/components/shared/LogView/LogView.module.scss index f99ef14..f0240b1 100644 --- a/src/components/shared/LogView/LogView.module.scss +++ b/src/components/shared/LogView/LogView.module.scss @@ -57,4 +57,10 @@ display: inline-block; margin-left: 0.5rem; } + [class*='xterm-cursor-outline'] { + outline-color: var(--text-color) !important; + } + [class*='xterm-cursor-bar'] { + box-shadow: 1px 0 0 var(--text-color) inset !important; + } } diff --git a/src/components/shared/LogView/LogView.tsx b/src/components/shared/LogView/LogView.tsx index cc1de19..7163d4b 100644 --- a/src/components/shared/LogView/LogView.tsx +++ b/src/components/shared/LogView/LogView.tsx @@ -37,7 +37,7 @@ const LogView: FC = ({ filter }) => { grey: '\x1b[38;5;243m', success: '\x1b[38;5;40m', error: '\x1b[38;5;196m', - warning: '\x1b[38;5;226m', + warning: '\x1b[38;5;214m', info: '\x1b[38;5;33m', reset: '\x1b[0m', }; diff --git a/src/components/shared/ThemeProvider/ThemeProvider.tsx b/src/components/shared/ThemeProvider/ThemeProvider.tsx new file mode 100644 index 0000000..ceed820 --- /dev/null +++ b/src/components/shared/ThemeProvider/ThemeProvider.tsx @@ -0,0 +1,74 @@ +import { ConfigProvider, theme as antdTheme } from 'antd'; +import { + ReactNode, + createContext, + useContext, + useEffect, + useState, +} from 'react'; + +type Theme = 'light' | 'dark'; + +interface ThemeContextProps { + theme: Theme; + toggleTheme: () => void; +} + +export const ThemeContext = createContext( + undefined, +); + +export const useTheme = () => { + const context = useContext(ThemeContext); + if (!context) { + throw new Error('useTheme must be used within a ThemeProvider'); + } + return context; +}; + +export const ThemeProvider = ({ children }: { children: ReactNode }) => { + // Initialize theme based on local storage or system preference + const [theme, setTheme] = useState(() => { + if (typeof window === 'undefined') { + return 'dark'; + } + const savedTheme = localStorage.getItem('theme'); + if (savedTheme) { + return savedTheme as Theme; + } + const prefersDark = window.matchMedia( + '(prefers-color-scheme: dark)', + ).matches; + return prefersDark ? 'dark' : 'light'; + }); + + // Apply the theme to the HTML element and save it in localStorage + useEffect(() => { + document.documentElement.setAttribute('data-theme', theme); + localStorage.setItem('theme', theme); + }, [theme]); + + const toggleTheme = () => { + setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light')); + }; + + // Ant Design's theme configuration + const antdConfig = { + token: { + colorPrimary: '#0098ea', + colorError: '#C84075', + fontFamily: 'var(--font-body)', + borderRadius: 4, + }, + algorithm: + theme === 'dark' ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm, + }; + + return ( + + {children} + + ); +}; + +export default ThemeProvider; diff --git a/src/components/shared/ThemeProvider/index.ts b/src/components/shared/ThemeProvider/index.ts new file mode 100644 index 0000000..c3d210e --- /dev/null +++ b/src/components/shared/ThemeProvider/index.ts @@ -0,0 +1 @@ +export { ThemeContext, default, useTheme } from './ThemeProvider'; diff --git a/src/components/shared/index.ts b/src/components/shared/index.ts index 94d5a8a..21bfb55 100644 --- a/src/components/shared/index.ts +++ b/src/components/shared/index.ts @@ -1,2 +1,3 @@ export { default as Layout } from './Layout'; export { default as LogView } from './LogView'; +export { default as ThemeProvider } from './ThemeProvider'; diff --git a/src/components/template/ProjectTemplate/ProjectTemplate.tsx b/src/components/template/ProjectTemplate/ProjectTemplate.tsx index 4ce3ce8..65e166e 100644 --- a/src/components/template/ProjectTemplate/ProjectTemplate.tsx +++ b/src/components/template/ProjectTemplate/ProjectTemplate.tsx @@ -1,5 +1,6 @@ /* eslint-disable react/no-children-prop */ import { NewProject } from '@/components/project'; +import { useTheme } from '@/components/shared/ThemeProvider'; import AppIcon from '@/components/ui/icon'; import { AppConfig } from '@/config/AppConfig'; import { projectExamples } from '@/constant/projectExamples'; @@ -9,7 +10,10 @@ import Link from 'next/link'; import { FC, useEffect, useState } from 'react'; import Markdown from 'react-markdown'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; -import { oneDark as darkTheme } from 'react-syntax-highlighter/dist/cjs/styles/prism'; +import { + oneDark as darkTheme, + oneLight as lightTheme, +} from 'react-syntax-highlighter/dist/cjs/styles/prism'; import s from './ProjectTemplate.module.scss'; function LinkRenderer({ @@ -35,6 +39,7 @@ const ProjectTemplate: FC = () => { contract: string; content: string; }>({ contract: '', content: '' }); + const { theme } = useTheme(); const getContent = async () => { const link = examples[currentExample].link; @@ -166,7 +171,7 @@ const ProjectTemplate: FC = () => { PreTag="div" children={String(children).replace(/\n$/, '')} language={match[1]} - style={darkTheme} + style={theme === 'dark' ? darkTheme : lightTheme} /> ) : ( diff --git a/src/components/ui/icon/index.tsx b/src/components/ui/icon/index.tsx index c7de7a0..a1147e2 100644 --- a/src/components/ui/icon/index.tsx +++ b/src/components/ui/icon/index.tsx @@ -12,7 +12,7 @@ import { import { BsShare } from 'react-icons/bs'; import { BsFillPlayFill } from 'react-icons/bs'; -import { FaRegClone } from 'react-icons/fa'; +import { FaMoon, FaRegClone, FaSun } from 'react-icons/fa'; import { FiEdit2, FiEye } from 'react-icons/fi'; import { GoTriangleDown, GoTriangleRight, GoTriangleUp } from 'react-icons/go'; import { GrClear } from 'react-icons/gr'; @@ -69,7 +69,9 @@ export type AppIconType = | 'Import' | 'Reload' | 'Share' - | 'Save'; + | 'Save' + | 'Moon' + | 'Sun'; export interface AppIconInterface { name: AppIconType; @@ -110,6 +112,8 @@ const Components = { Reload: AiOutlineReload, Share: BsShare, Save: AiOutlineSave, + Moon: FaMoon, + Sun: FaSun, }; const AppIcon: FC = ({ name, className = '' }) => { diff --git a/src/components/workspace/ABIUi/ABIUi.module.scss b/src/components/workspace/ABIUi/ABIUi.module.scss index 4eed051..35bc504 100644 --- a/src/components/workspace/ABIUi/ABIUi.module.scss +++ b/src/components/workspace/ABIUi/ABIUi.module.scss @@ -12,6 +12,9 @@ height: 1px; margin: 0 auto; background-color: #373636; + [data-theme='light'] & { + background-color: #e0e0e0; + } } &.tact { display: flex; @@ -30,6 +33,9 @@ background: rgba(14, 14, 16, 0.9); border-radius: 5px; padding: 5px; + [data-theme='light'] & { + background: rgba(255, 255, 255, 0.9); + } > div { border-left: 0 !important; @@ -62,27 +68,40 @@ border: 0; box-shadow: none; background: #232222; + [data-theme='light'] & { + background: #d6d6d6; + } + &:hover { + color: var(--text-color); + background-color: var(--grey--50); + } } } &.Setter { .btnAction { border: solid 1px transparent; - background-image: linear-gradient(rgb(20, 20, 20), rgb(20, 20, 20)), - var(--primary-gradient); - background-origin: border-box; - background-clip: content-box, border-box; + background: var(--primary); box-shadow: none; z-index: 0; + &:hover { border-color: transparent; - color: #fff; + color: var(--text-color); + &:before { background: #101010; + [data-theme='light'] & { + background: var(--btn-primary-hover); + } } } + & span, + &:hover span [data-theme='light'] & { + color: #fff; + } &:disabled { opacity: 0.5; - color: #fff; + color: var(--text-color); } &:before { @@ -95,6 +114,9 @@ background: #000; z-index: -1; border-radius: inherit; + [data-theme='light'] & { + background: var(--primary); + } } } } diff --git a/src/components/workspace/BottomPanel/BottomPanel.module.scss b/src/components/workspace/BottomPanel/BottomPanel.module.scss index ad26933..fa08640 100644 --- a/src/components/workspace/BottomPanel/BottomPanel.module.scss +++ b/src/components/workspace/BottomPanel/BottomPanel.module.scss @@ -1,5 +1,5 @@ .root { - background-color: #181717; + background-color: var(--dark-800); height: 100%; z-index: 1000; position: relative; @@ -43,11 +43,11 @@ justify-content: center; .icon { path { - stroke: #fff; + stroke: var(--text-color); } } &:hover { - background-color: var(--grey); + background-color: var(--grey--70); } } } diff --git a/src/components/workspace/BuildProject/BuildProject.module.scss b/src/components/workspace/BuildProject/BuildProject.module.scss index aa7085a..be4969a 100644 --- a/src/components/workspace/BuildProject/BuildProject.module.scss +++ b/src/components/workspace/BuildProject/BuildProject.module.scss @@ -4,7 +4,7 @@ height: 100vh; overflow-y: auto; .heading { - color: #ccc; + color: var(--color-light); text-align: center; display: block; font-size: 0.875rem; @@ -28,6 +28,9 @@ background: rgba(14, 14, 16, 0.9); border-radius: 5px; padding: 2px; + [data-theme='light'] & { + background: rgba(255, 255, 255, 0.9); + } > div { border-left: 0 !important; } @@ -42,11 +45,11 @@ } .connectedWallet { font-size: 0.8rem; - color: #ccc; + color: var(--color-light); margin-top: 1rem; span { font-size: 0.9rem; - color: #fff; + color: var(--text-color); } } .contractAddress { diff --git a/src/components/workspace/Editor/Editor.module.scss b/src/components/workspace/Editor/Editor.module.scss index c8f1aad..67b030c 100644 --- a/src/components/workspace/Editor/Editor.module.scss +++ b/src/components/workspace/Editor/Editor.module.scss @@ -14,6 +14,9 @@ justify-content: space-between; font-size: 0.8rem; padding: 0.2rem 1rem; + [data-theme='light'] & { + background-color: rgba(255, 255, 255, 0.7); + } .vimStatuBar { font-family: monospace; } diff --git a/src/components/workspace/Editor/Editor.tsx b/src/components/workspace/Editor/Editor.tsx index 2ff310c..56d0d91 100644 --- a/src/components/workspace/Editor/Editor.tsx +++ b/src/components/workspace/Editor/Editor.tsx @@ -7,6 +7,7 @@ import EditorDefault, { loader } from '@monaco-editor/react'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { FC, useEffect, useRef, useState } from 'react'; // import { useLatest } from 'react-use'; +import { useTheme } from '@/components/shared/ThemeProvider'; import { useFile, useFileTab } from '@/hooks'; import { useProject } from '@/hooks/projectV2.hooks'; import { useLatest } from 'react-use'; @@ -23,6 +24,7 @@ const Editor: FC = ({ className = '' }) => { const { activeProject } = useProject(); const { getFile, saveFile: storeFileContent } = useFile(); const { fileTab, updateFileDirty } = useFileTab(); + const { theme } = useTheme(); const { isFormatOnSave, getSettingStateByKey } = useSettingAction(); @@ -249,7 +251,7 @@ const Editor: FC = ({ className = '' }) => {