114 lines
7.2 KiB
JavaScript
114 lines
7.2 KiB
JavaScript
// Rôle : affiche une carte Aniimo retournable et les onglets des variantes correspondantes.
|
||
import { useId, useLayoutEffect, useRef, useState } from "react";
|
||
import { GuideChecklistCheckbox, GuideChecklistMark } from "../GuideChecklistControls.jsx";
|
||
import { Icon } from "../../../components/Icon.jsx";
|
||
import { Tooltip } from "../../../components/Tooltip.jsx";
|
||
import { ANIIMO_STATS, aniimoAsset } from "./utils.js";
|
||
|
||
function Attributes({ values, t }) {
|
||
return <span className="aniimo-attributes">{values.map((value) => <span className="nds-badge" data-variant="neutral" key={value} title={t(value)}><img src={aniimoAsset(value)} alt="" width="22" height="22" /><span>{t(value)}</span></span>)}</span>;
|
||
}
|
||
|
||
function shortForm(label) {
|
||
const words = label.split(/[\s’-]+/).filter((word) => !/^(forme|de|des|du|la|le|les|d)$/i.test(word));
|
||
// Trois lettres distinguent notamment les plateaux, prairies et prismana.
|
||
return (words.length === 1 ? words[0].slice(0, 3) : words.map((word) => word[0]).join("")).toLocaleUpperCase("fr");
|
||
}
|
||
|
||
function CardFaces({ variant, t, content, checklist, statsMax }) {
|
||
const completed = checklist.isCompleted(variant.key);
|
||
const [flipped, setFlipped] = useState(false);
|
||
const frontButton = useRef(null);
|
||
const backButton = useRef(null);
|
||
const focusAfterFlip = useRef(false);
|
||
useLayoutEffect(() => {
|
||
if (!focusAfterFlip.current) return;
|
||
(flipped ? backButton : frontButton).current?.focus({ preventScroll: true });
|
||
focusAfterFlip.current = false;
|
||
}, [flipped]);
|
||
function flip() {
|
||
focusAfterFlip.current = true;
|
||
setFlipped((value) => !value);
|
||
}
|
||
const flipButton = (back, ref) => <button ref={ref} type="button" className="aniimo-flip nds-icon-button" aria-pressed={back} aria-label={`${back ? content.showFront : content.showBack} — ${t(variant.name)}, ${t(variant.form)}`} title={back ? content.showFront : content.showBack} onClick={flip}><Icon name="flip" /></button>;
|
||
return (
|
||
<div className={`aniimo-card ${flipped ? "is-flipped" : ""}`}>
|
||
<div className="aniimo-card-inner">
|
||
<div className="aniimo-face aniimo-front nds-card" aria-hidden={flipped} inert={flipped}>
|
||
{flipButton(false, frontButton)}
|
||
<div className="aniimo-portrait">
|
||
<img src={variant.image} alt={t(variant.name)} width="512" height="840" loading="lazy" />
|
||
<span className="aniimo-number">N° {variant.aniimoId}</span>
|
||
<GuideChecklistCheckbox checklist={checklist} itemKey={variant.key} />
|
||
</div>
|
||
<div className="aniimo-card-body">
|
||
<h2 title={t(variant.name)}>{t(variant.name)}</h2>
|
||
<p className="aniimo-form" title={t(variant.form)}>{t(variant.form)}</p>
|
||
<span className="aniimo-element-icons">{variant.elements.map((element) => <Tooltip key={element} content={t(element)}><img src={aniimoAsset(element)} alt={t(element)} width="24" height="24" /></Tooltip>)}</span>
|
||
</div>
|
||
</div>
|
||
<div className="aniimo-face aniimo-back nds-card" aria-hidden={!flipped} inert={!flipped}>
|
||
{flipButton(true, backButton)}
|
||
<GuideChecklistMark completed={completed} label={checklist.content.completed} />
|
||
<div className="aniimo-card-body aniimo-back-heading">
|
||
<h2 title={t(variant.name)}>{t(variant.name)}</h2>
|
||
<p className="aniimo-form" title={t(variant.form)}>{t(variant.form)}</p>
|
||
</div>
|
||
<div className="aniimo-card-body aniimo-back-details nds-scrollbox" data-variant="ghost" role="region" aria-label={`${content.showBack} — ${t(variant.name)}`} tabIndex="0">
|
||
<div className="aniimo-back-summary">
|
||
<div><h3>{content.elements}</h3><Attributes values={variant.elements} t={t} /></div>
|
||
<div><h3>{content.role}</h3><Attributes values={[variant.role]} t={t} /></div>
|
||
</div>
|
||
<h3>{content.abilities}</h3>
|
||
<ul className="aniimo-abilities">{variant.homeland_abilities.map((ability) => <li key={ability.type}>
|
||
<img src={aniimoAsset(ability.type)} alt="" width="24" height="24" />
|
||
<span>{t(ability.type)}</span>
|
||
<span className="nds-badge" data-variant="gold">{content.level} {ability.level}</span>
|
||
</li>)}</ul>
|
||
<h3>{content.statsTitle}</h3>
|
||
<div className="aniimo-stats">
|
||
{ANIIMO_STATS.map((key) => <div className="aniimo-stat" key={key}>
|
||
<span title={content.stats[key]}>{content.stats[key]}</span>
|
||
<span className="nds-progress" data-size="sm" role="progressbar" aria-label={content.stats[key]} aria-valuemin={0} aria-valuemax={statsMax} aria-valuenow={variant.attributes.stats[key]}>
|
||
<span style={{ width: `${variant.attributes.stats[key] / statsMax * 100}%` }} />
|
||
</span>
|
||
<span className="aniimo-stat-value">{variant.attributes.stats[key]}</span>
|
||
</div>)}
|
||
<div className="aniimo-stat-total"><span>{content.statsTotal}</span><strong>{variant.attributes.total}</strong></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function AniimoCard({ variants, t, content, checklist, statsMax }) {
|
||
const id = useId();
|
||
const [selectedKey, setSelectedKey] = useState(variants[0].key);
|
||
const variant = variants.find((item) => item.key === selectedKey) || variants[0];
|
||
// Mémoriser le repli pour ne pas restaurer un ancien onglet lorsque le filtre est retiré.
|
||
if (selectedKey !== variant.key) setSelectedKey(variant.key);
|
||
const multiple = variants.length > 1;
|
||
function selectTab(index, target) {
|
||
setSelectedKey(variants[index].key);
|
||
target.parentElement.children[index].focus();
|
||
}
|
||
return (
|
||
<article className="aniimo-result" data-checklist-complete={checklist.isCompleted(variant.key) || undefined} data-aniimo={variant.aniimoId} data-form={variant.form} aria-label={t(variant.name)}>
|
||
<div className="aniimo-tabs-slot">
|
||
{multiple && <div className="aniimo-tabs nds-radio-button-group" data-size="xs" role="tablist" aria-label={`${content.variants} — ${t(variant.name)}`}>
|
||
{variants.map((item, index) => <Tooltip as="button" content={t(item.form)} aria-label={t(item.form)} key={item.key} type="button" role="tab" data-form={item.form} id={`${id}-${item.form}`} aria-controls={`${id}-panel`} aria-selected={item.key === variant.key} tabIndex={item.key === variant.key ? 0 : -1} className={item.key === variant.key ? "active" : ""} onClick={() => setSelectedKey(item.key)} onKeyDown={(event) => {
|
||
const next = event.key === "ArrowRight" ? (index + 1) % variants.length : event.key === "ArrowLeft" ? (index - 1 + variants.length) % variants.length : event.key === "Home" ? 0 : event.key === "End" ? variants.length - 1 : null;
|
||
if (next === null) return;
|
||
event.preventDefault();
|
||
selectTab(next, event.currentTarget);
|
||
}}>{shortForm(t(item.form))}</Tooltip>)}
|
||
</div>}
|
||
</div>
|
||
<div id={`${id}-panel`} role={multiple ? "tabpanel" : undefined} aria-labelledby={multiple ? `${id}-${variant.form}` : undefined}>
|
||
<CardFaces variant={variant} t={t} content={content} checklist={checklist} statsMax={statsMax} />
|
||
</div>
|
||
</article>
|
||
);
|
||
}
|