Comprehensive UX audit fixes: navigation, feedback, affordances, and accessibility
CI / build-and-push (push) Successful in 28s
CI / build-and-push (push) Successful in 28s
Address 18 issues across high/medium/low impact tiers identified in a full interface review. Key changes: Models page decomposed into tabs, confirmation dialogs for irreversible actions (deploy/open-source/acquire), chart Y-axes made visible, hash router extended for Market tab persistence, collapsible sidebar, keyboard navigation shortcuts (g+key chords), notification bulk actions, achievement progress bars, and ARIA label improvements. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { useState, useEffect, useRef } from 'react';
|
||||
import {
|
||||
LayoutDashboard, Server, FlaskConical, Brain,
|
||||
TrendingUp, Users, Database, Swords, DollarSign, Settings, Trophy, Medal,
|
||||
PanelLeftClose, PanelLeftOpen,
|
||||
} from 'lucide-react';
|
||||
import { useGameStore, type ActivePage } from '@/store';
|
||||
|
||||
@@ -20,11 +21,20 @@ const NAV_ITEMS: { page: ActivePage; label: string; icon: typeof LayoutDashboard
|
||||
{ page: 'settings', label: 'Settings', icon: Settings },
|
||||
];
|
||||
|
||||
function getInitialCollapsed(): boolean {
|
||||
try {
|
||||
const stored = localStorage.getItem('ai-tycoon-sidebar-collapsed');
|
||||
if (stored !== null) return stored === 'true';
|
||||
return window.innerWidth < 1280;
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
export function Sidebar() {
|
||||
const activePage = useGameStore((s) => s.activePage);
|
||||
const setActivePage = useGameStore((s) => s.setActivePage);
|
||||
const companyName = useGameStore((s) => s.meta.companyName);
|
||||
const era = useGameStore((s) => s.meta.currentEra);
|
||||
const [collapsed, setCollapsed] = useState(getInitialCollapsed);
|
||||
|
||||
const eraOrder = ['startup', 'scaleup', 'bigtech', 'agi'];
|
||||
const currentEraIdx = eraOrder.indexOf(era);
|
||||
@@ -55,13 +65,28 @@ export function Sidebar() {
|
||||
});
|
||||
};
|
||||
|
||||
const toggleCollapse = () => {
|
||||
setCollapsed(prev => {
|
||||
const next = !prev;
|
||||
localStorage.setItem('ai-tycoon-sidebar-collapsed', String(next));
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<aside className="w-56 bg-surface-900 border-r border-surface-700 flex flex-col h-screen">
|
||||
<div className="p-4 border-b border-surface-700">
|
||||
<h1 className="text-lg font-bold text-accent-light truncate">{companyName}</h1>
|
||||
<span className="text-xs text-surface-400 uppercase tracking-wider">
|
||||
{era === 'startup' ? 'Startup' : era === 'scaleup' ? 'Scale-up' : era === 'bigtech' ? 'Big Tech' : 'AGI Era'}
|
||||
</span>
|
||||
<aside className={`${collapsed ? 'w-16' : 'w-56'} bg-surface-900 border-r border-surface-700 flex flex-col h-screen transition-all duration-200`}>
|
||||
<div className={`${collapsed ? 'px-2 py-3' : 'p-4'} border-b border-surface-700 flex items-center ${collapsed ? 'justify-center' : 'justify-between'}`}>
|
||||
{!collapsed && (
|
||||
<div className="min-w-0">
|
||||
<h1 className="text-lg font-bold text-accent-light truncate">{companyName}</h1>
|
||||
<span className="text-xs text-surface-400 uppercase tracking-wider">
|
||||
{era === 'startup' ? 'Startup' : era === 'scaleup' ? 'Scale-up' : era === 'bigtech' ? 'Big Tech' : 'AGI Era'}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<button onClick={toggleCollapse} className="text-surface-400 hover:text-surface-200 shrink-0 p-1" aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}>
|
||||
{collapsed ? <PanelLeftOpen size={18} /> : <PanelLeftClose size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 py-2 overflow-y-auto">
|
||||
@@ -73,28 +98,32 @@ export function Sidebar() {
|
||||
const showDivider = page === 'talent' || page === 'achievements';
|
||||
return (
|
||||
<div key={page}>
|
||||
{showDivider && <div className="border-t border-surface-700 my-1 mx-4" />}
|
||||
{showDivider && <div className={`border-t border-surface-700 my-1 ${collapsed ? 'mx-2' : 'mx-4'}`} />}
|
||||
<button
|
||||
onClick={() => handleNavClick(page)}
|
||||
className={`w-full flex items-center gap-3 px-4 py-2.5 text-sm transition-colors ${
|
||||
className={`w-full flex items-center ${collapsed ? 'justify-center px-2' : 'gap-3 px-4'} py-2.5 text-sm transition-colors ${
|
||||
isActive
|
||||
? 'bg-accent/10 text-accent-light border-r-2 border-accent'
|
||||
: 'text-surface-300 hover:bg-surface-800 hover:text-surface-100'
|
||||
}`}
|
||||
title={collapsed ? label : undefined}
|
||||
>
|
||||
<Icon size={18} />
|
||||
{label}
|
||||
{isNew && (
|
||||
{!collapsed && label}
|
||||
{!collapsed && isNew && (
|
||||
<span className="ml-auto text-[10px] font-bold bg-accent text-white px-1.5 py-0.5 rounded">NEW</span>
|
||||
)}
|
||||
{collapsed && isNew && (
|
||||
<span className="absolute top-0 right-0 w-1.5 h-1.5 bg-accent rounded-full" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<div className="p-4 border-t border-surface-700 text-xs text-surface-500">
|
||||
AI Tycoon v0.1
|
||||
<div className={`${collapsed ? 'px-2 py-3 text-center' : 'p-4'} border-t border-surface-700 text-xs text-surface-500`}>
|
||||
{collapsed ? 'v0.1' : 'AI Tycoon v0.1'}
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user