Files
Dune/components/faction-inspector.tsx
T

80 lines
4.9 KiB
TypeScript

'use client';
import { useState } from 'react';
import Image from 'next/image';
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog';
import { Eye, X } from 'lucide-react';
import { FACTIONS, faction, type FactionId } from '@/game/catalog';
import { FACTION_RULES } from '@/game/faction-reference';
import { HOMEWORLD_CARDS } from '@/game/homeworld-cards';
import { Button } from './ui/button';
import { Dialog, DialogClose, DialogDescription, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger } from './ui/dialog';
/** Static public reference only: no player, holdings, hidden choices or action callback. */
export function FactionSheet({ factionId, advanced }: { factionId: FactionId; advanced: boolean }) {
const house = faction(factionId);
const rules = FACTION_RULES[factionId];
const world = HOMEWORLD_CARDS.find(card => card.faction === factionId)!;
return <article className="faction-sheet" aria-label={`${house.name} faction sheet`} style={{ borderTopColor: house.color }}>
<header>
<p className="faction-sheet-label">Faction sheet · {advanced ? 'Advanced' : 'Basic'}</p>
<h3>{house.name}</h3>
<p>{house.title}</p>
</header>
<Image src={`/art/homeworlds/${world.id}-v1.png`} alt="" width={1536} height={768} unoptimized loading="lazy" className="faction-sheet-art" />
<section aria-label={`${house.name} Basic powers and alliances`}>
<h4>Basic powers and alliances</h4>
<ol>{rules.basic.map(text => <li key={text}>{text}</li>)}</ol>
</section>
{advanced && <section aria-label={`${house.name} Advanced powers`}>
<h4>Advanced powers</h4>
<p>Use these with the Basic powers above and the Advanced game rules.</p>
<ol>{rules.advanced.map(text => <li key={text}>{text}</li>)}</ol>
</section>}
<footer>
<p>The rules and game are still being completed. This sheet describes faction powers; table controls show the choices currently available. Optional modules can add or change powers.</p>
<a href={`/rules?topic=faction-${factionId}#faction-${factionId}`}>Full {house.name} reference</a>
{advanced && <a href={`/rules?topic=advanced-${factionId}#advanced-${factionId}`}>Advanced {house.name} reference</a>}
</footer>
</article>;
}
function FactionSheetReader({ factionId, tableAdvanced }: { factionId: FactionId; tableAdvanced?: boolean }) {
const [advanced, setAdvanced] = useState(tableAdvanced ?? false);
return <div className="faction-sheet-reader">
{tableAdvanced !== undefined && <p className="force-notice">This table uses <strong>{tableAdvanced ? 'Advanced' : 'Basic'} rules</strong>.{advanced !== tableAdvanced && ' You are previewing the other rules mode; the table has not changed.'}</p>}
<label className="faction-sheet-toggle"><input type="checkbox" checked={advanced} onChange={event => setAdvanced(event.target.checked)} /> Include Advanced powers</label>
<FactionSheet factionId={factionId} advanced={advanced} />
</div>;
}
export function FactionInspector({ factionId, advanced }: { factionId: FactionId; advanced: boolean }) {
const house = faction(factionId);
return <Dialog>
<DialogTrigger render={<Button variant="outline" className="min-h-11 min-w-11 motion-reduce:transition-none" />} aria-label={`Inspect faction: ${house.name}`}>
<Eye aria-hidden="true" />Inspect faction
</DialogTrigger>
<DialogPortal>
<DialogOverlay className="bg-black/75 motion-reduce:animate-none motion-reduce:transition-none" />
<DialogPrimitive.Popup className="force-dialog">
<div className="force-dialog-header"><span>Public faction sheet</span><DialogClose render={<Button variant="outline" className="min-h-11 min-w-11" />}><X aria-hidden="true" />Close</DialogClose></div>
{/* oxlint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- The long faction sheet scrolls independently and needs keyboard access. */}
<section className="force-dialog-scroll" aria-label="Faction sheet details" tabIndex={0}>
<DialogTitle>{house.name} faction sheet</DialogTitle>
<DialogDescription>Read public faction powers and alliance guidance without changing the game.</DialogDescription>
<FactionSheetReader key={`${factionId}:${advanced}`} factionId={factionId} tableAdvanced={advanced} />
<a className="faction-sheet-help" href="/rules?topic=faction-sheets#faction-sheets">Faction sheet help</a>
</section>
</DialogPrimitive.Popup>
</DialogPortal>
</Dialog>;
}
export function FactionSheetGallery() {
const [selected, setSelected] = useState<FactionId>('atreides');
return <section className="force-reference" aria-label="Faction sheet gallery">
<label>Faction sheet<select value={selected} onChange={event => setSelected(event.target.value as FactionId)}>{FACTIONS.map(house => <option key={house.id} value={house.id}>{house.name}</option>)}</select></label>
<FactionSheetReader key={selected} factionId={selected} />
</section>;
}