Add a Prismana-only filter with a rainbow star icon
All checks were successful
Deploy Sokko G / deploy (push) Successful in 17s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 17s
This commit is contained in:
parent
14f1b79cbc
commit
2255de94a7
9 changed files with 69 additions and 1 deletions
|
|
@ -816,6 +816,7 @@ Date picker et menus compacts :
|
|||
- le verso occupe exactement la même surface que le recto ; garder son en-tête fixe et faire défiler les détails dans une zone NDS accessible au clavier ; masquer la face inactive aux technologies d'assistance ;
|
||||
- réduire l'animation selon `prefers-reduced-motion` ;
|
||||
- les filtres se cumulent par famille et sont évalués sur chaque variante avant regroupement ;
|
||||
- la chip « Prismana uniquement », sous le nom, garde 12 px de marge inférieure, utilise une étoile SVG arc-en-ciel et limite les résultats à `prismana_form` avant regroupement ; inactive par défaut, elle se cumule avec tous les autres critères et se désactive à la réinitialisation ;
|
||||
- les catégories Rôles, Éléments et Compétences de logis partagent un défilement unique via `filter-options`, comme MHW ; des séparateurs discrets délimitent les catégories et leurs en-têtes permettent de les replier sans désactiver leurs sélections ;
|
||||
- rôles et éléments sont sur deux colonnes de chips `sm` ; les compétences élémentaires précèdent les autres sans séparation interne, avec icône, texte, curseur et valeur sur une ligne ; un seuil à 0 reste visible mais ne participe pas aux opérations ET/OU ;
|
||||
- garder le nombre de critères sélectionnés visible dans l'en-tête, même replié.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,38 @@ const route = "/#/games/aniimo/aniimos";
|
|||
const cards = (page) => page.locator(".aniimo-result");
|
||||
const section = (page, name) => page.getByRole("group", { name, exact: true }).filter({ has: page.locator("legend") });
|
||||
|
||||
for (const width of [1280, 390]) {
|
||||
test(`Prismana filter combines with search and survives grouping and navigation at ${width}px`, async ({ page }) => {
|
||||
test.setTimeout(45000);
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
await page.goto(route);
|
||||
const prismana = page.getByRole("checkbox", { name: "Prismana uniquement", exact: true });
|
||||
await expect(prismana).toBeVisible({ timeout: 30000 });
|
||||
await expect(prismana).not.toBeChecked();
|
||||
await prismana.focus();
|
||||
await prismana.press("Space");
|
||||
await expect(cards(page)).toHaveCount(27);
|
||||
await expect(page.locator('.aniimo-result:not([data-form="prismana_form"])')).toHaveCount(0);
|
||||
await expect(page.getByRole("tablist")).toHaveCount(0);
|
||||
expect(await page.locator('img[src="/static/icons/prismana.svg"]').evaluate((image) => image.complete && image.naturalWidth > 0)).toBe(true);
|
||||
await page.getByRole("searchbox").fill("iris");
|
||||
await expect(cards(page)).toHaveCount(3);
|
||||
await page.locator('.game-breadcrumb a[href="#/games/aniimo"]').click();
|
||||
await page.locator('a[href="#/games/aniimo/aniimos"]').click();
|
||||
await expect(prismana).toBeChecked();
|
||||
await expect(cards(page)).toHaveCount(3);
|
||||
await page.getByRole("button", { name: "Regrouper les variantes", exact: true }).click();
|
||||
await expect(cards(page)).toHaveCount(3);
|
||||
await page.getByRole("searchbox").fill("emberpup");
|
||||
await expect(page.getByRole("heading", { name: "Aucun résultat" })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Réinitialiser les filtres", exact: true }).click();
|
||||
await expect(prismana).not.toBeChecked();
|
||||
await expect(cards(page)).toHaveCount(208);
|
||||
await expect(page.getByRole("button", { name: "Regrouper les variantes", exact: true })).toHaveAttribute("aria-pressed", "false");
|
||||
expect(await page.evaluate(() => globalThis.document.documentElement.scrollWidth <= globalThis.innerWidth)).toBe(true);
|
||||
});
|
||||
}
|
||||
|
||||
test("Aniimo matches MHW card motion, surfaces and filter controls", async ({ page }) => {
|
||||
test.setTimeout(45000);
|
||||
async function styles(locator, properties) {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,19 @@ test("name searches accept French, English, accents and case", () => {
|
|||
assert.deepEqual(filter({ name: "inexistant-xyz" }), []);
|
||||
});
|
||||
|
||||
test("Prismana-only filtering combines with other criteria before grouping", () => {
|
||||
assert.equal(createAniimoFilters().prismanaOnly, false);
|
||||
const expected = data.variants.filter((variant) => variant.form === "prismana_form");
|
||||
assert.ok(expected.length > 0);
|
||||
assert.deepEqual(filter({ prismanaOnly: true }), expected);
|
||||
const variant = expected[0];
|
||||
const criteria = { name: variant.name, elements: variant.elements, roles: [variant.role], abilities: Object.fromEntries(variant.homeland_abilities.map(({ type, level }) => [type, level])) };
|
||||
assert.deepEqual(filter({ ...criteria, prismanaOnly: true }), [variant]);
|
||||
assert.equal(groupAniimoVariants(expected).length, expected.length);
|
||||
assert.deepEqual(filter({ prismanaOnly: true, name: "emberpup" }), []);
|
||||
assert.equal(filter({ prismanaOnly: false }).length, data.variants.length);
|
||||
});
|
||||
|
||||
test("Prismana remains the final variation in data and grouped results", () => {
|
||||
for (const group of groupAniimoVariants(data.variants)) {
|
||||
if (group.some((variant) => variant.form === "prismana_form")) assert.equal(group.at(-1).form, "prismana_form");
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ export function validateSiteContent(site) {
|
|||
"aniimo.filters",
|
||||
"aniimo.name",
|
||||
"aniimo.searchPlaceholder",
|
||||
"aniimo.prismanaOnly",
|
||||
"aniimo.elements",
|
||||
"aniimo.roles",
|
||||
"aniimo.role",
|
||||
|
|
|
|||
|
|
@ -2740,6 +2740,7 @@
|
|||
"filters": "Filtres",
|
||||
"name": "Nom",
|
||||
"searchPlaceholder": "Nom français ou anglais…",
|
||||
"prismanaOnly": "Prismana uniquement",
|
||||
"elements": "Éléments",
|
||||
"roles": "Rôles",
|
||||
"role": "Rôle",
|
||||
|
|
|
|||
13
website/public/static/icons/prismana.svg
Normal file
13
website/public/static/icons/prismana.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Rôle : représente les formes Prismana par une étoile arc-en-ciel. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<defs>
|
||||
<linearGradient id="rainbow" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#fb7185"/>
|
||||
<stop offset=".25" stop-color="#facc15"/>
|
||||
<stop offset=".5" stop-color="#4ade80"/>
|
||||
<stop offset=".75" stop-color="#38bdf8"/>
|
||||
<stop offset="1" stop-color="#c084fc"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="m12 2 3.1 6.4 7.1 1-5.1 5 1.2 7.1-6.3-3.4-6.3 3.4 1.2-7.1-5.1-5 7.1-1Z" fill="url(#rainbow)" stroke="#fff" stroke-opacity=".45" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 672 B |
|
|
@ -31,6 +31,11 @@ export function AniimoFilters({ filters, onChange, options, t, content }) {
|
|||
<span>{content.name}</span>
|
||||
<input className="nds-input" data-size="sm" type="search" placeholder={content.searchPlaceholder} value={filters.name} onChange={(event) => onChange({ ...filters, name: event.target.value })} />
|
||||
</label>
|
||||
<label className={`filter-chip nds-chip ${filters.prismanaOnly ? "active" : ""}`} data-size="sm">
|
||||
<input className="nds-checkbox" data-size="sm" type="checkbox" checked={filters.prismanaOnly} onChange={(event) => onChange({ ...filters, prismanaOnly: event.target.checked })} />
|
||||
<img src="/static/icons/prismana.svg" alt="" width="16" height="16" />
|
||||
<span>{content.prismanaOnly}</span>
|
||||
</label>
|
||||
<div className="aniimo-filter-sections filter-options legacy-scrollbar" role="region" aria-label={content.filters} tabIndex="0">
|
||||
{["roles", "elements", "abilities"].map((key) => (
|
||||
<div key={key} className="aniimo-filter-section-surface">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Rôle : prépare les variantes Aniimo et applique les critères avant leur regroupement.
|
||||
export function createAniimoFilters() {
|
||||
return { name: "", elements: [], roles: [], abilities: {}, elementsLogic: "and", abilitiesLogic: "and", grouped: true };
|
||||
return { name: "", prismanaOnly: false, elements: [], roles: [], abilities: {}, elementsLogic: "and", abilitiesLogic: "and", grouped: true };
|
||||
}
|
||||
|
||||
export const ANIIMO_STATS = ["hp", "atk", "p_def", "m_def", "break", "regen"];
|
||||
|
|
@ -51,6 +51,7 @@ export function filterAniimoVariants(variants, filters) {
|
|||
const abilities = Object.entries(filters.abilities);
|
||||
return variants.filter((variant) => (
|
||||
(!search || variant.searchNames.some((name) => name.includes(search)))
|
||||
&& (!filters.prismanaOnly || variant.form === "prismana_form")
|
||||
&& matchesSelection(filters.elements, filters.elementsLogic, (element) => variant.elements.includes(element))
|
||||
&& (!filters.roles.length || filters.roles.includes(variant.role))
|
||||
&& matchesSelection(abilities, filters.abilitiesLogic, ([type, minimum]) => variant.homeland_abilities.some((ability) => ability.type === type && ability.level >= minimum))
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
.aniimo-home-card img { padding: 0 15%; object-fit: cover; object-position: 50% 38%; }
|
||||
.aniimo-layout .game-results { min-width: 0; }
|
||||
.aniimo-filters { min-width: 0; }
|
||||
.aniimo-filters > .filter-chip { margin-bottom: var(--space-3); }
|
||||
.aniimo-filter-sections { gap: 16px; }
|
||||
.aniimo-filter-section-surface + .aniimo-filter-section-surface { padding-top: 12px; border-top: 1px solid var(--color-border); }
|
||||
.aniimo-filter-section {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue