Add responsive game filters and hybrid toolbox drawer
All checks were successful
Deploy Sokko G / deploy (push) Successful in 20s

This commit is contained in:
Shinuwa 2026-09-27 18:00:49 +02:00
parent 1ee460b94f
commit 00e73f0062
32 changed files with 1202 additions and 241 deletions

View file

@ -1,7 +1,10 @@
// Rôle : expose en développement un catalogue vivant et interactif des contrôles NDS.
import { useState } from "react";
import { CompactDropdown } from "../components/CompactDropdown.jsx";
import { Icon } from "../components/Icon.jsx";
import { MixTagsInput } from "../components/MixTagsInput.jsx";
import { Tooltip } from "../components/Tooltip.jsx";
import { ActiveFilterBadges } from "../features/games/ActiveFilterBadges.jsx";
const SIZE_VALUES = {
field: ["xs", "sm", "md"],
@ -419,14 +422,9 @@ const PATTERN_COMPONENTS = [
name: "Filter Panel",
className: "nds-filter-panel",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
attributes: {},
render: () => (
<aside className="nds-filter-panel design-system-pattern-card">
<header className="nds-section-heading"><strong>Filtres</strong><button className="nds-icon-button" data-size="xs" type="button" aria-label="Reset"><Icon name="rubber" /></button></header>
<button className="nds-chip" data-size="sm" type="button" aria-pressed="true">Actif</button>
</aside>
)
variants: ["vertical", "horizontal-compact", "horizontal-menu"],
attributes: { "data-variant": ["vertical", "horizontal-compact", "horizontal-menu"] },
render: ({ attributes, variant }) => <FilterPanelPreview attributes={attributes} variant={variant} />
},
{
id: "nds-reorder-indicator",
@ -714,6 +712,70 @@ function NativeSwitchPreview({ disabled = false, initialChecked = false }) {
);
}
function FilterPanelPreview({ attributes, variant }) {
const [search, setSearch] = useState("");
const [logic, setLogic] = useState("and");
const [selected, setSelected] = useState(["fire"]);
const options = [
{ id: "fire", label: "Feu", icon: "fire" },
{ id: "water", label: "Eau", icon: "tear" },
{ id: "nature", label: "Nature", icon: "leaf" }
];
const toggle = (id) => setSelected((current) => current.includes(id) ? current.filter((value) => value !== id) : [...current, id]);
const activeBadges = [
...(search.trim() ? [{ id: "search", label: `Nom : ${search.trim()}`, onRemove: () => setSearch("") }] : []),
...selected.map((id) => ({ id, label: `Éléments : ${options.find((option) => option.id === id)?.label}`, onRemove: () => toggle(id) }))
];
const reset = () => {
setSearch("");
setLogic("and");
setSelected([]);
};
if (variant === "vertical") {
return (
<aside className="nds-filter-panel design-system-filter-preview" {...attributes}>
<header className="nds-section-heading"><strong>Filtres</strong><button className="nds-icon-button" data-size="xs" type="button" aria-label="Réinitialiser" onClick={reset}><Icon name="rubber" /></button></header>
<label className="field compact"><span>Nom</span><input className="nds-input" data-size="sm" type="search" value={search} placeholder="Rechercher..." onChange={(event) => setSearch(event.target.value)} /></label>
<div className="filter-options">
{options.map((option) => <label className={`filter-chip nds-chip ${selected.includes(option.id) ? "active" : ""}`} data-size="sm" key={option.id}><input className="nds-checkbox" data-size="sm" type="checkbox" checked={selected.includes(option.id)} onChange={() => toggle(option.id)} /><Icon name={option.icon} /><span>{option.label}</span></label>)}
</div>
</aside>
);
}
return (
<aside className="nds-filter-panel design-system-filter-preview" {...attributes}>
<label className="nds-filter-search"><span className="sr-only">Nom</span><input className="nds-input" data-size="sm" type="search" value={search} placeholder="Rechercher..." onChange={(event) => setSearch(event.target.value)} /></label>
<div className="nds-filter-group">
{variant === "horizontal-compact" ? (
<>
<span className="nds-filter-group-label">Éléments</span>
<div className="nds-filter-direct-options" role="group" aria-label="Éléments">
{options.map((option) => <Tooltip as="button" className={`nds-filter-option ${selected.includes(option.id) ? "active" : ""}`} content={option.label} key={option.id} type="button" aria-label={option.label} aria-pressed={selected.includes(option.id)} onClick={() => toggle(option.id)}><Icon name={option.icon} /><span className="nds-filter-option-label">{option.label}</span></Tooltip>)}
</div>
</>
) : (
<CompactDropdown
className="nds-filter-dropdown"
menuClassName="nds-filter-menu nds-popover"
menuProps={{ role: "dialog", "aria-label": "Catégories", "data-columns": "2" }}
label="Catégories"
renderTrigger={({ open, toggle: toggleMenu, triggerRef }) => <button ref={triggerRef} className={`nds-filter-menu-trigger nds-select-trigger ${open ? "active" : ""}`} data-size="sm" type="button" aria-expanded={open} aria-haspopup="dialog" onClick={toggleMenu}><Icon name="checklist" /><span>Catégories</span>{selected.length > 0 && <strong className="nds-filter-count">{selected.length}</strong>}</button>}
>
{() => <><div className="filter-logic nds-filter-logic"><span>Correspondance</span><div className="filter-logic-group nds-radio-button-group" data-size="xs" role="radiogroup" aria-label="Correspondance">{["and", "or"].map((value) => <button className={logic === value ? "active" : ""} key={value} type="button" role="radio" aria-checked={logic === value} onClick={() => setLogic(value)}>{value === "and" ? "Et" : "Ou"}</button>)}</div></div><div className="nds-filter-menu-options">{options.map((option) => <label className={`filter-chip nds-chip nds-filter-menu-option ${selected.includes(option.id) ? "active" : ""}`} data-size="sm" key={option.id}><input className="nds-checkbox" data-size="sm" type="checkbox" checked={selected.includes(option.id)} onChange={() => toggle(option.id)} /><Icon name={option.icon} /><span>{option.label}</span></label>)}</div></>}
</CompactDropdown>
)}
</div>
{variant === "horizontal-compact" && <div className="nds-filter-group">
<div className="filter-logic nds-filter-logic"><span>Correspondance</span><div className="filter-logic-group nds-radio-button-group" data-size="xs" role="radiogroup" aria-label="Correspondance">{["and", "or"].map((value) => <button className={logic === value ? "active" : ""} key={value} type="button" role="radio" aria-checked={logic === value} onClick={() => setLogic(value)}>{value === "and" ? "Et" : "Ou"}</button>)}</div></div>
</div>}
<button className="nds-icon-button nds-filter-reset" data-size="xs" type="button" aria-label="Réinitialiser" onClick={reset}><Icon name="rubber" /></button>
<ActiveFilterBadges items={activeBadges} />
</aside>
);
}
function DatePickerPreview() {
const [activeDay, setActiveDay] = useState(2);
const days = Array.from({ length: 35 }, (_, index) => {