5 Commits

Author SHA1 Message Date
97d83923d9 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
2026-01-21 09:45:38 +01:00
fef9c9732e Add comprehensive dark mode support and fix color consistency
- Add automatic dark/light theme detection via prefers-color-scheme
- Add smooth transitions between theme changes
- Fix color contrast issues with explicit text colors
- Make JSON Data and Query Result boxes static (no color changes)
- Keep only Expression input with error indication (red styling)
- Remove dynamic success/error styling from data input/output areas
- Add proper placeholder and focus state colors for both themes
- Ensure all text remains visible in both light and dark modes
- Clean up unused CSS classes
2026-01-18 16:47:59 +01:00
61408d6362 Fix serve command options in Dockerfile
- Remove unsupported --host flag from serve command
- The serve package with -l option binds to 0.0.0.0 by default in containers
- Container will now start properly without ArgError
2026-01-18 16:13:00 +01:00
ce508d32b5 Fix Dockerfile to properly expose container service
- Install all dependencies with 'npm ci' instead of production-only
- Install 'serve' package globally in container
- Use direct serve command with proper host binding (0.0.0.0)
- Fix container service accessibility for production deployment
2026-01-18 16:06:01 +01:00
db3b6beaa3 Modernize Docker Compose configuration
- Replace docker-compose.yml with compose.yaml (modern standard)
- Remove obsolete 'version' field (deprecated in Docker Compose)
- Remove development service configuration
- Add proper image tagging with skoszewski/jmespath-playground
- Keep build context for local development flexibility
- Simplify to production-only service configuration
2026-01-18 16:01:18 +01:00
8 changed files with 544 additions and 46 deletions

View File

@@ -7,8 +7,8 @@ WORKDIR /app
# Copy package files # Copy package files
COPY package*.json ./ COPY package*.json ./
# Install dependencies # Install dependencies (including serve for production)
RUN npm ci --only=production RUN npm ci
# Copy application source # Copy application source
COPY . . COPY . .
@@ -16,8 +16,11 @@ COPY . .
# Build the application # Build the application
RUN npm run build RUN npm run build
# Install serve globally for production serving
RUN npm install -g serve
# Expose port 3000 # Expose port 3000
EXPOSE 3000 EXPOSE 3000
# Start the application # Start the application using serve directly
CMD ["npm", "run", "serve"] CMD ["serve", "-s", "build", "-l", "3000"]

9
compose.yaml Normal file
View File

@@ -0,0 +1,9 @@
services:
jmespath-playground:
build: .
image: skoszewski/jmespath-playground
ports:
- "3000:3000"
environment:
- NODE_ENV=production
restart: unless-stopped

View File

@@ -1,25 +0,0 @@
version: '3.8'
services:
jmespath-playground:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
restart: unless-stopped
# Development service
jmespath-playground-dev:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3001:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- CHOKIDAR_USEPOLLING=true
profiles:
- dev

2
package-lock.json generated
View File

@@ -17,7 +17,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-scripts": "^5.0.1", "react-scripts": "^5.0.1",
"serve": "^14.2.1", "serve": "^14.2.5",
"web-vitals": "^3.5.0" "web-vitals": "^3.5.0"
}, },
"engines": { "engines": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "jmespath-playground", "name": "jmespath-playground",
"version": "1.0.0", "version": "1.0.1",
"description": "A React-based web application for testing JMESPath expressions against JSON data", "description": "A React-based web application for testing JMESPath expressions against JSON data",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@@ -22,7 +22,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-scripts": "^5.0.1", "react-scripts": "^5.0.1",
"serve": "^14.2.1", "serve": "^14.2.5",
"web-vitals": "^3.5.0" "web-vitals": "^3.5.0"
}, },
"eslintConfig": { "eslintConfig": {
@@ -52,4 +52,4 @@
], ],
"author": "", "author": "",
"license": "MIT" "license": "MIT"
} }

View File

@@ -7,6 +7,7 @@ body {
sans-serif; sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
transition: background-color 0.3s ease, color 0.3s ease;
} }
/* Layout structure */ /* Layout structure */
@@ -17,6 +18,7 @@ body {
/* Header section styling - more compact */ /* Header section styling - more compact */
.header-section { .header-section {
/* Removed gradient background to fix text visibility */ /* Removed gradient background to fix text visibility */
transition: background-color 0.3s ease;
} }
/* Custom card styling */ /* Custom card styling */
@@ -24,12 +26,15 @@ body {
border: none; border: none;
box-shadow: 0 2px 8px rgba(0,0,0,0.1); box-shadow: 0 2px 8px rgba(0,0,0,0.1);
border-radius: 8px; border-radius: 8px;
transition: background-color 0.3s ease, box-shadow 0.3s ease;
} }
.card-header { .card-header {
background-color: #f8f9fa; background-color: #f8f9fa;
border-bottom: 2px solid #dee2e6; border-bottom: 2px solid #dee2e6;
font-weight: 600; font-weight: 600;
color: #212529;
transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
} }
/* Input and textarea styling */ /* Input and textarea styling */
@@ -38,6 +43,10 @@ body {
font-size: 14px; font-size: 14px;
padding: 10px; padding: 10px;
font-weight: 400; font-weight: 400;
background-color: #ffffff;
border: 1px solid #ced4da;
color: #495057;
transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
} }
.json-input, .result-output { .json-input, .result-output {
@@ -45,18 +54,10 @@ body {
font-size: 13px; font-size: 13px;
background-color: #f8f9fa; background-color: #f8f9fa;
border: 1px solid #dee2e6; border: 1px solid #dee2e6;
color: #495057;
font-weight: 400; font-weight: 400;
line-height: 1.4; line-height: 1.4;
} transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
.json-input.error {
border-color: #dc3545;
background-color: #fff5f5;
}
.result-output.success {
border-color: #28a745;
background-color: #f0fff4;
} }
/* Button styling */ /* Button styling */
@@ -104,4 +105,436 @@ footer a:hover {
.card-body textarea { .card-body textarea {
min-height: 300px !important; min-height: 300px !important;
} }
}
/* 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 {
background-color: #1a1a1a;
color: #e9ecef;
}
.header-section {
background-color: #2d2d2d;
border-bottom: 1px solid #404040;
}
.card {
background-color: #2d2d2d;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
color: #e9ecef;
}
.card-header {
background-color: #3a3a3a;
border-bottom: 2px solid #505050;
color: #f8f9fa;
}
.jmespath-input {
background-color: #3a3a3a;
border: 1px solid #505050;
color: #f8f9fa;
}
.jmespath-input::placeholder {
color: #adb5bd;
}
.jmespath-input:focus {
background-color: #404040;
border-color: #007bff;
color: #ffffff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
.json-input, .result-output {
background-color: #2a2a2a;
border: 1px solid #505050;
color: #e9ecef;
}
.json-input::placeholder, .result-output::placeholder {
color: #6c757d;
}
.json-input:focus, .result-output:focus {
background-color: #323232;
border-color: #007bff;
color: #ffffff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
.alert-danger {
background-color: #3d1a1a;
border-color: #dc3545;
color: #f8d7da;
}
.text-muted {
color: #adb5bd !important;
}
footer {
background-color: #2d2d2d !important;
border-top: 1px solid #404040 !important;
color: #e9ecef;
}
footer .text-muted {
color: #adb5bd !important;
}
footer a {
color: #adb5bd;
}
footer a:hover {
color: #e9ecef;
}
/* Bootstrap dark mode overrides */
.btn-outline-success {
color: #28a745;
border-color: #28a745;
}
.btn-outline-success:hover {
background-color: #28a745;
border-color: #28a745;
color: #fff;
}
.btn-outline-info {
color: #17a2b8;
border-color: #17a2b8;
}
.btn-outline-info:hover {
background-color: #17a2b8;
border-color: #17a2b8;
color: #fff;
}
.btn-outline-primary {
color: #007bff;
border-color: #007bff;
}
.btn-outline-primary:hover {
background-color: #007bff;
border-color: #007bff;
color: #fff;
}
.btn-outline-secondary {
color: #6c757d;
border-color: #6c757d;
}
.btn-outline-secondary:hover {
background-color: #6c757d;
border-color: #6c757d;
color: #fff;
}
.btn-outline-danger {
color: #dc3545;
border-color: #dc3545;
}
.btn-outline-danger:hover {
background-color: #dc3545;
border-color: #dc3545;
color: #fff;
}
} }

View File

@@ -5,6 +5,10 @@ import './App.css';
// JMESPath Testing Tool - Main Application Component // JMESPath Testing Tool - Main Application Component
function App() { function App() {
const [jmespathExpression, setJmespathExpression] = useState('people[0].name'); 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(`{ const [jsonData, setJsonData] = useState(`{
"people": [ "people": [
{ {
@@ -24,6 +28,29 @@ function App() {
const [error, setError] = useState(''); const [error, setError] = useState('');
const [jsonError, setJsonError] = 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 = () => { const evaluateExpression = () => {
try { try {
// Clear previous errors // Clear previous errors
@@ -185,8 +212,37 @@ function App() {
<div className="header-section py-2"> <div className="header-section py-2">
<div className="container"> <div className="container">
<div className="row"> <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> <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> </div>
</div> </div>
@@ -283,7 +339,7 @@ function App() {
<div className="card-body d-flex flex-column" style={{ minHeight: 0 }}> <div className="card-body d-flex flex-column" style={{ minHeight: 0 }}>
<div className="flex-grow-1" style={{ minHeight: 0 }}> <div className="flex-grow-1" style={{ minHeight: 0 }}>
<textarea <textarea
className={`form-control h-100 json-input ${jsonError ? 'error' : ''}`} className="form-control h-100 json-input"
value={jsonData} value={jsonData}
onChange={handleJsonChange} onChange={handleJsonChange}
placeholder="Enter JSON data here..." placeholder="Enter JSON data here..."
@@ -311,7 +367,7 @@ function App() {
<div className="card-body d-flex flex-column" style={{ minHeight: 0 }}> <div className="card-body d-flex flex-column" style={{ minHeight: 0 }}>
<div className="flex-grow-1" style={{ minHeight: 0 }}> <div className="flex-grow-1" style={{ minHeight: 0 }}>
<textarea <textarea
className={`form-control h-100 result-output ${result && !error && !jsonError ? 'success' : ''}`} className="form-control h-100 result-output"
value={result} value={result}
readOnly readOnly
placeholder="Results will appear here..." placeholder="Results will appear here..."

View File

@@ -92,4 +92,26 @@ code {
.result-output { .result-output {
background-color: #e7f3ff; background-color: #e7f3ff;
border-color: #b3d7ff; 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;
} }