From 9138ac1098c37598435efcc5d021aede15c3d648 Mon Sep 17 00:00:00 2001 From: fahed Date: Sun, 26 Apr 2026 15:46:54 +0300 Subject: [PATCH] fix: accessibility, theming, and focus-visibility improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses critical and high-severity findings from UI audit: - C1: Define missing CSS tokens (--hover, --bg-primary/secondary/tertiary) fixing broken hover states and Slides Builder backgrounds - C2: Chart colors now read CSS custom properties at render-time via getChartTheme(), adapting tooltip, ticks, and grid to dark mode - C3: Multi-select ARIA fixed — label elements now carry role="option" and aria-selected for valid listbox semantics - H1/M1: Remove unused --gold and duplicate --primary tokens; replace all var(--primary) with var(--accent) throughout App.css - H3/H4: Focus-visible outlines added to all custom interactive elements (chips, controls, year buttons, hero button, multi-select trigger) - H5: access-badge--full hardcoded colors replaced with design tokens - H7: aria-pressed added to all chart toggle buttons - L1: Hardcoded #fff/white replaced with var(--text-inverse) - M4: index.html now preloads DM Serif Display, Outfit, and IBM Plex Sans Arabic — all fonts actually used in the app Co-Authored-By: Claude Sonnet 4.6 --- index.html | 2 +- src/App.css | 53 ++++++++++++------- src/components/Comparison.tsx | 17 ++++-- src/components/Dashboard.tsx | 45 +++++++++------- src/config/chartConfig.ts | 98 +++++++++++++++++++++-------------- 5 files changed, 136 insertions(+), 79 deletions(-) diff --git a/index.html b/index.html index 3d045a5..d02ad0a 100644 --- a/index.html +++ b/index.html @@ -8,7 +8,7 @@ - + HiHala Data diff --git a/src/App.css b/src/App.css index f7b416e..991c1ee 100644 --- a/src/App.css +++ b/src/App.css @@ -23,13 +23,11 @@ --text-secondary: #334155; --text-muted: #64748b; --accent: #2563eb; - --primary: #2563eb; --accent-light: #dbeafe; --success: #059669; --success-light: #d1fae5; --danger: #dc2626; --danger-light: #fee2e2; - --gold: #b8860b; --brand-icon: #3b82f6; --brand-text: #1e3a5f; --text-inverse: #ffffff; @@ -44,6 +42,10 @@ --shadow-sm: 0 1px 2px rgba(0,0,0,0.05); --shadow: 0 4px 12px rgba(0,0,0,0.08); --radius: 12px; + --hover: var(--muted-light); + --bg-primary: var(--surface); + --bg-secondary: var(--bg); + --bg-tertiary: var(--border); } /* Dark mode */ @@ -56,7 +58,6 @@ --text-secondary: #cbd5e1; --text-muted: #94a3b8; --accent: #3b82f6; - --primary: #3b82f6; --accent-light: #1e3a5f; --success: #34d399; --success-light: #064e3b; @@ -88,7 +89,6 @@ --text-secondary: #cbd5e1; --text-muted: #94a3b8; --accent: #3b82f6; - --primary: #3b82f6; --accent-light: #1e3a5f; --success: #34d399; --success-light: #064e3b; @@ -226,7 +226,7 @@ html[dir="rtl"] { .empty-state-action { padding: 10px 20px; - background: var(--primary); + background: var(--accent); color: var(--text-inverse); border: none; border-radius: 8px; @@ -1089,7 +1089,7 @@ table tbody tr:hover { .drp-custom-field input[type="date"]:focus { outline: none; border-color: var(--accent); - box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.12); + box-shadow: 0 0 0 2px var(--accent); } .drp-custom-sep { @@ -1127,7 +1127,7 @@ table tbody tr:hover { .drp-apply { background: var(--accent); border: 1px solid transparent; - color: #fff; + color: var(--text-inverse); } .drp-apply:hover { @@ -1301,7 +1301,7 @@ table tbody tr:hover { font-size: 1rem; font-weight: 600; background: var(--accent); - color: white; + color: var(--text-inverse); cursor: pointer; transition: opacity 150ms ease; } @@ -1398,8 +1398,8 @@ table tbody tr:hover { } .access-badge--full { - background: #d1fae5; - color: #065f46; + background: var(--success-light); + color: var(--success); } .btn-small { @@ -1418,7 +1418,7 @@ table tbody tr:hover { .btn-small.btn-primary { background: var(--accent); - color: white; + color: var(--text-inverse); border-color: var(--accent); } @@ -1758,7 +1758,7 @@ tr.editing td { .chart-metric-selector button.active { background: var(--surface); - color: var(--primary); + color: var(--accent); box-shadow: 0 1px 3px rgba(0,0,0,0.1); font-weight: 600; } @@ -1792,7 +1792,7 @@ tr.editing td { } .carousel:focus-visible { - outline: 2px solid var(--primary); + outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 8px; } @@ -1877,7 +1877,7 @@ tr.editing td { } .carousel-dot.active { - background: var(--primary); + background: var(--accent); width: 24px; } @@ -1903,14 +1903,14 @@ tr.editing td { } .carousel-dots.labeled .carousel-dot:hover { - border-color: var(--primary); + border-color: var(--accent); background: var(--bg); } .carousel-dots.labeled .carousel-dot.active { width: auto; - background: var(--primary); - border-color: var(--primary); + background: var(--accent); + border-color: var(--accent); } .carousel-dots.labeled .carousel-dot.active .dot-label { @@ -2082,7 +2082,7 @@ tr.editing td { .mobile-nav-item:hover, .mobile-nav-item.active { - color: var(--primary); + color: var(--accent); background: rgba(37, 99, 235, 0.08); } @@ -2833,6 +2833,23 @@ html[dir="rtl"] .exportable-chart-wrapper .chart-export-btn.visible { text-align: left !important; } +/* ======================================== + Focus Visible — Keyboard accessibility + ======================================== */ + +.drp-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } +.drp-year-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } +.drp-cancel:focus-visible, +.drp-apply:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } +.nav-refresh-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } +.nav-lang-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } +.multi-select-trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; } +.toggle-switch button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } +.chart-metric-selector button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } +.nav-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } +.controls-reset:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } +.alt-filter-reset:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } + /* ======================================== Reduced Motion ======================================== */ diff --git a/src/components/Comparison.tsx b/src/components/Comparison.tsx index ba45855..9bda6e4 100644 --- a/src/components/Comparison.tsx +++ b/src/components/Comparison.tsx @@ -303,7 +303,7 @@ function AltMultiSelect({ value, options, onChange, allLabel, countLabel, clearL
{options.map(opt => ( -