Polish Nebula design system controls before release
All checks were successful
Deploy Sokko G / deploy (push) Successful in 16s

This commit is contained in:
Shinuwa 2026-08-29 16:07:16 +02:00
parent 817e21567b
commit 07de342fc2
31 changed files with 79 additions and 58 deletions

View file

@ -78,6 +78,7 @@ Règles :
- `data-border="joined"` masque les bordures de jonction déduites depuis `data-edge` ;
- `data-border="none"` masque toutes les bordures sans changer la taille, le fond, le hover ou l'état actif ;
- `data-axis="horizontal|vertical"` reste réservé aux composants dont l'axe est métier, comme `nds-scrollbox` ;
- les boutons icônes de réorganisation utilisent `nds-icon-button` avec `data-variant="secondary"` et une classe métier contenant `drag-handle` ; le curseur `grab/grabbing` est alors géré par le NDS ;
- les états fonctionnels restent portés par le HTML et l'application : `disabled`, `aria-pressed="true"`, `.active`, `.is-active`.
Exemples :

View file

@ -1683,8 +1683,8 @@
"importOne": "Importer une toolbox",
"preferencesLabel": "Préférences d'interface",
"toastPositionLabel": "Positionnement des alertes",
"toastPositionLeft": "Afficher les alertes en bas à gauche",
"toastPositionRight": "Afficher les alertes en bas à droite",
"toastPositionLeft": "Positionner les alertes en bas à gauche",
"toastPositionRight": "Positionner les alertes en bas à droite",
"summary": "Créez des espaces modulaires pour regrouper vos outils de session : notes, suivis, références, captures et futurs outils restent accessibles rapidement, en local, par jeu ou par usage.",
"creationTemplate": {
"memos": {

View file

@ -1,16 +1,15 @@
// Rôle : affiche le switch global de position des notifications internes.
// Rôle : expose le choix gauche/droite des notifications via un Radio Button Group NDS.
import { Icon } from "./Icon.jsx";
export function ToastPositionSwitch({ position, labels, onChange }) {
const normalizedPosition = position === "left" ? "left" : "right";
return (
<div
className="toast-position-switch nds-radio-button-group"
data-size="xs"
className="nds-radio-button-group"
data-size="md"
role="radiogroup"
data-position={normalizedPosition}
aria-label={normalizedPosition === "left" ? labels.left : labels.right}
title={labels.title}
aria-label={labels.title}
>
<button
className={normalizedPosition === "left" ? "active" : ""}
@ -18,6 +17,7 @@ export function ToastPositionSwitch({ position, labels, onChange }) {
role="radio"
aria-checked={normalizedPosition === "left"}
aria-label={labels.left}
title={labels.left}
onClick={() => onChange("left")}
>
<Icon name="toast-left" />
@ -28,6 +28,7 @@ export function ToastPositionSwitch({ position, labels, onChange }) {
role="radio"
aria-checked={normalizedPosition === "right"}
aria-label={labels.right}
title={labels.right}
onClick={() => onChange("right")}
>
<Icon name="toast-right" />

View file

@ -36,7 +36,7 @@ export function ToolboxCard({ toolbox, game, actions, reorder }) {
<button
className="toolbox-card-drag-handle nds-icon-button"
data-size="md"
data-variant="ghost"
data-variant="secondary"
type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, toolbox.id)}
aria-label={`Déplacer ${toolbox.name}`}

View file

@ -43,7 +43,7 @@ export function CalculatorEntries({ entries, parentId, getScopedParentId, active
<button
className="calculator-drag-handle nds-icon-button"
data-size="xs"
data-variant="ghost"
data-variant="secondary"
type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, entry.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${entry.label || formatResult(entry.value)}`}

View file

@ -118,7 +118,7 @@ export function CalendarAlertModeSwitch({ mode, textContent, onChange }) {
};
return (
<div className="timer-alert-mode-switch calendar-alert-mode-switch nds-radio-button-group" data-size="xs" role="group" aria-label={textContent.alertModeTitle || "Mode d'alerte"}>
<div className="timer-alert-mode-switch calendar-alert-mode-switch nds-radio-button-group" data-size="md" role="group" aria-label={textContent.alertModeTitle || "Mode d'alerte"}>
{ALERT_MODES.map((option) => (
<button
key={option.mode}

View file

@ -127,7 +127,7 @@ export function ChecklistModule({ toolboxId, moduleId, context, editing, textExc
)}
{namedSections.length > 0 && (
<div className="checklist-toolbar">
<div className="checklist-complete-mode-group nds-radio-button-group" data-size="xs" role="radiogroup" aria-label={textContent.completedSectionsModeTitle || "Affichage des catégories terminées"}>
<div className="checklist-complete-mode-group nds-radio-button-group" data-size="md" role="radiogroup" aria-label={textContent.completedSectionsModeTitle || "Affichage des catégories terminées"}>
{[
{ mode: "visible", label: textContent.completedSectionsVisibleLabel || "Visible", icon: "eye-open" },
{ mode: "reduced", label: textContent.completedSectionsReducedLabel || "Réduit", icon: "hide" },

View file

@ -69,7 +69,7 @@ export function ChecklistSection({ section, context, data, reorder, onRenameSect
<button
className="checklist-drag-handle nds-icon-button"
data-size="xs"
data-variant="ghost"
data-variant="secondary"
type="button"
onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: section.id })}
aria-label={`${textContent.reorderSectionTitle || "Déplacer la catégorie"} ${section.title}`}
@ -166,7 +166,7 @@ function ChecklistItem({ item, section, context, reorder, items, save, onUpdateI
<button
className="checklist-drag-handle nds-icon-button"
data-size="xs"
data-variant="ghost"
data-variant="secondary"
type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, item.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${item.label}`}

View file

@ -29,7 +29,7 @@ export function CombosList({
<button
className="task-planner-drag-handle combo-drag-handle nds-icon-button"
data-size="xs"
data-variant="ghost"
data-variant="secondary"
type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, combo.id)}
aria-label={`${textContent.reorderComboTitle || "Déplacer le combo"} ${combo.name}`}
@ -92,7 +92,7 @@ export function CombosList({
<button
className="task-planner-category-drag-handle nds-icon-button"
data-size="xs"
data-variant="ghost"
data-variant="secondary"
type="button"
onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: category })}
aria-label={`${textContent.reorderCategoryTitle || "Déplacer la catégorie"} ${category}`}

View file

@ -71,7 +71,7 @@ export function CountersModule({ toolboxId, moduleId, context, editing, textExch
<button
className="counter-drag-handle nds-icon-button"
data-size="sm"
data-variant="ghost"
data-variant="secondary"
type="button"
onPointerDown={(event) => itemReorder.startDrag(event, counter.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${counter.label}`}

View file

@ -145,7 +145,7 @@ function TraitCategoryList({ traits, categories, collapsedCategories, reorder, t
return (
<section className={className} key={category} {...reorder.getGroupProps({ groupId: category })}>
<div className="equipment-planner-trait-category-header">
<button type="button" className="task-planner-category-drag-handle nds-icon-button" data-size="xs" data-variant="ghost" onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: category })} title={textContent.reorderCategoryTitle || "Déplacer la catégorie"}>
<button type="button" className="task-planner-category-drag-handle nds-icon-button" data-size="xs" data-variant="secondary" onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: category })} title={textContent.reorderCategoryTitle || "Déplacer la catégorie"}>
<Icon name="drag" />
</button>
<strong>{category}</strong>
@ -182,7 +182,7 @@ function EquipmentTraitRow({ trait, className, itemProps, editing, textContent,
<li className={className} {...itemProps}>
{editing ? (
<>
<button type="button" className="task-planner-drag-handle nds-icon-button" data-size="xs" data-variant="ghost" onPointerDown={onStartDrag} title={textContent.reorderTraitTitle || "Déplacer"}>
<button type="button" className="task-planner-drag-handle nds-icon-button" data-size="xs" data-variant="secondary" onPointerDown={onStartDrag} title={textContent.reorderTraitTitle || "Déplacer"}>
<Icon name="drag" />
</button>
<IconChoiceDropdown

View file

@ -32,7 +32,7 @@ export function EquipmentItem({ equipment, reorder, textContent, onUpdate, onDel
<button
className="task-planner-drag-handle nds-icon-button"
data-size="sm"
data-variant="ghost"
data-variant="secondary"
type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, equipment.id)}
aria-label={`${textContent.reorderEquipmentTitle || "Déplacer"} ${equipment.name}`}

View file

@ -61,7 +61,7 @@ export function GoalTreeBranch({ nodes, parentId, selectedNodeId, editing, reord
<button
className="goal-tree-drag-handle nds-icon-button"
data-size="micro"
data-variant="ghost"
data-variant="secondary"
type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, node.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${node.title}`}

View file

@ -200,7 +200,7 @@ function GoalTreeAdvancedPanel({ selectedNode, selectedNodeIsStep, selectedProgr
<div className="goal-tree-form-section-title">{textContent.progressSectionTitle || "Progression"}</div>
<button
className={`tool-split-scroll-toggle goal-tree-progress-mode-toggle nds-switch ${selectedProgressMode === "auto" ? "active" : ""}`}
data-size="lg"
data-size="md"
type="button"
onClick={() => onUpdateNode(selectedNode.id, (node) => ({ ...node, progressMode: selectedProgressMode === "auto" ? "manual" : "auto" }))}
aria-pressed={selectedProgressMode === "auto"}

View file

@ -106,7 +106,7 @@ export function ImagesModule({ toolboxId, moduleId, context, editing, textExchan
<button
className="image-drag-handle nds-icon-button"
data-size="sm"
data-variant="ghost"
data-variant="secondary"
type="button"
onPointerDown={(event) => itemReorder.startDrag(event, image.id)}
aria-label={textContent.reorderAriaLabel || "Déplacer l'image"}

View file

@ -84,7 +84,7 @@ export function LinksModule({ toolboxId, moduleId, context, editing, textExchang
<button
className="link-drag-handle nds-icon-button"
data-size="sm"
data-variant="ghost"
data-variant="secondary"
type="button"
onPointerDown={(event) => itemReorder.startDrag(event, link.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${link.title || link.url}`}

View file

@ -138,7 +138,7 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
<button
className="memo-drag-handle nds-icon-button"
data-size="sm"
data-variant="ghost"
data-variant="secondary"
type="button"
onPointerDown={(event) => itemReorder.startDrag(event, memo.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${memo.text}`}

View file

@ -296,7 +296,7 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
renderTrigger={({ open, toggle }) => (
<button
className={`notepad-pages-toggle nds-icon-button ${open ? "active" : ""}`}
data-size="sm"
data-size="md"
data-variant="panel-toggle"
data-edge="right"
data-border="joined"

View file

@ -162,7 +162,7 @@ export function TaskPlannerBranch({
<button
className="task-planner-category-drag-handle nds-icon-button"
data-size="xs"
data-variant="ghost"
data-variant="secondary"
type="button"
onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: group.category, parentId })}
aria-label={`${textContent.categoryReorderTitle || "Déplacer la catégorie"} ${group.category}`}

View file

@ -67,7 +67,7 @@ export function TaskPlannerItem({
<button
className="task-planner-drag-handle nds-icon-button"
data-size="xs"
data-variant="ghost"
data-variant="secondary"
type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, task.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${task.title}`}

View file

@ -104,6 +104,7 @@ export function CountdownList({ countdowns, nowMs, textContent, reorder, reorder
<button
className="timer-drag-handle nds-icon-button"
data-size="xs"
data-variant="secondary"
type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, countdown.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${countdown.label}`}

View file

@ -16,7 +16,7 @@ export function TimerValue({ ms, compact = false, showCentiseconds = true }) {
);
}
export function TimerAlertModeSwitch({ mode, blocked, textContent, onChange }) {
export function TimerAlertModeSwitch({ mode, blocked, textContent, size = "xs", onChange }) {
const labels = {
off: textContent.alertOffTitle || "Pas d'alerte",
visible: textContent.alertVisibleTitle || "Alerte quand la toolbox est affichée",
@ -27,7 +27,7 @@ export function TimerAlertModeSwitch({ mode, blocked, textContent, onChange }) {
return (
<div
className={`timer-alert-mode-switch nds-radio-button-group ${blocked ? "is-blocked" : ""}`}
data-size="xs"
data-size={size}
role="group"
aria-label={textContent.alertModeTitle || "Mode d'alerte"}
title={blocked ? blockedTitle : undefined}

View file

@ -190,7 +190,7 @@ function ModuleShell({ toolbox, module, context, reorder, itemGroup = "", onRena
<button
className="module-drag-handle nds-icon-button"
data-size="md"
data-variant="ghost"
data-variant="secondary"
type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, module.id)}
aria-label={`Déplacer ${module.title || label}`}

View file

@ -2,7 +2,6 @@
import { useState } from "react";
import { Icon } from "../components/Icon.jsx";
import { MixTagsInput } from "../components/MixTagsInput.jsx";
import { ToastPositionSwitch } from "../components/ToastPositionSwitch.jsx";
const SIZE_VALUES = {
field: ["xs", "sm", "md"],
@ -566,20 +565,18 @@ function DatePickerButtonsSection() {
function SpecializedComponentsSection() {
const [mixValue, setMixValue] = useState("base + bonus");
const [smallMixValue, setSmallMixValue] = useState("base * 2");
const [toastPosition, setToastPosition] = useState("right");
return (
<ComponentSection id="specialized-components" title="Specialized Components" meta="not NDS public classes">
<VariantMatrix
title="Composants spécialisés conservés"
note="Ces éléments restent spécialisés : ils ne créent pas de classe de famille NDS publique."
columns={["MixTagsInput", "MixTagsInput small", "Toast switch"]}
columns={["MixTagsInput", "MixTagsInput small"]}
rows={[{
label: "interactive",
cells: [
<ControlSample label="MixTagsInput"><MixTagsInput value={mixValue} onChange={setMixValue} tokens={MIX_TAG_TOKENS} insertToken={{ value: "base", icon: "add", title: "Insérer base" }} aria-label="MixTagsInput standard" /></ControlSample>,
<ControlSample label="MixTagsInput size=small"><MixTagsInput size="small" value={smallMixValue} onChange={setSmallMixValue} tokens={MIX_TAG_TOKENS} insertToken={{ value: "bonus", icon: "add", title: "Insérer bonus" }} aria-label="MixTagsInput compact" /></ControlSample>,
<ControlSample label="ToastPositionSwitch"><ToastPositionSwitch position={toastPosition} labels={{ left: "Notifications à gauche", right: "Notifications à droite", title: "Position des notifications" }} onChange={setToastPosition} /></ControlSample>
<ControlSample label="MixTagsInput size=small"><MixTagsInput size="small" value={smallMixValue} onChange={setSmallMixValue} tokens={MIX_TAG_TOKENS} insertToken={{ value: "bonus", icon: "add", title: "Insérer bonus" }} aria-label="MixTagsInput compact" /></ControlSample>
]
}]}
/>

View file

@ -262,6 +262,20 @@
@include controls.icon-button-icon;
}
.nds-icon-button[class*="drag-handle"] {
cursor: grab;
touch-action: none;
user-select: none;
}
.nds-icon-button[class*="drag-handle"]:active {
cursor: grabbing;
}
.nds-icon-button[class*="drag-handle"]:disabled {
cursor: not-allowed;
}
.nds-icon-button:hover:not([data-variant="primary"], [data-variant="danger"], [data-variant="ghost"], .active, .is-active, [aria-pressed="true"]) {
@include controls.icon-button-hover;
}
@ -349,6 +363,15 @@
@include controls.icon-button-danger-hover-icon;
}
.nds-icon-button[class*="drag-handle"]:hover {
cursor: grab;
}
.nds-icon-button[class*="drag-handle"]:active,
.nds-icon-button[class*="drag-handle"]:hover:active {
cursor: grabbing;
}
.nds-icon-button[data-edge="left"] {
@include controls.button-edge(left);
}

View file

@ -16,7 +16,7 @@
}
.checklist-complete-mode-group {
@include controls.radio-button-group(xs);
@include controls.radio-button-group;
width: fit-content;
align-items: center;
}
@ -93,30 +93,30 @@
}
.timer-alert-mode-switch {
@include controls.radio-button-group(xs);
@include controls.radio-button-group;
width: fit-content;
align-items: center;
opacity: 0.78;
}
.checklist-complete-mode-button,
.timer-alert-mode-button {
@include controls.radio-button-group-option(xs);
.checklist-complete-mode-button {
@include controls.radio-button-group-option;
display: inline-grid;
width: 36px;
min-width: 36px;
place-items: center;
font-size: var(--font-size-xs);
}
.checklist-complete-mode-button .ui-icon,
.timer-alert-mode-button .ui-icon {
@include controls.radio-button-group-option-icon(xs);
.checklist-complete-mode-button .ui-icon {
@include controls.radio-button-group-option-icon;
}
.timer-alert-mode-button {
width: 24px;
min-width: 24px;
@include controls.radio-button-group-option;
display: inline-grid;
place-items: center;
}
.timer-alert-mode-button .ui-icon {
@include controls.radio-button-group-option-icon;
}
.checklist-section-items {

View file

@ -49,7 +49,8 @@
padding: 0;
}
.module-drag-handle {
.module-drag-handle,
.module-drag-handle.nds-icon-button {
flex: 0 0 40px;
cursor: grab;
opacity: 0.62;
@ -62,7 +63,8 @@
opacity: 1;
}
.module-drag-handle:active {
.module-drag-handle:active,
.module-drag-handle.nds-icon-button:active {
cursor: grabbing;
}

View file

@ -253,7 +253,7 @@
}
.timer-entry.has-drag-handle {
grid-template-columns: 30px minmax(0, 1fr) 34px;
grid-template-columns: 30px minmax(0, 1fr) 30px;
}
.tool-split-entry-list li.is-editing > .timer-entry.has-single-action {
@ -261,7 +261,7 @@
}
.tool-split-entry-list li.is-editing > .timer-entry.has-drag-handle {
grid-template-columns: 30px auto minmax(0, 1fr) 34px;
grid-template-columns: 30px auto minmax(0, 1fr) 30px;
}
.timer-drag-handle {

View file

@ -76,7 +76,3 @@
.toolbox-preferences-row + .cards {
margin-top: 0;
}
.toast-position-switch {
grid-template-columns: repeat(2, 30px);
}

View file

@ -29,7 +29,7 @@
position: relative;
}
.tool-split-entry-list .tool-split-entry-list > li::before {
.tool-split-entry-list .tool-split-entry-list > li::after {
content: "";
position: absolute;
top: 18px;

View file

@ -5,7 +5,7 @@
position: relative;
display: grid;
gap: 10px;
padding: 10px;
padding: 6px;
border: 1px solid rgba(165, 180, 252, 0.12);
border-radius: var(--radius-md);
background: rgba(5, 7, 17, 0.36);