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:
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { X, CheckCircle, AlertTriangle, AlertCircle, Info, Bell } from 'lucide-react';
|
||||
import { X, CheckCircle, AlertTriangle, AlertCircle, Info, Bell, Trash2 } from 'lucide-react';
|
||||
import { useGameStore, type GameNotification } from '@/store';
|
||||
import { formatDuration } from '@ai-tycoon/shared';
|
||||
|
||||
@@ -13,6 +13,8 @@ const ICON_MAP = {
|
||||
export function NotificationPanel({ onClose }: { onClose: () => void }) {
|
||||
const notifications = useGameStore((s) => s.notifications);
|
||||
const markAllRead = useGameStore((s) => s.markAllNotificationsRead);
|
||||
const removeNotification = useGameStore((s) => s.removeNotification);
|
||||
const clearAll = useGameStore((s) => s.clearAllNotifications);
|
||||
const currentTick = useGameStore((s) => s.meta.tickCount);
|
||||
const panelRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -44,9 +46,16 @@ export function NotificationPanel({ onClose }: { onClose: () => void }) {
|
||||
>
|
||||
<div className="px-4 py-3 border-b border-surface-700 flex items-center justify-between shrink-0">
|
||||
<h3 className="text-sm font-semibold">Notifications</h3>
|
||||
<button onClick={onClose} className="text-surface-400 hover:text-surface-200">
|
||||
<X size={14} />
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
{notifications.length > 0 && (
|
||||
<button onClick={clearAll} className="text-surface-500 hover:text-surface-300 text-[10px]" title="Clear all">
|
||||
<Trash2 size={12} />
|
||||
</button>
|
||||
)}
|
||||
<button onClick={onClose} className="text-surface-400 hover:text-surface-200">
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="overflow-y-auto flex-1">
|
||||
{notifications.length === 0 ? (
|
||||
@@ -59,7 +68,7 @@ export function NotificationPanel({ onClose }: { onClose: () => void }) {
|
||||
const { icon: Icon, color } = ICON_MAP[n.type] ?? ICON_MAP.info;
|
||||
const ticksAgo = currentTick - n.tick;
|
||||
return (
|
||||
<div key={n.id} className="px-4 py-3 border-b border-surface-800 last:border-0 hover:bg-surface-800/50">
|
||||
<div key={n.id} className="px-4 py-3 border-b border-surface-800 last:border-0 hover:bg-surface-800/50 group">
|
||||
<div className="flex items-start gap-2">
|
||||
<Icon size={14} className={`${color} mt-0.5 shrink-0`} />
|
||||
<div className="flex-1 min-w-0">
|
||||
@@ -67,6 +76,9 @@ export function NotificationPanel({ onClose }: { onClose: () => void }) {
|
||||
<div className="text-xs text-surface-400">{n.message}</div>
|
||||
<div className="text-xs text-surface-600 mt-1">{formatDuration(ticksAgo)} ago</div>
|
||||
</div>
|
||||
<button onClick={() => removeNotification(n.id)} className="text-surface-600 hover:text-surface-300 opacity-0 group-hover:opacity-100 transition-opacity shrink-0 mt-0.5">
|
||||
<X size={12} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user