v1.0.1: Add comprehensive theme switcher and fix dark mode issues

- Add theme switcher widget with Auto/Light/Dark options in header
- Implement manual theme override system with localStorage persistence
- Add complete button theme overrides for all variants (primary, outline-*)
- Fix missing focus states and placeholder colors for light theme
- Add proper alert styling for both themes
- Fix expression input error state colors in dark theme
- Complete comprehensive theme coverage for all UI elements
- Theme switcher overrides CSS media queries when manually selected
- All buttons, inputs, and surfaces now properly adapt to theme changes
This commit is contained in:
2026-01-21 09:45:38 +01:00
parent fef9c9732e
commit 97d83923d9
4 changed files with 369 additions and 2 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "jmespath-playground",
"version": "1.0.0",
"version": "1.0.1",
"description": "A React-based web application for testing JMESPath expressions against JSON data",
"main": "index.js",
"scripts": {

View File

@@ -107,6 +107,295 @@ footer a:hover {
}
}
/* Manual theme overrides */
.theme-light {
/* Force light theme regardless of system preference */
background-color: #ffffff !important;
color: #212529 !important;
}
.theme-light .header-section {
background-color: transparent !important;
border-bottom: none !important;
}
.theme-light .card {
background-color: #ffffff !important;
box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important;
color: #212529 !important;
}
.theme-light .card-header {
background-color: #f8f9fa !important;
border-bottom: 2px solid #dee2e6 !important;
color: #212529 !important;
}
.theme-light .jmespath-input {
background-color: #ffffff !important;
border: 1px solid #ced4da !important;
color: #495057 !important;
}
.theme-light .json-input,
.theme-light .result-output {
background-color: #f8f9fa !important;
border: 1px solid #dee2e6 !important;
color: #495057 !important;
}
.theme-light .text-muted {
color: #6c757d !important;
}
.theme-light .jmespath-input:focus {
background-color: #ffffff !important;
border-color: #007bff !important;
color: #495057 !important;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25) !important;
}
.theme-light .jmespath-input::placeholder {
color: #6c757d !important;
}
.theme-light .json-input::placeholder,
.theme-light .result-output::placeholder {
color: #6c757d !important;
}
.theme-light .json-input:focus,
.theme-light .result-output:focus {
background-color: #ffffff !important;
border-color: #007bff !important;
color: #495057 !important;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25) !important;
}
.theme-light .alert-danger {
background-color: #f8d7da !important;
border-color: #f5c6cb !important;
color: #721c24 !important;
}
.theme-light .btn-primary {
background-color: #007bff !important;
border-color: #007bff !important;
color: #ffffff !important;
}
.theme-light .btn-outline-secondary {
color: #6c757d !important;
border-color: #6c757d !important;
}
.theme-light .btn-outline-secondary:hover {
background-color: #6c757d !important;
border-color: #6c757d !important;
color: #ffffff !important;
}
.theme-light .btn-outline-success {
color: #28a745 !important;
border-color: #28a745 !important;
}
.theme-light .btn-outline-success:hover {
background-color: #28a745 !important;
border-color: #28a745 !important;
color: #ffffff !important;
}
.theme-light .btn-outline-info {
color: #17a2b8 !important;
border-color: #17a2b8 !important;
}
.theme-light .btn-outline-info:hover {
background-color: #17a2b8 !important;
border-color: #17a2b8 !important;
color: #ffffff !important;
}
.theme-light .btn-outline-primary {
color: #007bff !important;
border-color: #007bff !important;
}
.theme-light .btn-outline-primary:hover {
background-color: #007bff !important;
border-color: #007bff !important;
color: #ffffff !important;
}
.theme-light .btn-outline-danger {
color: #dc3545 !important;
border-color: #dc3545 !important;
}
.theme-light .btn-outline-danger:hover {
background-color: #dc3545 !important;
border-color: #dc3545 !important;
color: #ffffff !important;
}
.theme-light footer {
background-color: #f8f9fa !important;
border-top: 1px solid #dee2e6 !important;
color: #212529 !important;
}
.theme-light footer a {
color: #6c757d !important;
}
.theme-light footer a:hover {
color: #495057 !important;
}
/* Force dark theme regardless of system preference */
.theme-dark {
background-color: #1a1a1a !important;
color: #e9ecef !important;
}
.theme-dark .header-section {
background-color: #2d2d2d !important;
border-bottom: 1px solid #404040 !important;
}
.theme-dark .card {
background-color: #2d2d2d !important;
box-shadow: 0 2px 8px rgba(0,0,0,0.3) !important;
color: #e9ecef !important;
}
.theme-dark .card-header {
background-color: #3a3a3a !important;
border-bottom: 2px solid #505050 !important;
color: #f8f9fa !important;
}
.theme-dark .jmespath-input {
background-color: #3a3a3a !important;
border: 1px solid #505050 !important;
color: #f8f9fa !important;
}
.theme-dark .jmespath-input::placeholder {
color: #adb5bd !important;
}
.theme-dark .jmespath-input:focus {
background-color: #404040 !important;
border-color: #007bff !important;
color: #ffffff !important;
}
.theme-dark .json-input,
.theme-dark .result-output {
background-color: #2a2a2a !important;
border: 1px solid #505050 !important;
color: #e9ecef !important;
}
.theme-dark .json-input::placeholder,
.theme-dark .result-output::placeholder {
color: #6c757d !important;
}
.theme-dark .json-input:focus,
.theme-dark .result-output:focus {
background-color: #323232 !important;
border-color: #007bff !important;
color: #ffffff !important;
}
.theme-dark .alert-danger {
background-color: #3d1a1a !important;
border-color: #dc3545 !important;
color: #f8d7da !important;
}
.theme-dark .text-muted {
color: #adb5bd !important;
}
.theme-dark footer {
background-color: #2d2d2d !important;
border-top: 1px solid #404040 !important;
color: #e9ecef !important;
}
.theme-dark footer a {
color: #adb5bd !important;
}
.theme-dark footer a:hover {
color: #e9ecef !important;
}
.theme-dark .btn-primary {
background-color: #007bff !important;
border-color: #007bff !important;
color: #ffffff !important;
}
.theme-dark .btn-outline-secondary {
color: #6c757d !important;
border-color: #6c757d !important;
}
.theme-dark .btn-outline-secondary:hover {
background-color: #6c757d !important;
border-color: #6c757d !important;
color: #ffffff !important;
}
.theme-dark .btn-outline-success {
color: #28a745 !important;
border-color: #28a745 !important;
}
.theme-dark .btn-outline-success:hover {
background-color: #28a745 !important;
border-color: #28a745 !important;
color: #ffffff !important;
}
.theme-dark .btn-outline-info {
color: #17a2b8 !important;
border-color: #17a2b8 !important;
}
.theme-dark .btn-outline-info:hover {
background-color: #17a2b8 !important;
border-color: #17a2b8 !important;
color: #ffffff !important;
}
.theme-dark .btn-outline-primary {
color: #007bff !important;
border-color: #007bff !important;
}
.theme-dark .btn-outline-primary:hover {
background-color: #007bff !important;
border-color: #007bff !important;
color: #ffffff !important;
}
.theme-dark .btn-outline-danger {
color: #dc3545 !important;
border-color: #dc3545 !important;
}
.theme-dark .btn-outline-danger:hover {
background-color: #dc3545 !important;
border-color: #dc3545 !important;
color: #ffffff !important;
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
body {

View File

@@ -5,6 +5,10 @@ import './App.css';
// JMESPath Testing Tool - Main Application Component
function App() {
const [jmespathExpression, setJmespathExpression] = useState('people[0].name');
const [theme, setTheme] = useState(() => {
// Load theme from localStorage or default to 'auto'
return localStorage.getItem('theme') || 'auto';
});
const [jsonData, setJsonData] = useState(`{
"people": [
{
@@ -24,6 +28,29 @@ function App() {
const [error, setError] = useState('');
const [jsonError, setJsonError] = useState('');
// Theme management
useEffect(() => {
// Apply theme to document
const applyTheme = (selectedTheme) => {
const root = document.documentElement;
root.className = ''; // Clear existing theme classes
if (selectedTheme === 'light') {
root.classList.add('theme-light');
} else if (selectedTheme === 'dark') {
root.classList.add('theme-dark');
}
// 'auto' uses CSS media queries (no class needed)
};
applyTheme(theme);
localStorage.setItem('theme', theme);
}, [theme]);
const handleThemeChange = (newTheme) => {
setTheme(newTheme);
};
const evaluateExpression = () => {
try {
// Clear previous errors
@@ -185,8 +212,37 @@ function App() {
<div className="header-section py-2">
<div className="container">
<div className="row">
<div className="col-12 text-center">
<div className="col-12 text-center position-relative">
<h2 className="mb-1">JMESPath Testing Tool</h2>
{/* Theme switcher */}
<div className="position-absolute top-0 end-0">
<div className="btn-group btn-group-sm" role="group" aria-label="Theme switcher">
<button
type="button"
className={`btn ${theme === 'auto' ? 'btn-primary' : 'btn-outline-secondary'}`}
onClick={() => handleThemeChange('auto')}
title="Auto (follow system)"
>
🌓 Auto
</button>
<button
type="button"
className={`btn ${theme === 'light' ? 'btn-primary' : 'btn-outline-secondary'}`}
onClick={() => handleThemeChange('light')}
title="Light theme"
>
Light
</button>
<button
type="button"
className={`btn ${theme === 'dark' ? 'btn-primary' : 'btn-outline-secondary'}`}
onClick={() => handleThemeChange('dark')}
title="Dark theme"
>
🌙 Dark
</button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -92,4 +92,26 @@ code {
.result-output {
background-color: #e7f3ff;
border-color: #b3d7ff;
}
/* Dark mode support for error states */
@media (prefers-color-scheme: dark) {
.error {
background-color: #4a1e1e !important;
border-color: #6d2c2c !important;
color: #f8d7da !important;
}
}
/* Manual theme overrides for error states */
.theme-dark .error {
background-color: #4a1e1e !important;
border-color: #6d2c2c !important;
color: #f8d7da !important;
}
.theme-light .error {
background-color: #f8d7da !important;
border-color: #f5c6cb !important;
color: #721c24 !important;
}