feat(m1): PR3 T3.0 — shell UI, action kinds, story tab #16
@@ -10,3 +10,19 @@ body {
|
||||
min-height: 100dvh;
|
||||
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 { useGameStore } from '../state/store';
|
||||
import type { ActionView } from '../state/viewModel';
|
||||
@@ -11,7 +11,21 @@ export function ActionCard({ action }: ActionCardProps) {
|
||||
const activeActionId = useGameStore((s) => s.activeActionId);
|
||||
const actionProgress = useGameStore((s) => s.actionProgress);
|
||||
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 isDisabled = !action.available && !isActive;
|
||||
@@ -36,14 +50,17 @@ export function ActionCard({ action }: ActionCardProps) {
|
||||
}
|
||||
}
|
||||
|
||||
const tooltipId = `tooltip-${action.id}`;
|
||||
|
||||
return (
|
||||
<div className="relative flex gap-1 w-full">
|
||||
<div ref={containerRef} className="relative flex gap-1 w-full">
|
||||
<button
|
||||
type="button"
|
||||
disabled={isDisabled}
|
||||
aria-pressed={action.kind === 'loop' ? action.loopEnabled : undefined}
|
||||
title={prefs.actionDetailMode === 'hover' ? action.storyTooltip : undefined}
|
||||
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 ? (
|
||||
<div
|
||||
@@ -109,15 +126,16 @@ export function ActionCard({ action }: ActionCardProps) {
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`Details for ${action.name}`}
|
||||
aria-expanded={isOpen}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
aria-expanded={openInfoId === action.id}
|
||||
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"
|
||||
title={action.storyTooltip}
|
||||
>
|
||||
ⓘ
|
||||
</button>
|
||||
{isOpen ? (
|
||||
{openInfoId === action.id ? (
|
||||
<div
|
||||
id={tooltipId}
|
||||
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"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user