refactor(ui): improve action card accessibility, flex layout and scroll styling
This commit is contained in:
@@ -10,3 +10,19 @@ body {
|
|||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
background-color: #020617; /* slate-950 */
|
background-color: #020617; /* slate-950 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom Scrollbar for action columns */
|
||||||
|
.overflow-x-auto::-webkit-scrollbar {
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
.overflow-x-auto::-webkit-scrollbar-track {
|
||||||
|
background: rgba(15, 23, 42, 0.3);
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
.overflow-x-auto::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(100, 116, 139, 0.4);
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
.overflow-x-auto::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(245, 158, 11, 0.5);
|
||||||
|
}
|
||||||
|
|||||||
+26
-8
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { gameRuntime } from '../state/runtime';
|
import { gameRuntime } from '../state/runtime';
|
||||||
import { useGameStore } from '../state/store';
|
import { useGameStore } from '../state/store';
|
||||||
import type { ActionView } from '../state/viewModel';
|
import type { ActionView } from '../state/viewModel';
|
||||||
@@ -11,7 +11,21 @@ export function ActionCard({ action }: ActionCardProps) {
|
|||||||
const activeActionId = useGameStore((s) => s.activeActionId);
|
const activeActionId = useGameStore((s) => s.activeActionId);
|
||||||
const actionProgress = useGameStore((s) => s.actionProgress);
|
const actionProgress = useGameStore((s) => s.actionProgress);
|
||||||
const prefs = useGameStore((s) => s.prefs);
|
const prefs = useGameStore((s) => s.prefs);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [openInfoId, setOpenInfoId] = useState<string | null>(null);
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function handleClickOutside(event: MouseEvent) {
|
||||||
|
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
||||||
|
setOpenInfoId(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('click', handleClickOutside);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('click', handleClickOutside);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const isActive = action.id === activeActionId;
|
const isActive = action.id === activeActionId;
|
||||||
const isDisabled = !action.available && !isActive;
|
const isDisabled = !action.available && !isActive;
|
||||||
@@ -36,14 +50,17 @@ export function ActionCard({ action }: ActionCardProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tooltipId = `tooltip-${action.id}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex gap-1 w-full">
|
<div ref={containerRef} className="relative flex gap-1 w-full">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
|
aria-pressed={action.kind === 'loop' ? action.loopEnabled : undefined}
|
||||||
title={prefs.actionDetailMode === 'hover' ? action.storyTooltip : undefined}
|
title={prefs.actionDetailMode === 'hover' ? action.storyTooltip : undefined}
|
||||||
onClick={() => gameRuntime.performAction(action.id)}
|
onClick={() => gameRuntime.performAction(action.id)}
|
||||||
className={`relative w-full overflow-hidden rounded-lg border px-4 py-3 text-left transition-all duration-200 ${borderBgClass}`}
|
className={`relative flex-1 overflow-hidden rounded-lg border px-4 py-3 text-left transition-all duration-200 ${borderBgClass}`}
|
||||||
>
|
>
|
||||||
{isActive ? (
|
{isActive ? (
|
||||||
<div
|
<div
|
||||||
@@ -109,15 +126,16 @@ export function ActionCard({ action }: ActionCardProps) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={`Details for ${action.name}`}
|
aria-label={`Details for ${action.name}`}
|
||||||
aria-expanded={isOpen}
|
aria-expanded={openInfoId === action.id}
|
||||||
onClick={() => setIsOpen(!isOpen)}
|
aria-describedby={openInfoId === action.id ? tooltipId : undefined}
|
||||||
|
onClick={() => setOpenInfoId(openInfoId === action.id ? null : action.id)}
|
||||||
className="flex items-center rounded-lg border border-slate-700 bg-slate-800/70 px-2 text-slate-400 transition-colors hover:border-amber-500/60 hover:text-slate-200"
|
className="flex items-center rounded-lg border border-slate-700 bg-slate-800/70 px-2 text-slate-400 transition-colors hover:border-amber-500/60 hover:text-slate-200"
|
||||||
title={action.storyTooltip}
|
|
||||||
>
|
>
|
||||||
ⓘ
|
ⓘ
|
||||||
</button>
|
</button>
|
||||||
{isOpen ? (
|
{openInfoId === action.id ? (
|
||||||
<div
|
<div
|
||||||
|
id={tooltipId}
|
||||||
role="tooltip"
|
role="tooltip"
|
||||||
className="absolute top-full right-0 z-10 mt-1 w-64 rounded-lg border border-slate-600 bg-slate-900 p-3 text-slate-300 text-xs shadow-lg"
|
className="absolute top-full right-0 z-10 mt-1 w-64 rounded-lg border border-slate-600 bg-slate-900 p-3 text-slate-300 text-xs shadow-lg"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user