From 908e7f054a2a8aad84ec6cdb0e18bc17eb0cbb73 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Jun 2025 02:59:50 +0000
Subject: [PATCH 1/4] Initial plan for issue
From e0b77c181e7a19410a69e596964135d4b4096187 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Jun 2025 03:15:19 +0000
Subject: [PATCH 2/4] Implement VS Code-inspired dark theme with complete
system
Co-authored-by: unbug <799578+unbug@users.noreply.github.com>
---
__static/app/resources/css/prettify.css | 64 ++++++-
docs/DARK_THEME.md | 216 ++++++++++++++++++++++++
src/App.js | 5 +-
src/components/ThemeToggle.js | 50 ++++++
src/containers/NavBarContainer.js | 2 +
src/hooks/useTheme.js | 112 ++++++++++++
styles/_common.scss | 8 +-
styles/_constants.scss | 45 ++++-
styles/_donate.scss | 4 +-
styles/_main-container.scss | 34 ++--
styles/_nav-bar-container.scss | 28 ++-
styles/_notice-container.scss | 14 +-
styles/_semantic-ui-overrides.scss | 164 ++++++++++++++++++
styles/_source-code.scss | 19 ++-
styles/_theme-variables.scss | 54 ++++++
styles/_utils.scss | 6 +-
styles/app.scss | 2 +
17 files changed, 786 insertions(+), 41 deletions(-)
create mode 100644 docs/DARK_THEME.md
create mode 100644 src/components/ThemeToggle.js
create mode 100644 src/hooks/useTheme.js
create mode 100644 styles/_semantic-ui-overrides.scss
create mode 100644 styles/_theme-variables.scss
diff --git a/__static/app/resources/css/prettify.css b/__static/app/resources/css/prettify.css
index 9c2959af..29848dec 100644
--- a/__static/app/resources/css/prettify.css
+++ b/__static/app/resources/css/prettify.css
@@ -2,6 +2,11 @@
color: #000
}
+/* Dark theme code highlighting */
+body.dark .pln {
+ color: #cccccc;
+}
+
@media screen {
.str {
color: #080
@@ -27,6 +32,31 @@
color: #660
}
+ /* Dark theme syntax highlighting */
+ body.dark .str {
+ color: #ce9178; /* VS Code string color */
+ }
+
+ body.dark .kwd {
+ color: #569cd6; /* VS Code keyword color */
+ }
+
+ body.dark .com {
+ color: #6a9955; /* VS Code comment color */
+ }
+
+ body.dark .typ {
+ color: #4ec9b0; /* VS Code type color */
+ }
+
+ body.dark .lit {
+ color: #b5cea8; /* VS Code literal color */
+ }
+
+ body.dark .pun, body.dark .opn, body.dark .clo {
+ color: #cccccc; /* VS Code punctuation color */
+ }
+
.tag {
color: #008
}
@@ -46,6 +76,27 @@
.fun {
color: red
}
+
+ /* Dark theme additional syntax highlighting */
+ body.dark .tag {
+ color: #569cd6; /* VS Code tag color */
+ }
+
+ body.dark .atn {
+ color: #9cdcfe; /* VS Code attribute name color */
+ }
+
+ body.dark .atv {
+ color: #ce9178; /* VS Code attribute value color */
+ }
+
+ body.dark .dec, body.dark .var {
+ color: #9cdcfe; /* VS Code declaration/variable color */
+ }
+
+ body.dark .fun {
+ color: #dcdcaa; /* VS Code function color */
+ }
}
@media print, projection {
@@ -92,7 +143,14 @@
pre.prettyprint {
padding: 2px;
- border: 1px solid #888
+ border: 1px solid #888;
+ background: #f8f8f8;
+}
+
+body.dark pre.prettyprint {
+ background: #1e1e1e;
+ border: 1px solid #3e3e3e;
+ color: #cccccc;
}
ol.linenums {
@@ -107,3 +165,7 @@ li.L0, li.L1, li.L2, li.L3, li.L5, li.L6, li.L7, li.L8 {
li.L1, li.L3, li.L5, li.L7, li.L9 {
background: #eee
}
+
+body.dark li.L1, body.dark li.L3, body.dark li.L5, body.dark li.L7, body.dark li.L9 {
+ background: #2d2d30;
+}
diff --git a/docs/DARK_THEME.md b/docs/DARK_THEME.md
new file mode 100644
index 00000000..77246c0c
--- /dev/null
+++ b/docs/DARK_THEME.md
@@ -0,0 +1,216 @@
+# Dark Theme Implementation for Codelf
+
+## Overview
+
+This document describes the implementation of a VS Code-inspired dark theme for the Codelf project. The implementation includes a complete theme system with automatic system theme detection, manual theme switching, and comprehensive styling for all UI components.
+
+## Features
+
+### Theme System
+- **Auto Detection**: Automatically detects user's system theme preference (`prefers-color-scheme`)
+- **Manual Override**: Users can manually switch between light, dark, and auto modes
+- **Persistence**: Theme preference is saved in localStorage and persists across sessions
+- **Smooth Transitions**: All theme switches include smooth CSS transitions
+
+### Theme Options
+1. **Light Theme**: Clean, bright interface (default)
+2. **Dark Theme**: VS Code-inspired dark interface with proper contrast
+3. **Auto Theme**: Follows system preference and responds to system theme changes
+
+## Implementation Details
+
+### File Structure
+
+```
+styles/
+├── _constants.scss # VS Code color constants
+├── _theme-variables.scss # CSS custom properties for theming
+├── _semantic-ui-overrides.scss # Dark theme overrides for Semantic UI
+└── app.scss # Main stylesheet importing all theme files
+
+src/
+├── hooks/
+│ └── useTheme.js # React theme management hook
+├── components/
+│ └── ThemeToggle.js # Theme toggle dropdown component
+└── App.js # Updated with ThemeProvider
+```
+
+### Color Scheme
+
+The dark theme uses VS Code's color palette:
+
+#### Background Colors
+- **Primary Background**: `#1e1e1e` (editor background)
+- **Secondary Background**: `#252526` (sidebar background)
+- **Input Background**: `#3c3c3c` (input fields)
+- **Hover Background**: `#2a2d2e` (hover states)
+
+#### Text Colors
+- **Primary Text**: `#cccccc` (main text)
+- **Secondary Text**: `#969696` (muted text)
+- **Active Text**: `#ffffff` (highlighted text)
+
+#### Accent Colors
+- **Primary Accent**: `#007acc` (links, buttons)
+- **Focus Accent**: `#0e639c` (focus states)
+- **Selection**: `#264f78` (selections)
+
+### CSS Variables System
+
+The theme system uses CSS custom properties that change based on the `body.dark` class:
+
+```scss
+:root {
+ --bg-primary: #ffffff; /* Light theme */
+ --text-primary: #373a3c;
+ // ... other variables
+}
+
+body.dark {
+ --bg-primary: #1e1e1e; /* Dark theme */
+ --text-primary: #cccccc;
+ // ... other variables
+}
+```
+
+### React Theme Hook
+
+The `useTheme` hook provides:
+
+```javascript
+const {
+ theme, // Current theme preference ('light'|'dark'|'auto')
+ actualTheme, // Resolved theme ('light'|'dark')
+ setTheme, // Function to set theme preference
+ toggleTheme, // Function to toggle between light/dark
+ isDark, // Boolean indicating if dark theme is active
+ isAuto // Boolean indicating if auto mode is enabled
+} = useTheme();
+```
+
+### Theme Toggle Component
+
+The `ThemeToggle` component provides a dropdown with three options:
+- ☀️ Light
+- 🌙 Dark
+- ⚪ Auto
+
+## Usage
+
+### For Developers
+
+#### Using the Theme Hook
+```javascript
+import { useTheme } from '../hooks/useTheme';
+
+function MyComponent() {
+ const { isDark, toggleTheme } = useTheme();
+
+ return (
+
+
+
+ );
+}
+```
+
+#### Using CSS Variables
+```scss
+.my-component {
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ border: 1px solid var(--border-primary);
+
+ &:hover {
+ background: var(--bg-hover);
+ }
+}
+```
+
+### Extending the Theme
+
+#### Adding New CSS Variables
+1. Add the variable to both light and dark sections in `_theme-variables.scss`
+2. Use the variable in your component styles
+
+#### Adding New Colors
+1. Define the color in `_constants.scss`
+2. Add it to the CSS variables system
+3. Use it in your components
+
+#### Custom Component Theming
+For custom components, follow this pattern:
+
+```scss
+.my-custom-component {
+ background: var(--bg-primary);
+ color: var(--text-primary);
+
+ .my-element {
+ border-color: var(--border-primary);
+
+ &:hover {
+ background: var(--bg-hover);
+ }
+ }
+}
+```
+
+## Code Syntax Highlighting
+
+The dark theme includes VS Code-inspired syntax highlighting for code blocks:
+
+- **Strings**: `#ce9178` (light orange)
+- **Keywords**: `#569cd6` (light blue)
+- **Comments**: `#6a9955` (green)
+- **Types**: `#4ec9b0` (cyan)
+- **Functions**: `#dcdcaa` (yellow)
+
+## Testing
+
+To test the theme implementation:
+
+1. **Manual Testing**:
+ - Click the theme toggle in the top-right corner
+ - Test all three modes (Light, Dark, Auto)
+ - Verify smooth transitions
+ - Check all UI components in both themes
+
+2. **System Theme Testing**:
+ - Set theme to "Auto"
+ - Change your OS theme preference
+ - Verify the app follows system changes
+
+3. **Persistence Testing**:
+ - Change theme preference
+ - Refresh the page
+ - Verify theme preference is maintained
+
+## Browser Support
+
+The theme system supports:
+- Modern browsers with CSS custom properties support
+- `prefers-color-scheme` media query support
+- localStorage for theme persistence
+
+## Future Enhancements
+
+Potential improvements for the theme system:
+1. Additional theme variants (high contrast, custom themes)
+2. Theme editor for custom color schemes
+3. Keyboard shortcuts for theme switching
+4. More granular component-level theming options
+5. Theme preview without applying changes
+
+## Migration Notes
+
+### From Legacy Dark Theme
+The old dark theme was a simple `body.dark` class with hardcoded colors. The new system:
+- Maintains backward compatibility
+- Provides more comprehensive coverage
+- Uses CSS variables for easier maintenance
+- Includes proper VS Code color scheme
+
+### Breaking Changes
+None - the new system is fully backward compatible with existing dark theme usage.
\ No newline at end of file
diff --git a/src/App.js b/src/App.js
index 3d9cb4f3..21fbc0f8 100644
--- a/src/App.js
+++ b/src/App.js
@@ -4,15 +4,16 @@ import MainContainer from './containers/MainContainer';
// import CopybookContainer from './containers/CopybookContainer';
import NoticeContainer from './containers/NoticeContainer';
import NavBarContainer from './containers/NavBarContainer';
+import { ThemeProvider } from './hooks/useTheme';
function App() {
return (
- <>
+
{/* */}
- >
+
);
}
diff --git a/src/components/ThemeToggle.js b/src/components/ThemeToggle.js
new file mode 100644
index 00000000..68803cc0
--- /dev/null
+++ b/src/components/ThemeToggle.js
@@ -0,0 +1,50 @@
+import React from 'react';
+import { Icon, Dropdown } from 'semantic-ui-react';
+import { useTheme, THEMES } from '../hooks/useTheme';
+
+const ThemeToggle = () => {
+ const { theme, setTheme, isDark, isAuto } = useTheme();
+
+ const themeOptions = [
+ {
+ key: THEMES.LIGHT,
+ text: 'Light',
+ value: THEMES.LIGHT,
+ icon: 'sun'
+ },
+ {
+ key: THEMES.DARK,
+ text: 'Dark',
+ value: THEMES.DARK,
+ icon: 'moon'
+ },
+ {
+ key: THEMES.AUTO,
+ text: 'Auto',
+ value: THEMES.AUTO,
+ icon: 'circle outline'
+ }
+ ];
+
+ const getCurrentIcon = () => {
+ if (isAuto) return 'circle outline';
+ return isDark ? 'moon' : 'sun';
+ };
+
+ return (
+
+
+
+ }
+ options={themeOptions}
+ value={theme}
+ onChange={(e, { value }) => setTheme(value)}
+ direction="left"
+ pointing="top right"
+ />
+ );
+};
+
+export default ThemeToggle;
\ No newline at end of file
diff --git a/src/containers/NavBarContainer.js b/src/containers/NavBarContainer.js
index b06d9499..cab2bf61 100644
--- a/src/containers/NavBarContainer.js
+++ b/src/containers/NavBarContainer.js
@@ -1,5 +1,6 @@
import React from 'react';
import { Container, Icon, Popup } from 'semantic-ui-react';
+import ThemeToggle from '../components/ThemeToggle';
// import CopybookModel from '../models/CopybookModel';
export default function NavBarContainer() {
@@ -29,6 +30,7 @@ export default function NavBarContainer() {
Sorry, GitHub stars organize tool currently is not available, new version is coming soon :)
*/}
+
diff --git a/src/hooks/useTheme.js b/src/hooks/useTheme.js
new file mode 100644
index 00000000..bcb2b504
--- /dev/null
+++ b/src/hooks/useTheme.js
@@ -0,0 +1,112 @@
+import React, { createContext, useContext, useEffect, useState } from 'react';
+
+// Theme types
+export const THEMES = {
+ LIGHT: 'light',
+ DARK: 'dark',
+ AUTO: 'auto'
+};
+
+const ThemeContext = createContext();
+
+// Theme detection utilities
+const getSystemTheme = () => {
+ if (typeof window !== 'undefined' && window.matchMedia) {
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? THEMES.DARK : THEMES.LIGHT;
+ }
+ return THEMES.LIGHT;
+};
+
+const getStoredTheme = () => {
+ if (typeof window !== 'undefined' && window.localStorage) {
+ const stored = localStorage.getItem('codelf-theme');
+ return stored && Object.values(THEMES).includes(stored) ? stored : THEMES.AUTO;
+ }
+ return THEMES.AUTO;
+};
+
+const setStoredTheme = (theme) => {
+ if (typeof window !== 'undefined' && window.localStorage) {
+ localStorage.setItem('codelf-theme', theme);
+ }
+};
+
+const applyTheme = (theme) => {
+ if (typeof document !== 'undefined') {
+ const isDark = theme === THEMES.DARK;
+ document.body.classList.toggle('dark', isDark);
+ }
+};
+
+export function ThemeProvider({ children }) {
+ const [themePreference, setThemePreference] = useState(THEMES.AUTO);
+ const [actualTheme, setActualTheme] = useState(THEMES.LIGHT);
+
+ useEffect(() => {
+ // Initialize theme from storage
+ const storedTheme = getStoredTheme();
+ setThemePreference(storedTheme);
+
+ const systemTheme = getSystemTheme();
+ const currentTheme = storedTheme === THEMES.AUTO ? systemTheme : storedTheme;
+ setActualTheme(currentTheme);
+ applyTheme(currentTheme);
+ }, []);
+
+ useEffect(() => {
+ // Listen for system theme changes
+ if (typeof window !== 'undefined' && window.matchMedia) {
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+
+ const handleChange = (e) => {
+ if (themePreference === THEMES.AUTO) {
+ const newTheme = e.matches ? THEMES.DARK : THEMES.LIGHT;
+ setActualTheme(newTheme);
+ applyTheme(newTheme);
+ }
+ };
+
+ mediaQuery.addEventListener('change', handleChange);
+ return () => mediaQuery.removeEventListener('change', handleChange);
+ }
+ }, [themePreference]);
+
+ const setTheme = (newTheme) => {
+ setThemePreference(newTheme);
+ setStoredTheme(newTheme);
+
+ const systemTheme = getSystemTheme();
+ const actualNewTheme = newTheme === THEMES.AUTO ? systemTheme : newTheme;
+ setActualTheme(actualNewTheme);
+ applyTheme(actualNewTheme);
+ };
+
+ const toggleTheme = () => {
+ const currentTheme = themePreference === THEMES.AUTO ? getSystemTheme() : themePreference;
+ const newTheme = currentTheme === THEMES.DARK ? THEMES.LIGHT : THEMES.DARK;
+ setTheme(newTheme);
+ };
+
+ const value = {
+ theme: themePreference,
+ actualTheme,
+ setTheme,
+ toggleTheme,
+ isDark: actualTheme === THEMES.DARK,
+ isAuto: themePreference === THEMES.AUTO
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useTheme() {
+ const context = useContext(ThemeContext);
+ if (!context) {
+ throw new Error('useTheme must be used within a ThemeProvider');
+ }
+ return context;
+}
\ No newline at end of file
diff --git a/styles/_common.scss b/styles/_common.scss
index 2a6a51db..5c241226 100644
--- a/styles/_common.scss
+++ b/styles/_common.scss
@@ -13,13 +13,9 @@ html, body {
}
body {
- background: #fff;
- color: #373a3c;
+ background: var(--bg-primary);
+ color: var(--text-primary);
transition: all 350ms $anim-cubic-bezier;
- &.dark {
- background: $dark;
- color: #fff;
- }
}
/*
diff --git a/styles/_constants.scss b/styles/_constants.scss
index 7b6be2cb..bdb56af3 100644
--- a/styles/_constants.scss
+++ b/styles/_constants.scss
@@ -2,8 +2,49 @@
$anim-cubic-bezier: cubic-bezier(0.4, 0, 0.2, 1);
$anim-default-time: 0.6s;
-/* color */
+/* VS Code Dark Theme Colors */
+// Background colors
+$vscode-dark-bg: #1e1e1e;
+$vscode-dark-sidebar: #252526;
+$vscode-dark-input-bg: #3c3c3c;
+$vscode-dark-hover: #2a2d2e;
+$vscode-dark-border: #3e3e3e;
+
+// Text colors
+$vscode-dark-text: #cccccc;
+$vscode-dark-text-muted: #969696;
+$vscode-dark-text-active: #ffffff;
+
+// Accent colors
+$vscode-dark-blue: #007acc;
+$vscode-dark-focus: #0e639c;
+$vscode-dark-selection: #264f78;
+
+// Status colors
+$vscode-dark-error: #f48771;
+$vscode-dark-warning: #ddb76d;
+$vscode-dark-success: #89d185;
+
+/* Light Theme Colors */
+// Background colors
+$vscode-light-bg: #ffffff;
+$vscode-light-sidebar: #f3f3f3;
+$vscode-light-input-bg: #ffffff;
+$vscode-light-hover: #e8e8e8;
+$vscode-light-border: #d4d4d4;
+
+// Text colors
+$vscode-light-text: #373a3c;
+$vscode-light-text-muted: #6c6c6c;
+$vscode-light-text-active: #000000;
+
+// Accent colors
+$vscode-light-blue: #0066cc;
+$vscode-light-focus: #005a9e;
+$vscode-light-selection: #add6ff;
+
+/* Legacy colors (keeping for compatibility) */
$box-shadow: rgb(187,187,187) 0px 2px 8px !important;
-$dark: #272b38;
+$dark: $vscode-dark-bg; // Updated to use VS Code dark color
/* dimensions */
diff --git a/styles/_donate.scss b/styles/_donate.scss
index 95506e4b..ca6b499b 100755
--- a/styles/_donate.scss
+++ b/styles/_donate.scss
@@ -9,6 +9,7 @@
.hd {
padding-bottom: .8rem;
text-align: center;
+ color: var(--text-primary);
}
.bd {
@@ -22,7 +23,8 @@
.paypal {
margin: 0 0.5rem;
width: 5rem;
- background: #fff;
+ background: var(--bg-primary);
border-radius: 0.1rem;
+ border: 1px solid var(--border-primary);
}
}
diff --git a/styles/_main-container.scss b/styles/_main-container.scss
index ab04b478..1af98d32 100644
--- a/styles/_main-container.scss
+++ b/styles/_main-container.scss
@@ -1,10 +1,10 @@
.main-container {
- $dark-theme: 'body.dark &';
position: relative;
display: flex;
align-items: center;
flex-direction: column;
padding-bottom: 3rem;
+
.title {
text-align: center;
margin-top: 5rem;
@@ -110,18 +110,23 @@
}
&__input {
- background: #F1F3F4;
+ background: var(--bg-input);
border-radius: 5rem;
transition: box-shadow 350ms $anim-cubic-bezier;
&:focus, &:active, &:hover {
- box-shadow: 0 3px 8px 0 rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.08);
+ box-shadow: 0 3px 8px 0 var(--shadow-focus);
}
input {
- background: #F1F3F4;
+ background: var(--bg-input);
border-radius: 5rem;
border: 0 !important;
+ color: var(--text-primary);
+
+ &::placeholder {
+ color: var(--text-secondary);
+ }
}
}
@@ -162,16 +167,6 @@
}
}
- #{$dark-theme} {
- .search-bar {
- &__input {
- &:focus, &:active, &:hover {
- box-shadow: $box-shadow;
- }
- }
- }
- }
-
.suggestion {
padding-bottom: 2rem;
max-width: 100%;
@@ -179,8 +174,10 @@
min-width: 43rem;
}
a {
+ color: var(--accent-primary);
&:hover {
text-decoration: underline;
+ color: var(--accent-focus);
}
}
}
@@ -192,8 +189,13 @@
.label {
@extend .pointer-link;
margin: 0.4rem 0.4rem 0 0;
+ background-color: var(--bg-hover);
+ color: var(--text-primary);
+ border: 1px solid var(--border-primary);
+
&:hover {
- box-shadow: $box-shadow;
+ box-shadow: 0 2px 8px var(--shadow-primary);
+ background-color: var(--accent-selection);
}
&.animated {
transform: translateZ(0);
@@ -203,7 +205,7 @@
hr {
width: 70%;
- border-color: rgba(0, 0, 0, .16);
+ border-color: var(--border-primary);
margin-top: 1rem;
}
}
diff --git a/styles/_nav-bar-container.scss b/styles/_nav-bar-container.scss
index e1d6fcc4..e8ce776d 100755
--- a/styles/_nav-bar-container.scss
+++ b/styles/_nav-bar-container.scss
@@ -23,10 +23,36 @@
margin: 0 !important;
font-size: 2rem;
color: #70B7FD;
+ transition: color 0.2s ease;
&:hover,&:active{
color: #59AAF9;
}
}
+
+ // Theme toggle button styles
+ .theme-toggle-btn {
+ display: inline-block;
+ cursor: pointer;
+ margin-right: 0.4rem;
+ i {
+ color: var(--text-secondary);
+ transition: color 0.2s ease;
+ &:hover {
+ color: var(--accent-primary);
+ }
+ }
+ }
+
+ // Dark theme icon colors
+ body.dark & {
+ i {
+ color: var(--text-secondary);
+ &:hover, &:active {
+ color: var(--accent-primary);
+ }
+ }
+ }
+
.github-corner {
margin-top: -0.2rem;
i {
@@ -38,7 +64,7 @@
}
$anim-delay: 150ms;
$anim-duration: 500ms;
- @for $i from 1 through 5 {
+ @for $i from 1 through 6 { // Updated to 6 to include theme toggle
>*:nth-child(#{$i}) {
animation-duration: $anim-duration;
animation-delay: 500 + $anim-delay*($i - 1);
diff --git a/styles/_notice-container.scss b/styles/_notice-container.scss
index e53f45fb..94bb1ed3 100755
--- a/styles/_notice-container.scss
+++ b/styles/_notice-container.scss
@@ -1,5 +1,4 @@
.notice-container {
- $dark-theme: 'body.dark &';
position: fixed;
bottom: 0;
left: 0;
@@ -7,18 +6,17 @@
padding-bottom: 1rem;
padding-top: 0.5rem;
text-align: center;
- background-color: #fff;
+ background-color: var(--bg-primary);
transition: background-color 150ms ease-in-out;
- @at-root {
- #{$dark-theme} {
- background-color: $dark;
- }
- }
+
a {
display: none;
- color: #70B7FD;
+ color: var(--accent-primary);
&.show {
display: block;
}
+ &:hover {
+ color: var(--accent-focus);
+ }
}
}
diff --git a/styles/_semantic-ui-overrides.scss b/styles/_semantic-ui-overrides.scss
new file mode 100644
index 00000000..308f0c14
--- /dev/null
+++ b/styles/_semantic-ui-overrides.scss
@@ -0,0 +1,164 @@
+/* Semantic UI Dark Theme Overrides */
+
+/* Dropdown menus */
+body.dark .ui.dropdown .menu {
+ background: var(--bg-primary);
+ border: 1px solid var(--border-primary);
+ box-shadow: 0 2px 8px var(--shadow-primary);
+}
+
+body.dark .ui.dropdown .menu > .item {
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ border-top: 1px solid var(--border-primary);
+}
+
+body.dark .ui.dropdown .menu > .item:hover {
+ background: var(--bg-hover);
+ color: var(--text-active);
+}
+
+body.dark .ui.dropdown .menu > .item.selected {
+ background: var(--accent-selection);
+ color: var(--text-active);
+}
+
+/* Modals */
+body.dark .ui.modal {
+ background: var(--bg-primary);
+ border: 1px solid var(--border-primary);
+ box-shadow: 0 8px 32px var(--shadow-primary);
+}
+
+body.dark .ui.modal > .header {
+ background: var(--bg-secondary);
+ color: var(--text-active);
+ border-bottom: 1px solid var(--border-primary);
+}
+
+body.dark .ui.modal > .content {
+ background: var(--bg-primary);
+ color: var(--text-primary);
+}
+
+/* Buttons */
+body.dark .ui.button {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+ border: 1px solid var(--border-primary);
+}
+
+body.dark .ui.button:hover {
+ background: var(--accent-selection);
+ color: var(--text-active);
+}
+
+body.dark .ui.primary.button {
+ background: var(--accent-primary);
+ color: white;
+}
+
+body.dark .ui.primary.button:hover {
+ background: var(--accent-focus);
+}
+
+/* Input fields */
+body.dark .ui.input input {
+ background: var(--bg-input);
+ color: var(--text-primary);
+ border: 1px solid var(--border-primary);
+}
+
+body.dark .ui.input input:focus {
+ border-color: var(--accent-primary);
+ box-shadow: 0 0 0 2px var(--accent-selection);
+}
+
+/* Labels */
+body.dark .ui.label {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+ border: 1px solid var(--border-primary);
+}
+
+/* Popups */
+body.dark .ui.popup {
+ background: var(--bg-primary);
+ border: 1px solid var(--border-primary);
+ color: var(--text-primary);
+ box-shadow: 0 4px 16px var(--shadow-primary);
+}
+
+body.dark .ui.popup:before {
+ background: var(--bg-primary);
+ border: 1px solid var(--border-primary);
+}
+
+/* Form elements */
+body.dark .ui.form .field > label {
+ color: var(--text-primary);
+}
+
+body.dark .ui.form textarea,
+body.dark .ui.form input[type="text"],
+body.dark .ui.form input[type="email"],
+body.dark .ui.form input[type="search"],
+body.dark .ui.form input[type="password"] {
+ background: var(--bg-input);
+ color: var(--text-primary);
+ border: 1px solid var(--border-primary);
+}
+
+body.dark .ui.form textarea:focus,
+body.dark .ui.form input:focus {
+ border-color: var(--accent-primary);
+ box-shadow: 0 0 0 2px var(--accent-selection);
+}
+
+/* Menu */
+body.dark .ui.menu {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-primary);
+}
+
+body.dark .ui.menu .item {
+ color: var(--text-primary);
+}
+
+body.dark .ui.menu .item:hover {
+ background: var(--bg-hover);
+ color: var(--text-active);
+}
+
+/* Checkbox */
+body.dark .ui.checkbox input:checked ~ label:before {
+ background: var(--accent-primary);
+ border-color: var(--accent-primary);
+}
+
+body.dark .ui.checkbox label {
+ color: var(--text-primary);
+}
+
+/* Segment */
+body.dark .ui.segment {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-primary);
+ color: var(--text-primary);
+}
+
+/* Dimmer */
+body.dark .ui.dimmer {
+ background-color: rgba(0, 0, 0, 0.85);
+}
+
+/* Message */
+body.dark .ui.message {
+ background: var(--bg-secondary);
+ color: var(--text-primary);
+ border: 1px solid var(--border-primary);
+}
+
+body.dark .ui.message .header {
+ color: var(--text-active);
+}
\ No newline at end of file
diff --git a/styles/_source-code.scss b/styles/_source-code.scss
index f2e51880..9147cee2 100755
--- a/styles/_source-code.scss
+++ b/styles/_source-code.scss
@@ -1,21 +1,38 @@
@at-root {
.source-code {
+ background: var(--bg-primary);
+ color: var(--text-primary);
+
{&}> .header {
padding: 0.8rem 1rem;
+ background: var(--bg-secondary);
+ border-bottom: 1px solid var(--border-primary);
}
> .content {
+ background: var(--bg-primary);
+
pre {
display: inline-block;
margin: 0;
padding: 0;
+ background: var(--bg-input);
+ color: var(--text-primary);
}
code {
display: inline-block;
- color: #000;
+ color: var(--text-primary);
+ background: var(--bg-input);
}
}
{&} .ui.dropdown .menu>.item {
padding: 0.4rem 0 0 0.4rem !important;
+ background: var(--bg-primary);
+ color: var(--text-primary);
+
+ &:hover {
+ background: var(--bg-hover);
+ }
+
&:last-child {
padding-bottom: 0.4rem !important;
}
diff --git a/styles/_theme-variables.scss b/styles/_theme-variables.scss
new file mode 100644
index 00000000..5cbd8a08
--- /dev/null
+++ b/styles/_theme-variables.scss
@@ -0,0 +1,54 @@
+/* CSS Custom Properties for Theme System */
+
+:root {
+ /* Light Theme (Default) */
+ --bg-primary: #{$vscode-light-bg};
+ --bg-secondary: #{$vscode-light-sidebar};
+ --bg-input: #{$vscode-light-input-bg};
+ --bg-hover: #{$vscode-light-hover};
+
+ --text-primary: #{$vscode-light-text};
+ --text-secondary: #{$vscode-light-text-muted};
+ --text-active: #{$vscode-light-text-active};
+
+ --border-primary: #{$vscode-light-border};
+ --shadow-primary: rgba(0, 0, 0, 0.1);
+ --shadow-focus: rgba(0, 102, 204, 0.2);
+
+ --accent-primary: #{$vscode-light-blue};
+ --accent-focus: #{$vscode-light-focus};
+ --accent-selection: #{$vscode-light-selection};
+
+ --error-color: #d73a49;
+ --warning-color: #e36209;
+ --success-color: #28a745;
+}
+
+body.dark {
+ /* Dark Theme */
+ --bg-primary: #{$vscode-dark-bg};
+ --bg-secondary: #{$vscode-dark-sidebar};
+ --bg-input: #{$vscode-dark-input-bg};
+ --bg-hover: #{$vscode-dark-hover};
+
+ --text-primary: #{$vscode-dark-text};
+ --text-secondary: #{$vscode-dark-text-muted};
+ --text-active: #{$vscode-dark-text-active};
+
+ --border-primary: #{$vscode-dark-border};
+ --shadow-primary: rgba(0, 0, 0, 0.3);
+ --shadow-focus: rgba(14, 99, 156, 0.4);
+
+ --accent-primary: #{$vscode-dark-blue};
+ --accent-focus: #{$vscode-dark-focus};
+ --accent-selection: #{$vscode-dark-selection};
+
+ --error-color: #{$vscode-dark-error};
+ --warning-color: #{$vscode-dark-warning};
+ --success-color: #{$vscode-dark-success};
+}
+
+/* Theme transition for smooth switching */
+* {
+ transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
+}
\ No newline at end of file
diff --git a/styles/_utils.scss b/styles/_utils.scss
index e78bb877..fe7dd7ad 100755
--- a/styles/_utils.scss
+++ b/styles/_utils.scss
@@ -29,10 +29,10 @@
.hidden-link {
{&} {
- color: #000;
+ color: var(--text-primary);
}
{&}:hover {
- color: #1e70bf;
+ color: var(--accent-primary);
}
}
@@ -63,7 +63,7 @@
.close.icon {
top: 0.5rem;
right: 0.5rem;
- color: rgba(0,0,0,.87);
+ color: var(--text-secondary);
}
{&} > .content {
height: auto;
diff --git a/styles/app.scss b/styles/app.scss
index 77b3fcc5..3ac01c1a 100644
--- a/styles/app.scss
+++ b/styles/app.scss
@@ -1,5 +1,6 @@
@charset "UTF-8";
@import 'constants';
+@import 'theme-variables';
@import 'common';
@import 'utils';
@import "animation";
@@ -7,4 +8,5 @@
position: relative;
@import 'components';
@import 'containers';
+ @import 'semantic-ui-overrides';
}
From ecc2f4563af9140234e4c693aab69a94ccda68d0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Jun 2025 03:17:58 +0000
Subject: [PATCH 3/4] Enhance theme system with better accessibility and error
handling
Co-authored-by: unbug <799578+unbug@users.noreply.github.com>
---
README.md | 2 ++
src/components/ThemeToggle.js | 48 +++++++++++++++++++++++-----------
src/hooks/useTheme.js | 31 ++++++++++++++++++----
styles/_nav-bar-container.scss | 41 ++++++++++++++++++++++++-----
4 files changed, 95 insertions(+), 27 deletions(-)
diff --git a/README.md b/README.md
index 50c778fb..2888839d 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,8 @@ CODELF(变量命名神器)
Also a GitHub stars, repositories tagger and organizer tool.
+ ✨ **New**: VS Code-inspired dark theme with automatic system theme detection! Toggle between light, dark, and auto modes.
+
>There are only two hard things in Computer Science: cache invalidation and naming things.-- Phil Karlton
>
>
diff --git a/src/components/ThemeToggle.js b/src/components/ThemeToggle.js
index 68803cc0..a8911288 100644
--- a/src/components/ThemeToggle.js
+++ b/src/components/ThemeToggle.js
@@ -10,19 +10,22 @@ const ThemeToggle = () => {
key: THEMES.LIGHT,
text: 'Light',
value: THEMES.LIGHT,
- icon: 'sun'
+ icon: 'sun',
+ description: 'Light theme'
},
{
key: THEMES.DARK,
text: 'Dark',
value: THEMES.DARK,
- icon: 'moon'
+ icon: 'moon',
+ description: 'Dark theme'
},
{
key: THEMES.AUTO,
text: 'Auto',
value: THEMES.AUTO,
- icon: 'circle outline'
+ icon: 'circle outline',
+ description: 'Follow system preference'
}
];
@@ -31,19 +34,34 @@ const ThemeToggle = () => {
return isDark ? 'moon' : 'sun';
};
+ const getCurrentLabel = () => {
+ if (isAuto) return 'Auto';
+ return isDark ? 'Dark' : 'Light';
+ };
+
return (
-
-
-
- }
- options={themeOptions}
- value={theme}
- onChange={(e, { value }) => setTheme(value)}
- direction="left"
- pointing="top right"
- />
+
+
+
+
+ }
+ options={themeOptions}
+ value={theme}
+ onChange={(e, { value }) => setTheme(value)}
+ direction="left"
+ pointing="top right"
+ className="theme-dropdown"
+ aria-label="Select theme"
+ />
+
);
};
diff --git a/src/hooks/useTheme.js b/src/hooks/useTheme.js
index bcb2b504..f80e51db 100644
--- a/src/hooks/useTheme.js
+++ b/src/hooks/useTheme.js
@@ -19,15 +19,24 @@ const getSystemTheme = () => {
const getStoredTheme = () => {
if (typeof window !== 'undefined' && window.localStorage) {
- const stored = localStorage.getItem('codelf-theme');
- return stored && Object.values(THEMES).includes(stored) ? stored : THEMES.AUTO;
+ try {
+ const stored = localStorage.getItem('codelf-theme');
+ return stored && Object.values(THEMES).includes(stored) ? stored : THEMES.AUTO;
+ } catch (error) {
+ console.warn('Failed to read theme from localStorage:', error);
+ return THEMES.AUTO;
+ }
}
return THEMES.AUTO;
};
const setStoredTheme = (theme) => {
if (typeof window !== 'undefined' && window.localStorage) {
- localStorage.setItem('codelf-theme', theme);
+ try {
+ localStorage.setItem('codelf-theme', theme);
+ } catch (error) {
+ console.warn('Failed to save theme to localStorage:', error);
+ }
}
};
@@ -66,12 +75,24 @@ export function ThemeProvider({ children }) {
}
};
- mediaQuery.addEventListener('change', handleChange);
- return () => mediaQuery.removeEventListener('change', handleChange);
+ // Support both modern and legacy event listener methods
+ if (mediaQuery.addEventListener) {
+ mediaQuery.addEventListener('change', handleChange);
+ return () => mediaQuery.removeEventListener('change', handleChange);
+ } else if (mediaQuery.addListener) {
+ // Fallback for older browsers
+ mediaQuery.addListener(handleChange);
+ return () => mediaQuery.removeListener(handleChange);
+ }
}
}, [themePreference]);
const setTheme = (newTheme) => {
+ if (!Object.values(THEMES).includes(newTheme)) {
+ console.warn('Invalid theme:', newTheme);
+ return;
+ }
+
setThemePreference(newTheme);
setStoredTheme(newTheme);
diff --git a/styles/_nav-bar-container.scss b/styles/_nav-bar-container.scss
index e8ce776d..3c25badc 100755
--- a/styles/_nav-bar-container.scss
+++ b/styles/_nav-bar-container.scss
@@ -30,15 +30,42 @@
}
// Theme toggle button styles
- .theme-toggle-btn {
+ .theme-toggle-wrapper {
display: inline-block;
- cursor: pointer;
margin-right: 0.4rem;
- i {
- color: var(--text-secondary);
- transition: color 0.2s ease;
- &:hover {
- color: var(--accent-primary);
+
+ .theme-toggle-btn {
+ display: inline-block;
+ cursor: pointer;
+ i {
+ color: var(--text-secondary);
+ transition: color 0.2s ease;
+ &:hover {
+ color: var(--accent-primary);
+ }
+ }
+ }
+
+ .theme-dropdown {
+ .menu {
+ background: var(--bg-primary);
+ border: 1px solid var(--border-primary);
+ box-shadow: 0 4px 16px var(--shadow-primary);
+
+ .item {
+ color: var(--text-primary);
+ background: var(--bg-primary);
+
+ &:hover {
+ background: var(--bg-hover);
+ color: var(--text-active);
+ }
+
+ &.selected {
+ background: var(--accent-selection);
+ color: var(--text-active);
+ }
+ }
}
}
}
From d3ef9759d4adecdf1d772c2652fae23ce643d9b7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Jun 2025 04:10:59 +0000
Subject: [PATCH 4/4] Improve dark theme toggle for easier user control
Co-authored-by: unbug <799578+unbug@users.noreply.github.com>
---
src/components/ThemeToggle.js | 48 +++++++++++++++++++++++++++++-----
src/hooks/useTheme.js | 8 +++---
styles/_nav-bar-container.scss | 2 ++
3 files changed, 48 insertions(+), 10 deletions(-)
diff --git a/src/components/ThemeToggle.js b/src/components/ThemeToggle.js
index a8911288..c4039f6f 100644
--- a/src/components/ThemeToggle.js
+++ b/src/components/ThemeToggle.js
@@ -8,21 +8,21 @@ const ThemeToggle = () => {
const themeOptions = [
{
key: THEMES.LIGHT,
- text: 'Light',
+ text: '☀️ Light',
value: THEMES.LIGHT,
icon: 'sun',
- description: 'Light theme'
+ description: 'Always use light theme'
},
{
key: THEMES.DARK,
- text: 'Dark',
+ text: '🌙 Dark',
value: THEMES.DARK,
icon: 'moon',
- description: 'Dark theme'
+ description: 'Always use dark theme'
},
{
key: THEMES.AUTO,
- text: 'Auto',
+ text: '⚪ Auto',
value: THEMES.AUTO,
icon: 'circle outline',
description: 'Follow system preference'
@@ -39,18 +39,54 @@ const ThemeToggle = () => {
return isDark ? 'Dark' : 'Light';
};
+ const getTooltipText = () => {
+ if (isDark) {
+ return `Current: ${getCurrentLabel()} theme. Click to turn off dark mode, or hold to see all options`;
+ } else {
+ return `Current: ${getCurrentLabel()} theme. Click to change theme`;
+ }
+ };
+
+ // Simple toggle for easier dark mode control
+ const handleQuickToggle = () => {
+ if (isDark) {
+ // If in dark mode (manual or auto), switch to light
+ setTheme(THEMES.LIGHT);
+ } else {
+ // If in light mode, switch to dark
+ setTheme(THEMES.DARK);
+ }
+ };
+
return (
+ {/* Add a small indicator when in dark mode to make it clearer */}
+ {isDark && (
+
+ )}
}
options={themeOptions}
diff --git a/src/hooks/useTheme.js b/src/hooks/useTheme.js
index f80e51db..30738fce 100644
--- a/src/hooks/useTheme.js
+++ b/src/hooks/useTheme.js
@@ -21,13 +21,13 @@ const getStoredTheme = () => {
if (typeof window !== 'undefined' && window.localStorage) {
try {
const stored = localStorage.getItem('codelf-theme');
- return stored && Object.values(THEMES).includes(stored) ? stored : THEMES.AUTO;
+ return stored && Object.values(THEMES).includes(stored) ? stored : THEMES.LIGHT;
} catch (error) {
console.warn('Failed to read theme from localStorage:', error);
- return THEMES.AUTO;
+ return THEMES.LIGHT;
}
}
- return THEMES.AUTO;
+ return THEMES.LIGHT;
};
const setStoredTheme = (theme) => {
@@ -48,7 +48,7 @@ const applyTheme = (theme) => {
};
export function ThemeProvider({ children }) {
- const [themePreference, setThemePreference] = useState(THEMES.AUTO);
+ const [themePreference, setThemePreference] = useState(THEMES.LIGHT);
const [actualTheme, setActualTheme] = useState(THEMES.LIGHT);
useEffect(() => {
diff --git a/styles/_nav-bar-container.scss b/styles/_nav-bar-container.scss
index 3c25badc..a2195c9b 100755
--- a/styles/_nav-bar-container.scss
+++ b/styles/_nav-bar-container.scss
@@ -33,10 +33,12 @@
.theme-toggle-wrapper {
display: inline-block;
margin-right: 0.4rem;
+ position: relative;
.theme-toggle-btn {
display: inline-block;
cursor: pointer;
+ position: relative;
i {
color: var(--text-secondary);
transition: color 0.2s ease;