Improve compact desktop usability and action guidance
All checks were successful
Deploy Sokko G / deploy (push) Successful in 19s

This commit is contained in:
Shinuwa 2026-10-05 21:47:16 +02:00
parent 0ac1a0974e
commit 0fc18cd8b9
24 changed files with 325 additions and 90 deletions

View file

@ -25,7 +25,7 @@ export function ConfirmModal({ title, message, confirmLabel = "Confirmer", cance
);
}
export function NotificationToast({ id, message, durationMs = 120000, actionLabel = "", onAction, onClose }) {
export function NotificationToast({ id, message, durationMs = 6000, actionLabel = "", onAction, onClose }) {
useEffect(() => {
if (durationMs <= 0) return undefined;
const timeoutId = window.setTimeout(() => onClose(id), durationMs);

View file

@ -165,7 +165,7 @@ export function CopyChecklistItemsButton({
return (
<>
<button
className="filter-reset-button results-copy-button nds-icon-button"
className={filteredResults ? "results-copy-button nds-button" : "filter-reset-button results-copy-button nds-icon-button"}
data-size="xs"
type="button"
onClick={filteredResults ? () => setOpen(true) : copyItems}
@ -174,6 +174,7 @@ export function CopyChecklistItemsButton({
title={copied ? "Copié" : title}
>
<Icon name="copy" />
{filteredResults && <span>{content.resultsActionLabel || "Utiliser les résultats"}</span>}
</button>
{filteredResults && open && (
<ChecklistResultsModal

View file

@ -360,7 +360,11 @@ function ToolboxView({ siteContent, toolbox, toolboxGame, embedded, actions, sto
)}
</div>
<div className="actions nds-toolbar">
<AddToolControls onAdd={addModule} />
<AddToolControls
categories={siteContent.library.categories}
descriptions={siteContent.toolboxes.addToolDescriptions}
onAdd={addModule}
/>
</div>
</section>
<ToolboxSpaces

View file

@ -3,12 +3,17 @@ import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
import { CompactDropdown } from "../../../components/CompactDropdown.jsx";
import { Icon } from "../../../components/Icon.jsx";
import { Tooltip } from "../../../components/Tooltip.jsx";
import { lockBodyScroll } from "../../../utils/bodyScrollLock.js";
import { TOOLBOX_MODULES } from "../toolboxModuleRegistry.js";
import { LIBRARY_CATEGORY_DEFINITIONS, TOOLBOX_MODULES } from "../toolboxModuleRegistry.js";
export function AddToolControls({ onAdd }) {
export function AddToolControls({ categories, descriptions, onAdd }) {
const [open, setOpen] = useState(false);
const modules = Object.entries(TOOLBOX_MODULES);
const moduleCategories = LIBRARY_CATEGORY_DEFINITIONS.map((category) => ({
...category,
title: categories[category.key]
}));
useEffect(() => {
if (!open) return undefined;
@ -20,6 +25,19 @@ export function AddToolControls({ onAdd }) {
setOpen(false);
}
function renderToolCard(type) {
const module = TOOLBOX_MODULES[type];
return (
<button key={type} className="tool-add-card" type="button" onClick={() => addTool(type)}>
<span className="module-icon" aria-hidden="true">
<span className={`module-icon-svg module-icon-${module.icon}`} />
</span>
<strong>{module.label}</strong>
<span>{descriptions[type]}</span>
</button>
);
}
return (
<>
<div
@ -43,28 +61,31 @@ export function AddToolControls({ onAdd }) {
menuClassName="tool-quick-add"
label="Ajout rapide"
renderTrigger={({ open: quickOpen, toggle }) => (
<button
<Tooltip
as="button"
className={`tool-add-quick-toggle nds-icon-button ${quickOpen ? "active" : ""}`}
content={quickOpen ? "Masquer l'ajout rapide" : "Afficher l'ajout rapide"}
data-size="sm"
data-variant="primary"
data-edge="right"
data-border="joined"
type="button"
onClick={toggle}
aria-label="Afficher l'ajout rapide"
aria-label={quickOpen ? "Masquer l'ajout rapide" : "Afficher l'ajout rapide"}
aria-expanded={quickOpen}
title="Ajout rapide"
>
<Icon name="dropdown" />
</button>
</Tooltip>
)}
>
{({ close }) => (
<>
{modules.map(([type, module]) => (
<button
<Tooltip
as="button"
key={type}
className="tool-quick-add-button nds-icon-button"
content={module.label}
data-size="sm"
type="button"
onClick={() => {
@ -72,35 +93,32 @@ export function AddToolControls({ onAdd }) {
close();
}}
aria-label={`Ajouter ${module.label}`}
title={module.label}
>
<span className="module-icon" aria-hidden="true">
<span className={`module-icon-svg module-icon-${module.icon}`} />
</span>
</button>
</Tooltip>
))}
</>
)}
</CompactDropdown>
</div>
{open && createPortal(
<div className="tool-add-modal-root" role="dialog" aria-modal="true" aria-labelledby="tool-add-modal-title">
<div className="tool-add-modal-root">
<div className="confirm-backdrop nds-modal-backdrop" onClick={() => setOpen(false)} />
<section className="confirm-modal nds-modal tool-add-modal">
<section className="confirm-modal nds-modal tool-add-modal" role="dialog" aria-modal="true" aria-labelledby="tool-add-modal-title">
<header>
<h2 id="tool-add-modal-title">Ajouter un outil</h2>
<button className="drawer-close-button nds-icon-button" data-size="sm" type="button" onClick={() => setOpen(false)} aria-label="Fermer" title="Fermer">
<Icon name="close" />
</button>
</header>
<div className="tool-add-grid">
{modules.map(([type, module]) => (
<button key={type} className="tool-add-card" type="button" onClick={() => addTool(type)}>
<span className="module-icon" aria-hidden="true">
<span className={`module-icon-svg module-icon-${module.icon}`} />
</span>
<span>{module.label}</span>
</button>
<div className="tool-add-category-list">
{moduleCategories.map((category) => (
<section className="tool-add-category-section" key={category.key} aria-labelledby={`tool-add-category-${category.key}`}>
<h3 id={`tool-add-category-${category.key}`}><span>{category.title}</span></h3>
<div className="tool-add-grid">{category.types.map(renderToolCard)}</div>
</section>
))}
</div>
</section>

View file

@ -55,7 +55,7 @@ export function useToolboxAlerts({ toolboxes, moduleData, links, route, drawerGa
nextTargets.set(key, targetMs);
if (!targetMs || previousTargetMs <= lastTickMs || previousTargetMs > nowMs) return;
notify(formatAlertMessage(timerAlertTemplate, toolbox, countdown.label));
notify(formatAlertMessage(timerAlertTemplate, toolbox, countdown.label), { persistent: true });
});
}
@ -73,7 +73,7 @@ export function useToolboxAlerts({ toolboxes, moduleData, links, route, drawerGa
if (alertMs) nextTargets.set(alertKey, alertMs);
if (alertMs && previousAlertMs > lastTickMs && previousAlertMs <= nowMs) {
notify(formatAlertMessage(calendarAlertTemplate, toolbox, event.title));
notify(formatAlertMessage(calendarAlertTemplate, toolbox, event.title), { persistent: true });
}
});
@ -83,7 +83,7 @@ export function useToolboxAlerts({ toolboxes, moduleData, links, route, drawerGa
const resumeKey = `${key}:${currentOccurrence.startMs}`;
if (resumedCalendarEventsRef.current.has(resumeKey)) return;
resumedCalendarEventsRef.current.add(resumeKey);
notify(formatAlertMessage(calendarResumeTemplate, toolbox, event.title));
notify(formatAlertMessage(calendarResumeTemplate, toolbox, event.title), { persistent: true });
});
}
});

View file

@ -32,14 +32,12 @@ if (assetRecoveryUrl.searchParams.has("_asset-recovery")) {
}
const ToolboxDrawer = lazy(() => import("./features/toolboxes/ToolboxPages.jsx").then((module) => ({ default: module.ToolboxDrawer })));
const DEFAULT_NOTIFICATION_DURATION_MS = 120000;
const SHORT_NOTIFICATION_DURATION_MS = 5000;
const DEFAULT_NOTIFICATION_DURATION_MS = 6000;
function getNotificationDuration(message, options = {}) {
function getNotificationDuration(_message, options = {}) {
if (options.persistent) return 0;
if (Number.isFinite(options.durationMs)) return Math.max(1000, Number(options.durationMs));
const normalized = String(message || "").toLowerCase();
return /\bcopi[ée]|\bcoll[ée]|presse-papiers/.test(normalized) ? SHORT_NOTIFICATION_DURATION_MS : DEFAULT_NOTIFICATION_DURATION_MS;
return DEFAULT_NOTIFICATION_DURATION_MS;
}
function App() {

View file

@ -6,6 +6,7 @@ import { HomeOnboardingSection } from "./HomeOnboardingSection.jsx";
export function HomePage({ siteContent, toolboxes, actions }) {
const content = siteContent.home;
const toolCount = Object.keys(TOOLBOX_MODULES).length;
const hasToolboxes = toolboxes.length > 0;
const showOnboarding = import.meta.env.DEV || toolboxes.length === 0;
return (
<>
@ -15,7 +16,11 @@ export function HomePage({ siteContent, toolboxes, actions }) {
<h1>{content.hero.title}</h1>
<p>{content.hero.description}</p>
<div className="actions nds-toolbar">
<a className="nds-button" data-size="md" data-variant="primary" href="#/toolboxes">{content.hero.primaryAction}</a>
{hasToolboxes ? (
<a className="nds-button" data-size="md" data-variant="primary" href="#/toolboxes">{content.hero.primaryAction}</a>
) : (
<button className="nds-button" data-size="md" data-variant="primary" type="button" onClick={() => actions.setCreateModal({ tutorial: true })}>{content.hero.createAction}</button>
)}
<a className="nds-button" data-size="md" href="#/games">{content.hero.secondaryAction}</a>
</div>
</div>

View file

@ -427,14 +427,6 @@
justify-self: stretch;
}
.tool-add-modal {
width: min(420px, calc(100vw - 32px));
}
.tool-add-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 767px) {

View file

@ -13,12 +13,19 @@
font-weight: 800;
}
.results-copy-button {
.results-copy-button.nds-icon-button {
width: 32px;
min-width: 32px;
min-height: 32px;
}
.results-copy-button.nds-button {
width: auto;
min-width: 0;
min-height: 32px;
white-space: nowrap;
}
.results-checklist-modal.nds-modal {
width: min(560px, calc(100vw - 32px));
}

View file

@ -1,6 +1,10 @@
// Rôle : stylise les modales de création et d'ajout d'outil.
.tool-add-modal {
width: min(560px, calc(100vw - 32px));
@use "../design-system/controls";
.tool-add-modal.nds-modal {
width: min(900px, calc(100vw - 32px));
scrollbar-gutter: stable;
@include controls.scrollbar-control;
}
.tool-add-modal header {
@ -21,30 +25,67 @@
gap: var(--space-3);
}
.tool-add-category-list,
.tool-add-category-section {
display: grid;
gap: var(--space-3);
}
.tool-add-category-section h3 {
display: flex;
align-items: center;
gap: var(--space-2);
margin: 0;
color: var(--color-text-secondary);
font-size: var(--font-size-xs);
}
.tool-add-category-section h3::after {
height: 1px;
flex: 1;
background: var(--color-border);
content: "";
}
.tool-add-card {
display: grid;
height: 98px;
min-height: 96px;
min-width: 0;
grid-template-columns: 28px minmax(0, 1fr);
align-content: center;
justify-items: center;
gap: 8px;
padding: var(--space-3);
align-items: center;
gap: 4px 8px;
padding: 8px;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: rgba(21, 26, 48, 0.72);
text-align: center;
text-align: left;
}
.tool-add-card span:last-child {
.tool-add-card strong {
width: 100%;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--color-text-primary);
font-size: var(--font-size-sm);
line-height: 1.2;
}
.tool-add-card > span:last-child {
width: 100%;
grid-column: 1 / -1;
color: var(--color-text-secondary);
font-size: var(--font-size-xs);
line-height: 1.25;
}
.tool-add-card .module-icon {
flex: 0 0 34px;
width: 28px;
height: 28px;
}
.tool-add-card .module-icon-svg {
width: 16px;
height: 16px;
}
.tool-add-card:hover {
@ -53,6 +94,18 @@
transform: translateY(-1px);
}
@media (min-width: 720px) {
.tool-add-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
@media (min-width: 900px) {
.tool-add-grid {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
}
.toolbox-create-modal form,
.toolbox-link-modal form {
display: grid;