Migrate site controls to Nebula design system
All checks were successful
Deploy Sokko G / deploy (push) Successful in 17s

This commit is contained in:
Shinuwa 2026-08-29 09:16:58 +02:00
parent e3d0925eb1
commit 817e21567b
127 changed files with 5380 additions and 2363 deletions

View file

@ -0,0 +1,22 @@
{"id":"int-8d5f8f5714677d91360a20177b1f5a9e","kind":"field_change","created_at":"2026-08-27T12:58:35.614943857Z","actor":"Shinuwa","issue_id":"sokko-g-lki","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed"}}
{"id":"int-206ccb97f9d09712e2b4136d115a0e43","kind":"field_change","created_at":"2026-08-27T13:09:45.211867088Z","actor":"Shinuwa","issue_id":"sokko-g-e7s","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed remaining toolbox control migration to NDS mixins and updated design-system catalogue examples."}}
{"id":"int-5131e4130f57a4e8a55f176a46240fad","kind":"field_change","created_at":"2026-08-27T16:19:44.570963534Z","actor":"Shinuwa","issue_id":"sokko-g-npt","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed NDS button variant cleanup and catalogue simplification."}}
{"id":"int-3db2dfcbabd4cbb63eaeedb1bea8ab47","kind":"field_change","created_at":"2026-08-27T16:43:20.612129607Z","actor":"Shinuwa","issue_id":"sokko-g-0o4","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed NDS data attribute API migration and validation."}}
{"id":"int-578a8a6aff782082d539f0e14bb0aa79","kind":"field_change","created_at":"2026-08-27T16:58:07.410812508Z","actor":"Shinuwa","issue_id":"sokko-g-alp","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Expanded /design-system into exhaustive NDS family attribute matrices."}}
{"id":"int-cc32f7330c12cf2d37b0d22650ccfb5e","kind":"field_change","created_at":"2026-08-27T17:13:59.871782552Z","actor":"Shinuwa","issue_id":"sokko-g-pp5","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Reworked /design-system into component cards with configurable attribute matrices."}}
{"id":"int-17da0005e87891c66734a766d8456459","kind":"field_change","created_at":"2026-08-27T17:23:07.045839669Z","actor":"Shinuwa","issue_id":"sokko-g-73i","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed"}}
{"id":"int-d0531629593c076b63281346c729ac3e","kind":"field_change","created_at":"2026-08-27T17:28:11.740302589Z","actor":"Shinuwa","issue_id":"sokko-g-wnh","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed"}}
{"id":"int-fb41489d0517f51fcda643c4230461fe","kind":"field_change","created_at":"2026-08-27T17:35:40.407737936Z","actor":"Shinuwa","issue_id":"sokko-g-s0a","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed"}}
{"id":"int-b7bafbc214af080356e07d2d97f1ffba","kind":"field_change","created_at":"2026-08-27T21:32:26.611249333Z","actor":"Shinuwa","issue_id":"sokko-g-2u9","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed"}}
{"id":"int-ea50d03c7c84b32ab2fce86f15bbe05f","kind":"field_change","created_at":"2026-08-27T21:43:00.775108889Z","actor":"Shinuwa","issue_id":"sokko-g-bh4","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Completed"}}
{"id":"int-4f7a574335bb2d616e9caae09e22e55b","kind":"field_change","created_at":"2026-08-27T21:45:18.547229481Z","actor":"Shinuwa","issue_id":"sokko-g-7jm","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed"}}
{"id":"int-ae5fb0bbe41a7958929560a1bbf65052","kind":"field_change","created_at":"2026-08-27T21:48:45.268794089Z","actor":"Shinuwa","issue_id":"sokko-g-z2t","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed"}}
{"id":"int-8e3b38de72d948a6c241b0d8b2f8077b","kind":"field_change","created_at":"2026-08-27T21:54:30.02159376Z","actor":"Shinuwa","issue_id":"sokko-g-coh","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Completed"}}
{"id":"int-0d86b94b9a04df5b68e2362e0824a37b","kind":"field_change","created_at":"2026-08-27T21:57:24.401323676Z","actor":"Shinuwa","issue_id":"sokko-g-hif","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed"}}
{"id":"int-3fcb6bc2b605db6210c0da18da07e902","kind":"field_change","created_at":"2026-08-27T22:01:35.180268661Z","actor":"Shinuwa","issue_id":"sokko-g-6fs","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed NDS textarea min heights by size"}}
{"id":"int-611cb19bdd31f0e79cc8c2f2bb7a3dd7","kind":"field_change","created_at":"2026-08-28T07:13:54.812999765Z","actor":"Shinuwa","issue_id":"sokko-g-tvo","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed NDS field hover cascade and replaced public date trigger with select trigger"}}
{"id":"int-0e1196655dabd3096c10beef1ba8aff2","kind":"field_change","created_at":"2026-08-28T07:19:28.131918475Z","actor":"Shinuwa","issue_id":"sokko-g-z4x","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed select trigger hover chevron and focus-visible outline"}}
{"id":"int-9c27343c043121862608b8ed9013c306","kind":"field_change","created_at":"2026-08-28T07:25:53.259127006Z","actor":"Shinuwa","issue_id":"sokko-g-eln","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed select trigger md hover/focus and disabled checkbox styling"}}
{"id":"int-da5413e82c18d19a8432288bec703dd7","kind":"field_change","created_at":"2026-08-28T07:50:07.033524644Z","actor":"Shinuwa","issue_id":"sokko-g-h7z","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Completed global NDS control migration and validation"}}
{"id":"int-42f39cb889dfab3bc998d4ce566d2339","kind":"field_change","created_at":"2026-08-28T08:09:50.738078383Z","actor":"Shinuwa","issue_id":"sokko-g-9p1","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed filter radio group, sidebar storage buttons, memo controls, and textarea ghost variant"}}
{"id":"int-03ed1c823da90551f51a5a0d5691e440","kind":"field_change","created_at":"2026-08-28T12:35:29.257332022Z","actor":"Shinuwa","issue_id":"sokko-g-39h","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Applied NDS event-surface to calendar week and month events, documented data-color/data-size, and validated checks."}}

1
.gitignore vendored
View file

@ -4,6 +4,7 @@ playwright-report/
test-results/ test-results/
.env .env
.DS_Store .DS_Store
.local/
# Beads / Dolt files (added by bd init) # Beads / Dolt files (added by bd init)
.dolt/ .dolt/

View file

@ -1,6 +1,6 @@
# Design System - Sokko G # Nebula Design System - Sokko G
Ce document est la reference visuelle du projet Sokko G. Toute nouvelle interface doit rester coherente avec le theme **Nebula Command** actuel. Ce document est la reference visuelle du projet Sokko G. Toute nouvelle interface doit rester coherente avec le theme **Nebula Command** actuel et les controles du **Nebula Design System**.
## 1. Vision ## 1. Vision
@ -63,7 +63,36 @@ Animations :
- `--duration-normal: 180ms` - `--duration-normal: 180ms`
- `--ease-standard: cubic-bezier(0.2, 0.8, 0.2, 1)` - `--ease-standard: cubic-bezier(0.2, 0.8, 0.2, 1)`
## 3. Fond Nebula ## 3. API Des Contrôles NDS
Le Nebula Design System expose ses contrôles publics avec une classe de famille et des attributs `data-*`.
Règles :
- la classe décrit uniquement la famille : `nds-button`, `nds-icon-button`, `nds-input`, `nds-select`, `nds-select-trigger`, `nds-time`, `nds-textarea`, `nds-inline-input`, `nds-switch`, `nds-radio-button-group`, `nds-checkbox`, `nds-quantity-stepper`, `nds-list-row`, `nds-large-icon-picker-trigger`, `nds-event-surface`, `nds-scrollbox` ;
- les variations passent par attributs : `data-size`, `data-variant`, `data-edge`, `data-border`, `data-axis` ;
- la page `/design-system` affiche aussi `data-state` comme axe de comparaison visuelle des états ;
- `data-size` accepte seulement les tailles prévues par la famille (`xs`, `sm`, `md`, `lg`, `xxs`, `micro` selon le contrôle) ; les checkboxes utilisent `md` 20 px, `sm` 18 px et `xs` 14 px ;
- `data-variant` couvre les intentions visuelles partagées : `primary`, `secondary`, `ghost`, `dashed`, `danger` et les variantes spécialisées documentées dans `/design-system`, dont `panel-toggle` pour les boutons icones accolés à un panneau et `icon-picker` pour les triggers de sélection compacts à icône ;
- `data-edge="left|middle|right|top|center|bottom|top-left|top-right|bottom-right|bottom-left"` décrit la position d'un bouton dans un groupe ou contre un bord, et ajuste uniquement les coins ;
- `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 états fonctionnels restent portés par le HTML et l'application : `disabled`, `aria-pressed="true"`, `.active`, `.is-active`.
Exemples :
```html
<button class="nds-button" data-size="xs" data-variant="primary" data-edge="left" data-border="joined">Ajouter</button>
<button class="nds-icon-button" data-size="sm" data-variant="danger" aria-label="Supprimer"></button>
<button class="nds-icon-button" data-variant="panel-toggle" data-edge="right" data-border="joined" aria-pressed="true" aria-label="Pages"></button>
<select class="nds-select" data-size="sm"></select>
<button class="nds-select-trigger" data-size="sm" aria-pressed="false"></button>
```
Les anciennes classes de variation `nds-*--*` ne doivent plus etre ajoutees. Les anciennes classes globales `.button`, `.primary`, `.danger`, `.ghost` et `.import-button` ne sont plus des APIs visuelles publiques : utiliser `nds-button` / `nds-icon-button` avec `data-variant`. Dans les outils, les classes métier peuvent rester pour le layout, la grille, les largeurs, le positionnement et les états fonctionnels. Le style visuel d'un contrôle générique doit venir de `website/src/styles/design-system/_controls.scss` ou de l'API publique `website/src/styles/design-system/_attributes.scss`.
## 4. Fond Nebula
Le fond global est defini dans `website/src/styles/_nebula.scss`. Le fond global est defini dans `website/src/styles/_nebula.scss`.
@ -76,7 +105,7 @@ Regles :
Ne pas ajouter d'orbes decoratifs supplementaires. Si une section a besoin de profondeur, utiliser les fonds semi-transparents et les bordures existantes. Ne pas ajouter d'orbes decoratifs supplementaires. Si une section a besoin de profondeur, utiliser les fonds semi-transparents et les bordures existantes.
## 4. Typographie ## 5. Typographie
Police principale : Police principale :
@ -93,7 +122,7 @@ Regles :
- paragraphs limites visuellement, pas de longs blocs sans respiration ; - paragraphs limites visuellement, pas de longs blocs sans respiration ;
- les `<i>` doivent etre en italique, pas utilises comme gris decoratif. - les `<i>` doivent etre en italique, pas utilises comme gris decoratif.
## 5. Heroes ## 6. Heroes
Les heroes de la page d'accueil et des pages jeux utilisent le meme style via `.hero` et `.game-hero`. Les heroes de la page d'accueil et des pages jeux utilisent le meme style via `.hero` et `.game-hero`.
@ -108,7 +137,7 @@ Caracteristiques :
Les pages jeux doivent heriter du hero commun. Si un jeu a une image, elle doit etre utilisee dans les cards ou les covers, pas comme remplacement complet du hero commun sauf decision explicite. Les pages jeux doivent heriter du hero commun. Si un jeu a une image, elle doit etre utilisee dans les cards ou les covers, pas comme remplacement complet du hero commun sauf decision explicite.
## 6. Cards ## 7. Cards
Les cards representent les elements repetes : toolboxes, jeux, categories, monstres, faune. Les cards representent les elements repetes : toolboxes, jeux, categories, monstres, faune.
@ -124,7 +153,7 @@ La bordure haute/basse interne d'une cover doit rester doree quand elle separe u
Eviter les cards dans des cards. Les sections de page doivent rester des layouts ou panneaux, pas des empilements de cartes decoratives. Eviter les cards dans des cards. Les sections de page doivent rester des layouts ou panneaux, pas des empilements de cartes decoratives.
## 7. Modules Toolbox ## 8. Modules Toolbox
Les modules sont des panneaux de travail, pas des cards classiques. Les modules sont des panneaux de travail, pas des cards classiques.
@ -389,7 +418,7 @@ Regles :
### Selects Des Outils ### Selects Des Outils
Tous les `<select>` rendus dans le contenu d'un outil doivent utiliser le style factorise de `.module-content select`, defini dans `website/src/styles/toolboxes/_shared-controls.scss`. Tous les `<select>` rendus dans le contenu d'un outil doivent utiliser le style factorise par `website/src/styles/design-system/_controls.scss`.
Regles : Regles :
@ -571,37 +600,180 @@ Notifications internes :
- duree d'affichage cible : 2 minutes, fermeture manuelle possible ; - duree d'affichage cible : 2 minutes, fermeture manuelle possible ;
- style plus visible que les confirmations simples : bordure conique animee, pulse leger, arrivee depuis le cote configure. - style plus visible que les confirmations simples : bordure conique animee, pulse leger, arrivee depuis le cote configure.
## 17. Scrollbars ## 17. Controles Interactifs
Les scrollbars reprennent l'ancien rendu : Les champs, selects, boutons icones, switchs et choix compacts utilisent une base commune issue de `website/src/styles/design-system/_controls.scss`. Les fichiers d'outils peuvent choisir une taille ou poser une contrainte de layout, mais ne doivent pas redefinir localement les fonds, bordures, hovers ou etats `primary`/`danger`.
```css Inputs et textareas :
::-webkit-scrollbar {
width: 6px;
height: 6px;
background-color: transparent;
}
::-webkit-scrollbar-track { - utiliser `field-control` pour les champs standards ;
background-color: transparent; - la base globale cible uniquement les types texte explicites : `text`, `search`, `email`, `url`, `password` et `textarea` ;
box-shadow: none; - ne pas utiliser de selecteur global negatif comme `input:not([type="checkbox"])`, afin de ne pas capter les champs `number`, `time`, `file` ou les cellules specialisees ;
} - tout nouvel `<input>` texte doit declarer son `type` explicitement ;
- utiliser `time-control` pour les champs `input[type="time"]`, avec variante `sm` ou `xs` selon le contexte ;
- taille normale : `44px` minimum ;
- taille compacte `sm` : `36px` minimum ;
- taille dense `xs` : `32px` minimum ;
- utiliser `field-textarea` pour les textareas redimensionnables avec scrollbar stable : `md` `108px`, `sm` `88px`, `xs` `72px` minimum ;
- utiliser `data-variant="ghost"` sur `nds-textarea` pour les zones d'edition intégrées à une surface métier, comme les mémos ;
- placeholders via `field-placeholder` ;
- hover via `field-hover`, focus via `field-focus` ;
- disabled via `field-disabled` ;
- les variantes complexes comme les cellules de table et le champ timer `hh:mm:ss` utilisent des mixins dedies du meme fichier.
::-webkit-scrollbar-thumb { Edition inline :
background-image: -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0.5, rgb(79, 26, 114)),
color-stop(1, rgb(44, 1, 135))
);
border-radius: 10px;
}
```
Ne pas ajouter de rail noir visible ni de double bordure pres des panneaux. - utiliser `inline-field` pour les inputs transparents au repos ;
- hover/focus via `inline-field-hover` ;
- utiliser `inline-textarea` pour les zones texte transparentes intégrées à une carte ;
- les outils peuvent changer la couleur texte selon le contexte, par exemple titre de section ou état terminé.
## 18. Images Et Assets Selects :
- taille normale : `44px` minimum ;
- taille compacte toolbox : `36px` minimum ;
- taille dense `xs` : `32px` minimum ;
- fleche custom unique, jamais de chevrons natifs ou empiles ;
- fond sombre Nebula force via `background-color`, hover/focus violet avec chevron dore ;
- focus visible global dore conserve.
Select triggers :
- utiliser `select-trigger` pour les boutons qui ouvrent une liste, un picker ou un calendrier avec le langage visuel d'un select ;
- tailles disponibles : `md` `44px`, `sm` `36px`, `xs` `32px` ;
- le chevron est le meme asset CSS que `select-control`, pas une icone locale ;
- hover via `select-trigger-hover`, focus via `field-focus` et focus visible dore ;
- `date-picker-trigger` reste un alias de compatibilite pour les outils deja migrés.
Menus et palettes :
- utiliser `floating-control-menu` pour les menus flottants compacts ;
- utiliser `compact-menu-button` pour les options d'icones ou d'actions compactes ;
- utiliser `color-swatch`, `color-swatch-rainbow` et `color-swatch-active` pour les palettes couleur ;
- utiliser `large-icon-picker-trigger` pour les gros triggers de choix d'icone de toolbox ;
- les outils peuvent garder uniquement la position du menu, le nombre de colonnes et la variable de couleur métier.
Champs horaires :
- les champs `input[type="time"]` restent natifs ;
- utiliser `time-control` pour aligner le champ avec le theme Nebula ;
- la popup native du navigateur/OS n'est pas consideree comme stylable de facon fiable en CSS.
Boutons texte :
- utiliser `text-button` depuis `website/src/styles/design-system/_controls.scss` ;
- taille normale : hauteur minimale `42px` ;
- taille compacte `sm` : hauteur minimale `34px` ;
- taille dense `xs` : hauteur minimale `30px` ;
- le bouton standard sert de variant secondaire par defaut ;
- variants disponibles : `primary`, `secondary`, `ghost`, `dashed`, `danger` ;
- les boutons texte peuvent utiliser `data-edge` pour ajuster leurs coins et `data-border` pour gérer les bordures jointes ou masquées ;
- etat toggler via `.active` ou `.is-active`, avec le mixin `temporary-toggle-active` ;
- etat desactive via `control-disabled` ;
- les outils peuvent uniquement ajuster la largeur, l'alignement ou l'espacement environnant.
Boutons icones :
- taille normale : `40px`, icone `21px` ;
- taille compacte : `34px`, icone `19px` ;
- taille dense `xs` : `30px`, icone `17px` ;
- taille inline `xxs` : `26px`, icone `15px` ;
- tailles micro reservees aux noeuds denses : `24px`/`13px` et `22px`/`12px` ;
- fond compact sombre, rayon `--radius-md`, hover violet contenu ;
- variant `primary` dore/violet pour les actions principales ;
- variant `ghost` transparent pour les handles discrets ;
- variant `danger` rouge contenu pour les suppressions ;
- variant de forme `button-edge(left|middle|right|top|center|bottom|top-left|top-right|bottom-right|bottom-left)` pour ajuster uniquement les coins contre une bordure ou dans un groupe ;
- variant de bordure `button-joined-border(...)` pour masquer les bordures de jonction déduites depuis `data-edge` ;
- variant de bordure `button-borderless` pour masquer toutes les bordures ;
- variant visuel `panel-toggle` pour les boutons icones accolés à un panneau, comme le bouton pages ;
- etat toggler via `.active` ou `.is-active`, avec le mixin `temporary-toggle-active`.
- les barres d'outils compactes reutilisent la famille bouton icone ; `toolbar-button` reste un alias pratique pour les boutons courts du bloc-notes (`B`, `I`, `U`) et les boutons de dessin.
Boutons composes et toggles de bord :
- utiliser `button-edge` + `button-joined-border` pour les boutons groupés type ajout + chevron ;
- utiliser `button-edge` pour la forme des toggles accoles au bord d'un module, et `icon-button-panel-toggle` pour leur rendu visuel quand ils reprennent le pattern du bouton `pages` ;
- utiliser `list-row-button` pour les lignes selectionnables type pages du bloc-notes ;
- utiliser `text-button-dashed` pour les actions d'ajout contextuelles encadrees en pointilles ;
- `split-button-start`, `split-button-end`, `edge-toggle` et `dashed-action-button` restent des alias de compatibilite internes, pas des familles visuelles separees ;
- les outils peuvent garder uniquement la position absolue, la largeur contextuelle et les icones ;
- l'etat actif/hover doit rester dans le DS, sans fond/bordure locale.
Steppers de quantite :
- utiliser `quantity-stepper` depuis `website/src/styles/design-system/_controls.scss` ;
- variante normale : checklist, boutons `28px` autour de la valeur editable ;
- variante compacte : arbre d'objectifs, boutons `18px` ou `16px` selon la densite du noeud ;
- hover des boutons via `quantity-stepper-button-hover`, pas via `icon-button-hover` ;
- les outils peuvent adapter la grille environnante, mais pas redefinir les boutons `- / +`, le hover ou le champ de valeur.
Switchs binaires :
- utiliser `switch-control` depuis `website/src/styles/design-system/_controls.scss` ;
- taille `lg` : switchs de module, hauteur `40px`, rail `30 x 16px` ;
- taille normale : tool-split, hauteur `34px`, rail `34 x 18px` ;
- taille compacte `sm` : contrôles denses, hauteur `28px`, rail `24 x 14px` ;
- taille inline `xs` : timer inline, hauteur `26px`, rail `18 x 12px` ;
- les variantes natives avec `<label><input type="checkbox" />...` doivent réutiliser `native-switch-label`, `native-switch-input`, `switch-track` et `switch-thumb` ;
- les outils peuvent uniquement ajuster l'alignement ou la largeur environnante.
Radio button groups :
- la primitive commune est `radio-button-group` / `radio-button-group-option` depuis `website/src/styles/design-system/_controls.scss` ;
- tailles disponibles : `md`, `sm`, `xs` ;
- cette base couvre les tabs d'outils, switches de layout, choix d'alertes et répétitions calendrier ;
- le nombre d'options, le fait d'utiliser une icone, un texte ou les deux ne constitue pas un variant visuel ;
- les toggles compacts non natifs peuvent reutiliser ces mixins meme avec des `span`, tant que le comportement reste celui d'un choix exclusif ou d'un choix compact ;
- l'etat actif passe par `.active` ou `.is-active` avec le style toggler temporaire ;
- les outils peuvent definir la grille ou la largeur, mais pas redefinir localement les fonds, bordures, hovers ou actifs.
Controles composes :
- utiliser `compound-toggle-field` pour les cas qui combinent un choix compact et un champ, comme `Hold + number` ;
- le bouton interne utilise `compound-toggle-field-option`, base sur `radio-button-group-option(xs)` ;
- le champ interne utilise `compound-toggle-field-input`, base sur `field-control(xs)` et `number-spinner-reset` ;
- ces controles ne sont pas des variants du groupe de choix, car leur structure mélange action et saisie.
Checkboxes :
- utiliser `checkbox-control` depuis `website/src/styles/design-system/_controls.scss` ;
- taille normale globale : `20px`, format carre strict `1:1` ;
- taille compacte `xs` : `14px` pour les listes denses comme les prérequis de l'arbre d'objectifs ;
- etat coche avec gradient dore/violet et coche blanche ;
- les outils peuvent ajuster uniquement l'alignement ou l'espacement environnant.
Date picker et menus compacts :
- les tabs internes d'outils utilisent les primitives `radio-button-group-surface`, `radio-button-group-option`, `radio-button-group-option-hover` et `radio-button-group-option-active` ;
- les boutons du mini calendrier mensuel utilisent `date-picker-button` et `date-picker-button-hover` ;
- les boutons de menu/picker compacts utilisent `compact-menu-button` et `compact-menu-button-hover` ;
- les boutons transparents integres aux evenements calendrier utilisent `event-surface-button`, `event-surface-button-hover` et les contrastes `event-surface-contrast-default` / `event-surface-contrast-dark` ;
- `nds-event-surface[data-variant="default"]` cible un texte blanc/gris blanc sur fond sombre ;
- `nds-event-surface[data-variant="dark"]` cible un texte noir/gris noir sur fond clair ;
- `nds-event-surface[data-size="md"]` vise les surfaces à partir de `44px` et affiche le titre et la metadata secondaire, par exemple l'heure ;
- `nds-event-surface[data-size="sm"]` vise les surfaces autour de `22px` et masque la metadata secondaire ;
- `nds-event-surface[data-size="xs"]` vise les surfaces ultra-fines autour de `10px` et affiche uniquement le titre en taille réduite ;
- `nds-event-surface[data-color="#8b5cf6"]` expose la couleur métier de l'event ; la variable inline `--nds-event-color` applique cette couleur au fond et a la bordure ;
- le hover utilise une superposition sombre legere pour rester lisible sur une couleur dynamique inconnue du Sass ;
- les couleurs de fond des events restent métier et ne sont pas des variants NDS ;
- les fichiers d'outils peuvent conserver les dimensions de grille, le placement du menu et les états métier `disabled`.
## 18. Scrollbars
Les scrollbars utilisent `scrollbar-control` depuis `website/src/styles/design-system/_controls.scss`.
Regles :
- largeur/hauteur WebKit : `6px` ;
- `scrollbar-width: thin` pour Firefox ;
- track transparent ;
- thumb violet/indigo avec hover dore contenu ;
- coin transparent pour les zones scrollables dans les deux axes ;
- les textareas redimensionnables ajoutent `scrollbar-resizer`, sans fond noir ;
- ne pas ajouter de rail noir visible ni de double bordure pres des panneaux.
## 19. Images Et Assets
Regles : Regles :
@ -617,7 +789,7 @@ Regles :
- les categories de jeu utilisent des images representatives, entieres et bien cadrees ; - les categories de jeu utilisent des images representatives, entieres et bien cadrees ;
- eviter les images trop grandes dans les cards. - eviter les images trop grandes dans les cards.
## 19. Accessibilite ## 20. Accessibilite
Minimum attendu : Minimum attendu :
@ -629,7 +801,7 @@ Minimum attendu :
- modales avec `role="dialog"` et `aria-modal="true"` ; - modales avec `role="dialog"` et `aria-modal="true"` ;
- ne pas transmettre une information uniquement par couleur. - ne pas transmettre une information uniquement par couleur.
## 20. A Eviter ## 21. A Eviter
- accents dores partout ; - accents dores partout ;
- hover trop lumineux sur tous les elements ; - hover trop lumineux sur tous les elements ;

View file

@ -138,9 +138,9 @@ test("style entrypoint and theme layers are wired", async () => {
assert.match(styles, /@use "diablo4"/); assert.match(styles, /@use "diablo4"/);
assert.match(styleTokens, /--gradient-brand/); assert.match(styleTokens, /--gradient-brand/);
assert.match(styleBase, /:is\(/); assert.match(styleBase, /:is\(/);
assert.match(styleBase, /\.card-icon-button/); assert.doesNotMatch(styleBase, /\.card-icon-button/);
assert.match(styleBase, /\.drawer-action-button/); assert.doesNotMatch(styleBase, /\.drawer-action-button/);
assert.match(styleBase, /\.import-icon-button/); assert.doesNotMatch(styleBase, /\.import-icon-button/);
assert.match(styleBase, /themed-scrollbar/); assert.match(styleBase, /themed-scrollbar/);
assert.match(styleBase, /body\.is-modal-open/); assert.match(styleBase, /body\.is-modal-open/);
assert.match(styleNebula, /@function star-field/); assert.match(styleNebula, /@function star-field/);
@ -172,7 +172,7 @@ test("style entrypoint and theme layers are wired", async () => {
assert.match(styleToolboxCalendar, /\.calendar-event-form/); assert.match(styleToolboxCalendar, /\.calendar-event-form/);
assert.match(styleToolboxCalendar, /\.calendar-month-date-picker/); assert.match(styleToolboxCalendar, /\.calendar-month-date-picker/);
assert.match(styleToolboxCalendar, /\.calendar-alert-mode-switch/); assert.match(styleToolboxCalendar, /\.calendar-alert-mode-switch/);
assert.match(styleToolboxSharedControls, /\.notepad-toolbar-button\.danger/); assert.match(styles, /@use "design-system\/attributes"/);
assert.match(styleToolboxLayout, /\.toolbox-icon-picker/); assert.match(styleToolboxLayout, /\.toolbox-icon-picker/);
assert.match(styleToolboxLayout, /\.toolbox-game-icon/); assert.match(styleToolboxLayout, /\.toolbox-game-icon/);
assert.match(styleToolboxLayout, /\.toolbox-hero-link/); assert.match(styleToolboxLayout, /\.toolbox-hero-link/);

View file

@ -437,7 +437,8 @@ test("toolbox module registry and modules expose expected behavior", async () =>
assert.match(calendarBundle, /calendar-repeat-switch/); assert.match(calendarBundle, /calendar-repeat-switch/);
assert.match(calendarBundle, /layout-switch calendar-month-repeat-switch/); assert.match(calendarBundle, /layout-switch calendar-month-repeat-switch/);
assert.match(calendarBundle, /WEEK_TIME_INPUT_STEP_MINUTES \* 60/); assert.match(calendarBundle, /WEEK_TIME_INPUT_STEP_MINUTES \* 60/);
assert.match(calendarBundle, /durationMinutes < 120/); assert.match(calendarBundle, /naturalHeight/);
assert.match(calendarBundle, /data-size=\{eventSurfaceSize\}/);
assert.match(calendarBundle, /is-compact/); assert.match(calendarBundle, /is-compact/);
assert.match(calendarController, /handleKeyDown/); assert.match(calendarController, /handleKeyDown/);
assert.match(calendarContent, /TextExchangeActions/); assert.match(calendarContent, /TextExchangeActions/);
@ -534,7 +535,7 @@ test("toolbox module registry and modules expose expected behavior", async () =>
assert.match(checklistParts, /chevron-down/); assert.match(checklistParts, /chevron-down/);
assert.match(checklistParts, /chevron-up/); assert.match(checklistParts, /chevron-up/);
assert.match(checklistParts, /checklist-qty-current/); assert.match(checklistParts, /checklist-qty-current/);
assert.match(checklistParts, /checklist-delete-button danger/); assert.match(checklistParts, /checklist-delete-button nds-icon-button/);
assert.match(checklistModule, /useGroupedReorder/); assert.match(checklistModule, /useGroupedReorder/);
assert.match(checklistBundle, /getItemProps/); assert.match(checklistBundle, /getItemProps/);
assert.match(checklistParts, /getGroupProps/); assert.match(checklistParts, /getGroupProps/);

View file

@ -89,7 +89,7 @@ export function GuidedTooltipTour({ steps, labels = {}, targetAttribute = "data-
return createPortal( return createPortal(
<section ref={tooltipRef} className="toolbox-tutorial-tooltip" aria-live="polite" aria-labelledby="toolbox-tutorial-title" data-placement={placement} role="dialog" style={tooltipStyle}> <section ref={tooltipRef} className="toolbox-tutorial-tooltip" aria-live="polite" aria-labelledby="toolbox-tutorial-title" data-placement={placement} role="dialog" style={tooltipStyle}>
<button className="toolbox-tutorial-close" type="button" onClick={onDismiss} aria-label={labels.dismiss || "Fermer le tutoriel"} title={labels.dismiss || "Fermer"}> <button className="toolbox-tutorial-close nds-icon-button" data-size="xs" type="button" onClick={onDismiss} aria-label={labels.dismiss || "Fermer le tutoriel"} title={labels.dismiss || "Fermer"}>
<Icon name="close" /> <Icon name="close" />
</button> </button>
<span className="toolbox-tutorial-count">{stepIndex + 1}/{steps.length}</span> <span className="toolbox-tutorial-count">{stepIndex + 1}/{steps.length}</span>
@ -99,7 +99,7 @@ export function GuidedTooltipTour({ steps, labels = {}, targetAttribute = "data-
</div> </div>
<p>{currentStep.text}</p> <p>{currentStep.text}</p>
<footer> <footer>
<button className="primary" type="button" onClick={isLastStep ? onDismiss : () => setStepIndex((index) => Math.min(index + 1, steps.length - 1))}> <button className="nds-button" data-size="sm" data-variant="primary" type="button" onClick={isLastStep ? onDismiss : () => setStepIndex((index) => Math.min(index + 1, steps.length - 1))}>
{isLastStep ? labels.done || "Terminer" : labels.next || "Suivant"} {isLastStep ? labels.done || "Terminer" : labels.next || "Suivant"}
</button> </button>
</footer> </footer>

View file

@ -142,9 +142,9 @@ export function ImageViewer({ image, onClose, createMarkerId = () => globalThis.
{image.label && <strong className="image-viewer-title">{image.label}</strong>} {image.label && <strong className="image-viewer-title">{image.label}</strong>}
<div className="image-viewer-actions"> <div className="image-viewer-actions">
{!canAnnotate && !hasMarkers && ( {!canAnnotate && !hasMarkers && (
<button className="image-viewer-button" onClick={() => openImageInNewTab(image.dataUrl)} aria-label="Ouvrir l'image en taille réelle" title="Taille réelle"><Icon name="zoom" /></button> <button className="image-viewer-button nds-icon-button" data-size="sm" onClick={() => openImageInNewTab(image.dataUrl)} aria-label="Ouvrir l'image en taille réelle" title="Taille réelle"><Icon name="zoom" /></button>
)} )}
<button className="image-viewer-button" onClick={onClose} aria-label="Fermer" title="Fermer"><Icon name="close" /></button> <button className="image-viewer-button nds-icon-button" data-size="sm" onClick={onClose} aria-label="Fermer" title="Fermer"><Icon name="close" /></button>
</div> </div>
</header> </header>
<div className={`image-viewer-body ${canAnnotate ? "has-annotation-side" : ""}`}> <div className={`image-viewer-body ${canAnnotate ? "has-annotation-side" : ""}`}>
@ -211,12 +211,15 @@ export function ImageViewer({ image, onClose, createMarkerId = () => globalThis.
<li key={marker.id}> <li key={marker.id}>
<span>{index + 1}</span> <span>{index + 1}</span>
<input <input
className="nds-input"
data-size="sm"
type="text"
value={marker.label} value={marker.label}
placeholder={image.markerPlaceholder || "Libellé du marqueur"} placeholder={image.markerPlaceholder || "Libellé du marqueur"}
onChange={(event) => updateViewerMarker(marker.id, (item) => ({ ...item, label: event.target.value }))} onChange={(event) => updateViewerMarker(marker.id, (item) => ({ ...item, label: event.target.value }))}
aria-label={`Libellé du marqueur ${index + 1}`} aria-label={`Libellé du marqueur ${index + 1}`}
/> />
<button className="tool-split-action-button danger" type="button" onClick={() => removeViewerMarker(marker.id)} aria-label={`${image.deleteMarkerTitle || "Supprimer"} ${marker.label || `${image.markerPrefix || "Marqueur"} ${index + 1}`}`} title={image.deleteMarkerTitle || "Supprimer"}> <button className="tool-split-action-button nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={() => removeViewerMarker(marker.id)} aria-label={`${image.deleteMarkerTitle || "Supprimer"} ${marker.label || `${image.markerPrefix || "Marqueur"} ${index + 1}`}`} title={image.deleteMarkerTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</li> </li>

View file

@ -1,9 +1,9 @@
// Rôle : encapsule l'input fichier JSON derrière un bouton d'import réutilisable. // Rôle : encapsule l'input fichier JSON derrière un bouton d'import réutilisable.
import { Icon } from "./Icon.jsx"; import { Icon } from "./Icon.jsx";
export function ImportButton({ className, label, title, ariaLabel, icon, onFile }) { export function ImportButton({ className, label, title, ariaLabel, icon, onFile, ...props }) {
return ( return (
<label className={className} aria-label={ariaLabel} title={title}> <label className={className} aria-label={ariaLabel} title={title} {...props}>
{icon && <Icon name={icon} />} {icon && <Icon name={icon} />}
{label} {label}
<input type="file" accept="application/json" hidden onChange={async (event) => { <input type="file" accept="application/json" hidden onChange={async (event) => {

View file

@ -6,7 +6,7 @@ export function Shell({ route, content, games, toolboxes, links, actions, childr
const gameId = route.split("/")[1] === "games" ? route.split("/")[2] || "" : ""; const gameId = route.split("/")[1] === "games" ? route.split("/")[2] || "" : "";
const game = games.find((item) => item.id === gameId); const game = games.find((item) => item.id === gameId);
const linkedToolbox = game ? toolboxes.find((item) => item.id === links[game.id]) : null; const linkedToolbox = game ? toolboxes.find((item) => item.id === links[game.id]) : null;
const topbarLabel = route.startsWith("/toolbox") ? content.topbar.toolbox : route.startsWith("/games") ? content.topbar.games : route === "/library" ? content.topbar.library : content.topbar.dashboard; const topbarLabel = route === "/design-system" && import.meta.env.DEV ? "Design system" : route.startsWith("/toolbox") ? content.topbar.toolbox : route.startsWith("/games") ? content.topbar.games : route === "/library" ? content.topbar.library : content.topbar.dashboard;
const toolboxLabel = linkedToolbox ? `Ouvrir la toolbox ${linkedToolbox.name}` : "Associer une toolbox"; const toolboxLabel = linkedToolbox ? `Ouvrir la toolbox ${linkedToolbox.name}` : "Associer une toolbox";
return ( return (
@ -27,13 +27,16 @@ export function Shell({ route, content, games, toolboxes, links, actions, childr
<div className="sidebar-secondary-links"> <div className="sidebar-secondary-links">
<a className={`sidebar-about-link ${route === "/about" ? "active" : ""}`} href="#/about"><span className="sidebar-about-icon" aria-hidden="true">?</span><strong>{content.navigation.about}</strong></a> <a className={`sidebar-about-link ${route === "/about" ? "active" : ""}`} href="#/about"><span className="sidebar-about-icon" aria-hidden="true">?</span><strong>{content.navigation.about}</strong></a>
<a className={`sidebar-about-link ${route === "/library" ? "active" : ""}`} href="#/library"><span className="sidebar-about-icon" aria-hidden="true"><Icon name="book" /></span><strong>{content.library.eyebrow}</strong></a> <a className={`sidebar-about-link ${route === "/library" ? "active" : ""}`} href="#/library"><span className="sidebar-about-icon" aria-hidden="true"><Icon name="book" /></span><strong>{content.library.eyebrow}</strong></a>
{import.meta.env.DEV && (
<a className={`sidebar-about-link ${route === "/design-system" ? "active" : ""}`} href="#/design-system"><span className="sidebar-about-icon" aria-hidden="true"><Icon name="settings" /></span><strong>Design system</strong></a>
)}
</div> </div>
<div className="sidebar-note"> <div className="sidebar-note">
<span className="badge">{content.sidebar.badge}</span> <span className="badge">{content.sidebar.badge}</span>
<p>{content.sidebar.note}</p> <p>{content.sidebar.note}</p>
<div className="sidebar-storage-actions" aria-label="Actions globales de stockage"> <div className="sidebar-storage-actions" aria-label="Actions globales de stockage">
<button className="sidebar-action-button primary" onClick={actions.exportAllToolboxes} aria-label="Exporter toutes les toolboxes" data-toolbox-tutorial-target="exportAll" title="Exporter tout"><Icon name="export" /></button> <button className="sidebar-action-button nds-icon-button" data-size="sm" data-variant="primary" onClick={actions.exportAllToolboxes} aria-label="Exporter toutes les toolboxes" data-toolbox-tutorial-target="exportAll" title="Exporter tout"><Icon name="export" /></button>
<ImportButton className="sidebar-action-button import-icon-button" title="Importer tout" ariaLabel="Importer toutes les toolboxes" onFile={actions.importAllToolboxes} icon="import" /> <ImportButton className="sidebar-action-button nds-icon-button import-icon-button" data-size="sm" title="Importer tout" ariaLabel="Importer toutes les toolboxes" onFile={actions.importAllToolboxes} icon="import" />
</div> </div>
</div> </div>
</aside> </aside>
@ -42,7 +45,7 @@ export function Shell({ route, content, games, toolboxes, links, actions, childr
<div><strong>{topbarLabel}</strong></div> <div><strong>{topbarLabel}</strong></div>
{game && ( {game && (
<div className="topbar-actions"> <div className="topbar-actions">
<button className="drawer-button toolbox-icon-button primary" onClick={() => actions.setDrawerGameId(game.id)} aria-label={toolboxLabel} title={toolboxLabel}> <button className="drawer-button toolbox-icon-button nds-icon-button" data-size="sm" data-variant="primary" onClick={() => actions.setDrawerGameId(game.id)} aria-label={toolboxLabel} title={toolboxLabel}>
<img src="/static/icons/toolbox.svg" alt="" aria-hidden="true" /> <img src="/static/icons/toolbox.svg" alt="" aria-hidden="true" />
</button> </button>
</div> </div>
@ -53,7 +56,7 @@ export function Shell({ route, content, games, toolboxes, links, actions, childr
<nav className="mobile-nav" aria-label="Navigation mobile"> <nav className="mobile-nav" aria-label="Navigation mobile">
<a className={route === "/" ? "active" : ""} href="#/">{content.navigation.home}</a> <a className={route === "/" ? "active" : ""} href="#/">{content.navigation.home}</a>
<a className={route.startsWith("/toolboxes") || route.startsWith("/toolbox") ? "active" : ""} href="#/toolboxes">{content.navigation.toolboxes}</a> <a className={route.startsWith("/toolboxes") || route.startsWith("/toolbox") ? "active" : ""} href="#/toolboxes">{content.navigation.toolboxes}</a>
<button className="primary" onClick={() => actions.setCreateModal({})}>+</button> <button className="nds-button" data-size="sm" data-variant="primary" onClick={() => actions.setCreateModal({})}>+</button>
<a className={route === "/library" ? "active" : ""} href="#/library">{content.navigation.mobileLibrary}</a> <a className={route === "/library" ? "active" : ""} href="#/library">{content.navigation.mobileLibrary}</a>
<a className={route.startsWith("/games") ? "active" : ""} href="#/games">{content.navigation.mobileGames}</a> <a className={route.startsWith("/games") ? "active" : ""} href="#/games">{content.navigation.mobileGames}</a>
</nav> </nav>

View file

@ -1,7 +1,7 @@
// Rôle : fournit un contrôle d'onglets réutilisable avec rôles ARIA. // Rôle : fournit un contrôle d'onglets réutilisable avec rôles ARIA.
export function Tabs({ tabs, activeTab, onChange, className = "", iconOnly = false }) { export function Tabs({ tabs, activeTab, onChange, className = "", iconOnly = false, size = "sm" }) {
return ( return (
<div className={`tabs ${className}`.trim()} role="tablist"> <div className={`tabs nds-radio-button-group ${className}`.trim()} data-size={size} role="tablist">
{tabs.map((tab) => { {tabs.map((tab) => {
const active = tab.id === activeTab; const active = tab.id === activeTab;
return ( return (

View file

@ -4,16 +4,34 @@ import { Icon } from "./Icon.jsx";
export function ToastPositionSwitch({ position, labels, onChange }) { export function ToastPositionSwitch({ position, labels, onChange }) {
const normalizedPosition = position === "left" ? "left" : "right"; const normalizedPosition = position === "left" ? "left" : "right";
return ( return (
<button <div
className="toast-position-switch" className="toast-position-switch nds-radio-button-group"
type="button" data-size="xs"
role="radiogroup"
data-position={normalizedPosition} data-position={normalizedPosition}
onClick={() => onChange(normalizedPosition === "left" ? "right" : "left")}
aria-label={normalizedPosition === "left" ? labels.left : labels.right} aria-label={normalizedPosition === "left" ? labels.left : labels.right}
title={labels.title} title={labels.title}
> >
<span><Icon name="toast-left" /></span> <button
<span><Icon name="toast-right" /></span> className={normalizedPosition === "left" ? "active" : ""}
type="button"
role="radio"
aria-checked={normalizedPosition === "left"}
aria-label={labels.left}
onClick={() => onChange("left")}
>
<Icon name="toast-left" />
</button> </button>
<button
className={normalizedPosition === "right" ? "active" : ""}
type="button"
role="radio"
aria-checked={normalizedPosition === "right"}
aria-label={labels.right}
onClick={() => onChange("right")}
>
<Icon name="toast-right" />
</button>
</div>
); );
} }

View file

@ -16,8 +16,8 @@ export function ConfirmModal({ title, message, confirmLabel = "Confirmer", cance
<header><h2 id="confirm-title">{title}</h2></header> <header><h2 id="confirm-title">{title}</h2></header>
<p id="confirm-message">{message}</p> <p id="confirm-message">{message}</p>
<footer> <footer>
<button onClick={() => onClose(false)}>{cancelLabel}</button> <button className="nds-button" data-size="sm" onClick={() => onClose(false)}>{cancelLabel}</button>
<button className={danger ? "danger confirm-danger" : "primary"} onClick={() => onClose(true)}>{confirmLabel}</button> <button className="confirm-danger nds-button" data-size="sm" data-variant={danger ? "danger" : "primary"} onClick={() => onClose(true)}>{confirmLabel}</button>
</footer> </footer>
</section> </section>
</div> </div>
@ -33,7 +33,7 @@ export function NotificationToast({ id, message, durationMs = 120000, onClose })
return ( return (
<div className="notification-toast" role="status"> <div className="notification-toast" role="status">
<span>{message}</span> <span>{message}</span>
<button type="button" onClick={() => onClose(id)} aria-label="Fermer l'alerte" title="Fermer"> <button className="nds-icon-button" data-size="xs" type="button" onClick={() => onClose(id)} aria-label="Fermer l'alerte" title="Fermer">
<Icon name="close" /> <Icon name="close" />
</button> </button>
</div> </div>
@ -52,8 +52,8 @@ export function CreateToolboxModal({ gameId, initialName = "", onClose }) {
event.preventDefault(); event.preventDefault();
if (name.trim()) onClose(name.trim()); if (name.trim()) onClose(name.trim());
}}> }}>
<label className="field"><span>Nom</span><input name="name" autoComplete="off" placeholder="Nom de la toolbox" required value={name} onChange={(event) => setName(event.target.value)} autoFocus /></label> <label className="field"><span>Nom</span><input className="nds-input" data-size="sm" name="name" type="text" autoComplete="off" placeholder="Nom de la toolbox" required value={name} onChange={(event) => setName(event.target.value)} autoFocus /></label>
<footer><button type="button" onClick={() => onClose("")}>Annuler</button><button className="primary" type="submit">Créer</button></footer> <footer><button className="nds-button" data-size="sm" type="button" onClick={() => onClose("")}>Annuler</button><button className="nds-button" data-size="sm" data-variant="primary" type="submit">Créer</button></footer>
</form> </form>
</section> </section>
</div> </div>
@ -72,8 +72,8 @@ export function LinkToolboxModal({ toolboxes, selectedId, onClose }) {
event.preventDefault(); event.preventDefault();
onClose(toolboxId); onClose(toolboxId);
}}> }}>
<label className="field"><span>Toolbox associée</span><select value={toolboxId} onChange={(event) => setToolboxId(event.target.value)} autoFocus><option value="">Aucune</option>{toolboxes.map((toolbox) => <option key={toolbox.id} value={toolbox.id}>{toolbox.name}</option>)}</select></label> <label className="field"><span>Toolbox associée</span><select className="nds-select" data-size="sm" value={toolboxId} onChange={(event) => setToolboxId(event.target.value)} autoFocus><option value="">Aucune</option>{toolboxes.map((toolbox) => <option key={toolbox.id} value={toolbox.id}>{toolbox.name}</option>)}</select></label>
<footer><button type="button" onClick={() => onClose(null)}>Annuler</button><button className="primary" type="submit">Valider</button></footer> <footer><button className="nds-button" data-size="sm" type="button" onClick={() => onClose(null)}>Annuler</button><button className="nds-button" data-size="sm" data-variant="primary" type="submit">Valider</button></footer>
</form> </form>
</section> </section>
</div> </div>

View file

@ -57,7 +57,8 @@ export function CopyChecklistItemsButton({ labels, sections, title = "Copier pou
return ( return (
<button <button
className="filter-reset-button results-copy-button" className="filter-reset-button results-copy-button nds-icon-button"
data-size="xs"
type="button" type="button"
onClick={copyItems} onClick={copyItems}
disabled={!text} disabled={!text}

View file

@ -17,8 +17,6 @@ export function GameFiltersPanel({
onChange onChange
}) { }) {
const selected = new Set(filters[selectedKey] || []); const selected = new Set(filters[selectedKey] || []);
const logicLabel = filters.logic === "or" ? logicLabels.or : logicLabels.and;
function update(updater) { function update(updater) {
onChange(updater(filters)); onChange(updater(filters));
} }
@ -31,7 +29,8 @@ export function GameFiltersPanel({
<h2>{title}</h2> <h2>{title}</h2>
</div> </div>
<button <button
className="filter-reset-button" className="filter-reset-button nds-icon-button"
data-size="xs"
onClick={() => onChange({ name: "", [selectedKey]: [], logic: "and" })} onClick={() => onChange({ name: "", [selectedKey]: [], logic: "and" })}
aria-label={resetLabel} aria-label={resetLabel}
title={resetLabel} title={resetLabel}
@ -41,11 +40,24 @@ export function GameFiltersPanel({
</div> </div>
<label className="field compact"> <label className="field compact">
<span>{nameLabel}</span> <span>{nameLabel}</span>
<input value={filters.name} placeholder="Rechercher..." onChange={(event) => update((state) => ({ ...state, name: event.target.value }))} /> <input className="nds-input" data-size="sm" type="search" value={filters.name} placeholder="Rechercher..." onChange={(event) => update((state) => ({ ...state, name: event.target.value }))} />
</label> </label>
<div className="filter-logic"> <div className="filter-logic">
<span>{matchLabel}</span> <span>{matchLabel}</span>
<button onClick={() => update((state) => ({ ...state, logic: state.logic === "and" ? "or" : "and" }))}>{logicLabel}</button> <div className="filter-logic-group nds-radio-button-group" data-size="xs" role="radiogroup" aria-label={matchLabel}>
{["and", "or"].map((logic) => (
<button
key={logic}
className={filters.logic === logic ? "active" : ""}
type="button"
role="radio"
aria-checked={filters.logic === logic}
onClick={() => update((state) => ({ ...state, logic }))}
>
{logicLabels[logic]}
</button>
))}
</div>
</div> </div>
<div className="filter-options legacy-scrollbar"> <div className="filter-options legacy-scrollbar">
{options.map((option) => { {options.map((option) => {
@ -53,6 +65,8 @@ export function GameFiltersPanel({
return ( return (
<label className={`filter-chip ${getOptionClassName(option)} ${selected.has(optionId) ? "active" : ""}`} key={optionId}> <label className={`filter-chip ${getOptionClassName(option)} ${selected.has(optionId) ? "active" : ""}`} key={optionId}>
<input <input
className="nds-checkbox"
data-size="sm"
type="checkbox" type="checkbox"
checked={selected.has(optionId)} checked={selected.has(optionId)}
onChange={(event) => update((state) => { onChange={(event) => update((state) => {

View file

@ -30,7 +30,7 @@ export function GameListsPage({ game, lists, selectedListId, onSelectList, linke
<h1>Listes</h1> <h1>Listes</h1>
</div> </div>
<p>Sélectionnez une liste, puis copiez-la ou créez une checklist dans la toolbox associée.</p> <p>Sélectionnez une liste, puis copiez-la ou créez une checklist dans la toolbox associée.</p>
<a className="game-list-help-link button" href="#/about#contribuer">Proposer une nouvelle liste</a> <a className="game-list-help-link nds-button" data-size="sm" href="#/about#contribuer">Proposer une nouvelle liste</a>
</div> </div>
</section> </section>
{lists.length ? ( {lists.length ? (
@ -43,7 +43,9 @@ export function GameListsPage({ game, lists, selectedListId, onSelectList, linke
<small>{list.itemCount} éléments</small> <small>{list.itemCount} éléments</small>
</button> </button>
<button <button
className="game-list-create-button primary" className="game-list-create-button nds-icon-button"
data-size="sm"
data-variant="primary"
type="button" type="button"
disabled={!linkedToolboxId} disabled={!linkedToolboxId}
onClick={() => onCreateChecklist?.(list)} onClick={() => onCreateChecklist?.(list)}

View file

@ -32,7 +32,7 @@ export function GamesPage({ siteContent, games, gamesError }) {
<h2>{game.title}</h2> <h2>{game.title}</h2>
<p>{game.summary}</p> <p>{game.summary}</p>
<div className="card-actions"> <div className="card-actions">
<a className="button primary" href={`#/games/${game.id}`}>Ouvrir</a> <a className="nds-button" data-size="sm" data-variant="primary" href={`#/games/${game.id}`}>Ouvrir</a>
</div> </div>
</div> </div>
</article> </article>

View file

@ -30,8 +30,8 @@ export function MhwildsListing({ category, game, mhwilds, filters, setFilters, t
<p>{isMonsters ? "Filtrez les monstres par nom et faiblesses, puis consultez leurs dégâts détaillés." : "Filtrez la faune par nom et zones d'apparition."}</p> <p>{isMonsters ? "Filtrez les monstres par nom et faiblesses, puis consultez leurs dégâts détaillés." : "Filtrez la faune par nom et zones d'apparition."}</p>
</div> </div>
<div className="actions"> <div className="actions">
<a className={`button ${isMonsters ? "primary" : ""}`} href="#/games/mhwilds/monsters">Monstres</a> <a className="nds-button" data-size="sm" data-variant={isMonsters ? "primary" : "secondary"} href="#/games/mhwilds/monsters">Monstres</a>
<a className={`button ${!isMonsters ? "primary" : ""}`} href="#/games/mhwilds/endemic">Faune</a> <a className="nds-button" data-size="sm" data-variant={!isMonsters ? "primary" : "secondary"} href="#/games/mhwilds/endemic">Faune</a>
</div> </div>
</section> </section>
<section className="game-layout" data-mhwilds-category={category}> <section className="game-layout" data-mhwilds-category={category}>

View file

@ -34,7 +34,9 @@ export function ToolboxCard({ toolbox, game, actions, reorder }) {
return ( return (
<article className={className} {...reorder.getItemProps({ itemId: toolbox.id })}> <article className={className} {...reorder.getItemProps({ itemId: toolbox.id })}>
<button <button
className="toolbox-card-drag-handle" className="toolbox-card-drag-handle nds-icon-button"
data-size="md"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, toolbox.id)} onPointerDown={(event) => reorder.itemReorder.startDrag(event, toolbox.id)}
aria-label={`Déplacer ${toolbox.name}`} aria-label={`Déplacer ${toolbox.name}`}
@ -56,9 +58,9 @@ export function ToolboxCard({ toolbox, game, actions, reorder }) {
<h2>{toolbox.name}</h2> <h2>{toolbox.name}</h2>
<small>Modifiée le {formatDate(toolbox.updatedAt)}</small> <small>Modifiée le {formatDate(toolbox.updatedAt)}</small>
<div className="card-actions"> <div className="card-actions">
<a className="button card-icon-button" href={`#/toolbox/${toolbox.id}`} aria-label={`Ouvrir ${toolbox.name}`} title="Ouvrir"><Icon name="open" /></a> <a className="card-icon-button nds-icon-button" data-size="md" href={`#/toolbox/${toolbox.id}`} aria-label={`Ouvrir ${toolbox.name}`} title="Ouvrir"><Icon name="open" /></a>
<button className="card-icon-button" onClick={() => actions.exportToolbox(toolbox.id)} aria-label={`Exporter ${toolbox.name}`} title="Exporter"><Icon name="export" /></button> <button className="card-icon-button nds-icon-button" data-size="md" onClick={() => actions.exportToolbox(toolbox.id)} aria-label={`Exporter ${toolbox.name}`} title="Exporter"><Icon name="export" /></button>
<button className="card-icon-button danger" onClick={() => actions.setConfirmModal({ <button className="card-icon-button nds-icon-button" data-size="md" data-variant="danger" onClick={() => actions.setConfirmModal({
title: "Supprimer la toolbox", title: "Supprimer la toolbox",
message: `Supprimer "${toolbox.name}" et ses données locales ?`, message: `Supprimer "${toolbox.name}" et ses données locales ?`,
confirmLabel: "Supprimer", confirmLabel: "Supprimer",
@ -81,11 +83,12 @@ export function ToolboxIconPicker({ toolbox, onChange, tutorialTarget = "" }) {
label="Icônes de toolbox" label="Icônes de toolbox"
renderTrigger={({ open, toggle }) => ( renderTrigger={({ open, toggle }) => (
<button <button
className="toolbox-icon-picker-button" className={`toolbox-icon-picker-button nds-large-icon-picker-trigger ${open ? "active" : ""}`}
type="button" type="button"
onClick={toggle} onClick={toggle}
aria-label="Changer l'icône de la toolbox" aria-label="Changer l'icône de la toolbox"
aria-expanded={open} aria-expanded={open}
aria-pressed={open}
data-toolbox-tutorial-target={tutorialTarget || undefined} data-toolbox-tutorial-target={tutorialTarget || undefined}
title="Changer l'icône" title="Changer l'icône"
> >
@ -98,7 +101,8 @@ export function ToolboxIconPicker({ toolbox, onChange, tutorialTarget = "" }) {
{TOOLBOX_ICONS.map((path) => ( {TOOLBOX_ICONS.map((path) => (
<button <button
key={path} key={path}
className={path === icon ? "active" : ""} className={`nds-icon-button ${path === icon ? "active" : ""}`}
data-size="md"
type="button" type="button"
onClick={() => { onClick={() => {
onChange(path); onChange(path);

View file

@ -102,17 +102,17 @@ export function ToolboxesPage({ siteContent, toolboxes, getToolboxGame, actions,
<p className="eyebrow">{content.eyebrow}</p> <p className="eyebrow">{content.eyebrow}</p>
<h1>{content.title}</h1> <h1>{content.title}</h1>
<p>{content.summary}</p> <p>{content.summary}</p>
<a className="button hero-library-button library-link-button" href="#/library"><Icon name="book" />{content.libraryLink}</a> <a className="hero-library-button library-link-button nds-button" data-size="sm" href="#/library"><Icon name="book" />{content.libraryLink}</a>
</div> </div>
</section> </section>
<section className="toolbar toolbox-actions-row"> <section className="toolbar toolbox-actions-row">
<div> <div>
<button className="primary" onClick={() => actions.setCreateModal({})}>{content.newButton}</button> <button className="nds-button" data-size="sm" data-variant="primary" onClick={() => actions.setCreateModal({})}>{content.newButton}</button>
<ImportButton className="import-button" label={content.importOne} onFile={actions.importToolbox} /> <ImportButton className="nds-button" data-size="sm" label={content.importOne} onFile={actions.importToolbox} />
</div> </div>
<div> <div>
<button className="primary" onClick={actions.exportAllToolboxes}>{content.exportAll}</button> <button className="nds-button" data-size="sm" data-variant="primary" onClick={actions.exportAllToolboxes}>{content.exportAll}</button>
<ImportButton className="import-button" label={content.importAll} onFile={actions.importAllToolboxes} /> <ImportButton className="nds-button" data-size="sm" label={content.importAll} onFile={actions.importAllToolboxes} />
</div> </div>
</section> </section>
<section className="toolbox-preferences-row" aria-label={content.preferencesLabel}> <section className="toolbox-preferences-row" aria-label={content.preferencesLabel}>
@ -169,7 +169,7 @@ function StorageHelpCard({ help }) {
export function ToolboxPage(props) { export function ToolboxPage(props) {
const { toolboxId, toolboxes, getToolboxGame } = props; const { toolboxId, toolboxes, getToolboxGame } = props;
const toolbox = toolboxes.find((item) => item.id === toolboxId); const toolbox = toolboxes.find((item) => item.id === toolboxId);
if (!toolbox) return <div className="empty"><h1>Toolbox introuvable</h1><a className="button" href="#/toolboxes">Retour</a></div>; if (!toolbox) return <div className="empty"><h1>Toolbox introuvable</h1><a className="nds-button" data-size="sm" href="#/toolboxes">Retour</a></div>;
return <ToolboxView {...props} toolbox={toolbox} toolboxGame={getToolboxGame(toolbox)} embedded={false} />; return <ToolboxView {...props} toolbox={toolbox} toolboxGame={getToolboxGame(toolbox)} embedded={false} />;
} }
@ -265,7 +265,8 @@ function ToolboxView({ siteContent, toolbox, toolboxGame, embedded, actions, sto
</div> </div>
{toolboxGame && ( {toolboxGame && (
<a <a
className="button toolbox-hero-link" className="toolbox-hero-link nds-button"
data-size="sm"
href={embedded ? `#/toolbox/${toolbox.id}` : `#/games/${toolboxGame.id}`} href={embedded ? `#/toolbox/${toolbox.id}` : `#/games/${toolboxGame.id}`}
onClick={embedded ? () => actions.setDrawerGameId("") : undefined} onClick={embedded ? () => actions.setDrawerGameId("") : undefined}
> >
@ -299,7 +300,7 @@ function ToolboxView({ siteContent, toolbox, toolboxGame, embedded, actions, sto
)} )}
{!embedded && ( {!embedded && (
<section className="modules-toolbar" aria-label="Options d'affichage des outils"> <section className="modules-toolbar" aria-label="Options d'affichage des outils">
<div className="layout-switch" role="group" aria-label="Mode d'affichage des outils"> <div className="layout-switch nds-radio-button-group" data-size="sm" role="group" aria-label="Mode d'affichage des outils">
<button className={moduleColumns === 1 ? "active" : ""} onClick={() => updateToolbox({ ...toolbox, moduleColumns: 1 })} aria-pressed={moduleColumns === 1} aria-label="Afficher en lignes" title="Afficher en lignes"><Icon name="rows" /></button> <button className={moduleColumns === 1 ? "active" : ""} onClick={() => updateToolbox({ ...toolbox, moduleColumns: 1 })} aria-pressed={moduleColumns === 1} aria-label="Afficher en lignes" title="Afficher en lignes"><Icon name="rows" /></button>
<button className={moduleColumns === 2 ? "active" : ""} onClick={() => updateToolbox({ ...toolbox, moduleColumns: 2 })} aria-pressed={moduleColumns === 2} aria-label="Afficher en colonnes" title="Afficher en colonnes"><Icon name="columns" /></button> <button className={moduleColumns === 2 ? "active" : ""} onClick={() => updateToolbox({ ...toolbox, moduleColumns: 2 })} aria-pressed={moduleColumns === 2} aria-label="Afficher en colonnes" title="Afficher en colonnes"><Icon name="columns" /></button>
</div> </div>
@ -387,15 +388,15 @@ export function ToolboxDrawer({ gameId, game, siteContent, toolboxes, links, act
<div> <div>
<p className="eyebrow">Toolbox liée</p> <p className="eyebrow">Toolbox liée</p>
<div className="drawer-toolbox-actions" aria-label="Actions toolbox liée"> <div className="drawer-toolbox-actions" aria-label="Actions toolbox liée">
<button className="drawer-action-button" onClick={() => actions.setLinkModalGameId(gameId)} aria-label="Lier une toolbox" title="Lier"><Icon name="link" /></button> <button className="drawer-action-button nds-icon-button" data-size="sm" onClick={() => actions.setLinkModalGameId(gameId)} aria-label="Lier une toolbox" title="Lier"><Icon name="link" /></button>
<ImportButton className="drawer-action-button import-icon-button" ariaLabel="Importer une toolbox" title="Importer" icon="import" onFile={(file) => actions.importToolbox(file, gameId)} /> <ImportButton className="drawer-action-button nds-icon-button import-icon-button" data-size="sm" ariaLabel="Importer une toolbox" title="Importer" icon="import" onFile={(file) => actions.importToolbox(file, gameId)} />
<button className="drawer-action-button" onClick={() => actions.exportToolbox(selectedId)} disabled={!selectedId} aria-label="Exporter la toolbox liée" title="Exporter"><Icon name="export" /></button> <button className="drawer-action-button nds-icon-button" data-size="sm" onClick={() => actions.exportToolbox(selectedId)} disabled={!selectedId} aria-label="Exporter la toolbox liée" title="Exporter"><Icon name="export" /></button>
</div> </div>
</div> </div>
<button className="drawer-close-button" onClick={() => actions.setDrawerGameId("")} aria-label="Fermer" title="Fermer"><Icon name="close" /></button> <button className="drawer-close-button nds-icon-button" data-size="sm" onClick={() => actions.setDrawerGameId("")} aria-label="Fermer" title="Fermer"><Icon name="close" /></button>
</header> </header>
{toolbox ? <ToolboxView siteContent={siteContent} toolbox={toolbox} toolboxGame={game} embedded actions={actions} storageUsage={storageUsage} storageReady={storageReady} getModuleData={getModuleData} updateToolbox={updateToolbox} updateModuleData={updateModuleData} addImageFiles={addImageFiles} /> : ( {toolbox ? <ToolboxView siteContent={siteContent} toolbox={toolbox} toolboxGame={game} embedded actions={actions} storageUsage={storageUsage} storageReady={storageReady} getModuleData={getModuleData} updateToolbox={updateToolbox} updateModuleData={updateModuleData} addImageFiles={addImageFiles} /> : (
<div className="empty"><p>Aucune toolbox associée à cette page jeu.</p><button className="primary" onClick={() => actions.setCreateModal({ gameId })}>Créer et associer</button></div> <div className="empty"><p>Aucune toolbox associée à cette page jeu.</p><button className="nds-button" data-size="sm" data-variant="primary" onClick={() => actions.setCreateModal({ gameId })}>Créer et associer</button></div>
)} )}
<StorageQuota usage={storageUsage} /> <StorageQuota usage={storageUsage} />
</div> </div>

View file

@ -27,7 +27,11 @@ export function AddToolControls({ onAdd }) {
aria-label="Ajouter un outil" aria-label="Ajouter un outil"
> >
<button <button
className="primary tool-add-modal-button" className="tool-add-modal-button nds-button"
data-size="sm"
data-variant="primary"
data-edge="left"
data-border="joined"
type="button" type="button"
onClick={() => setOpen(true)} onClick={() => setOpen(true)}
data-toolbox-tutorial-target="addTool" data-toolbox-tutorial-target="addTool"
@ -38,11 +42,13 @@ export function AddToolControls({ onAdd }) {
className="tool-add-quick-dropdown" className="tool-add-quick-dropdown"
menuClassName="tool-quick-add" menuClassName="tool-quick-add"
label="Ajout rapide" label="Ajout rapide"
openOnHover
closeOnMouseLeave
renderTrigger={({ open: quickOpen, toggle }) => ( renderTrigger={({ open: quickOpen, toggle }) => (
<button <button
className={`primary tool-add-quick-toggle ${quickOpen ? "active" : ""}`} className={`tool-add-quick-toggle nds-icon-button ${quickOpen ? "active" : ""}`}
data-size="sm"
data-variant="primary"
data-edge="right"
data-border="joined"
type="button" type="button"
onClick={toggle} onClick={toggle}
aria-label="Afficher l'ajout rapide" aria-label="Afficher l'ajout rapide"
@ -58,7 +64,8 @@ export function AddToolControls({ onAdd }) {
{modules.map(([type, module]) => ( {modules.map(([type, module]) => (
<button <button
key={type} key={type}
className="tool-quick-add-button" className="tool-quick-add-button nds-icon-button"
data-size="sm"
type="button" type="button"
onClick={() => { onClick={() => {
onAdd(type); onAdd(type);
@ -82,7 +89,7 @@ export function AddToolControls({ onAdd }) {
<section className="confirm-modal tool-add-modal"> <section className="confirm-modal tool-add-modal">
<header> <header>
<h2 id="tool-add-modal-title">Ajouter un outil</h2> <h2 id="tool-add-modal-title">Ajouter un outil</h2>
<button className="drawer-close-button" type="button" onClick={() => setOpen(false)} aria-label="Fermer" title="Fermer"> <button className="drawer-close-button nds-icon-button" data-size="sm" type="button" onClick={() => setOpen(false)} aria-label="Fermer" title="Fermer">
<Icon name="close" /> <Icon name="close" />
</button> </button>
</header> </header>

View file

@ -41,7 +41,9 @@ export function CalculatorEntries({ entries, parentId, getScopedParentId, active
<li className={className} key={entry.id} {...reorder.getItemProps({ itemId: entry.id, parentId: getScopedParentId(parentId) })}> <li className={className} key={entry.id} {...reorder.getItemProps({ itemId: entry.id, parentId: getScopedParentId(parentId) })}>
<div className="tool-split-entry calculator-entry has-drag-handle"> <div className="tool-split-entry calculator-entry has-drag-handle">
<button <button
className="calculator-drag-handle" className="calculator-drag-handle nds-icon-button"
data-size="xs"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, entry.id)} onPointerDown={(event) => reorder.itemReorder.startDrag(event, entry.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${entry.label || formatResult(entry.value)}`} aria-label={`${textContent.reorderTitle || "Déplacer"} ${entry.label || formatResult(entry.value)}`}
@ -58,6 +60,8 @@ export function CalculatorEntries({ entries, parentId, getScopedParentId, active
</span> </span>
</button> </button>
<button <button
className="nds-icon-button"
data-size="xs"
type="button" type="button"
onClick={() => onAddChild(entry)} onClick={() => onAddChild(entry)}
aria-label={`${textContent.addChildTitle || "Ajouter un calcul enfant"} ${entry.label || formatResult(entry.value)}`} aria-label={`${textContent.addChildTitle || "Ajouter un calcul enfant"} ${entry.label || formatResult(entry.value)}`}
@ -65,7 +69,7 @@ export function CalculatorEntries({ entries, parentId, getScopedParentId, active
> >
<Icon name="add" /> <Icon name="add" />
</button> </button>
<button type="button" className="danger" onClick={() => onDelete(entry.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${entry.label}`} title={textContent.deleteTitle || "Supprimer"}> <button type="button" className="nds-icon-button" data-size="xs" data-variant="danger" onClick={() => onDelete(entry.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${entry.label}`} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>

View file

@ -228,8 +228,10 @@ export function CalculatorModule({ toolboxId, moduleId, context, textExchangeOpe
/> />
) : ( ) : (
<input <input
type="text"
aria-label={textContent.expressionLabel || "Calcul"} aria-label={textContent.expressionLabel || "Calcul"}
className="calculator-expression-input" className="calculator-expression-input nds-input"
data-size="sm"
ref={expressionInputRef} ref={expressionInputRef}
onChange={(event) => setExpression(event.target.value)} onChange={(event) => setExpression(event.target.value)}
onKeyDown={(event) => { onKeyDown={(event) => {
@ -251,9 +253,9 @@ export function CalculatorModule({ toolboxId, moduleId, context, textExchangeOpe
</div> </div>
<label> <label>
<span>{textContent.labelLabel || "Libellé"}</span> <span>{textContent.labelLabel || "Libellé"}</span>
<input value={label} onChange={(event) => setLabel(event.target.value)} placeholder={textContent.labelPlaceholder || "Lingots de fer"} /> <input className="nds-input" data-size="sm" type="text" value={label} onChange={(event) => setLabel(event.target.value)} placeholder={textContent.labelPlaceholder || "Lingots de fer"} />
</label> </label>
<button className="primary" disabled={result == null}>{formMode === "edit" ? textContent.updateButton || "Mettre à jour" : textContent.saveButton || "Enregistrer"}</button> <button className="nds-button" data-size="sm" data-variant="primary" disabled={result == null}>{formMode === "edit" ? textContent.updateButton || "Mettre à jour" : textContent.saveButton || "Enregistrer"}</button>
</div> </div>
</form> </form>
<div <div
@ -262,18 +264,19 @@ export function CalculatorModule({ toolboxId, moduleId, context, textExchangeOpe
> >
<div className="tool-split-actions"> <div className="tool-split-actions">
{(formulaParent || editingEntry) && ( {(formulaParent || editingEntry) && (
<button className="tool-split-root-button" type="button" onClick={returnToRoot}> <button className="tool-split-root-button nds-button" data-size="xs" type="button" onClick={returnToRoot}>
{editingEntry ? textContent.newButton || "Nouveau calcul" : textContent.rootButton || "Revenir à la racine"} {editingEntry ? textContent.newButton || "Nouveau calcul" : textContent.rootButton || "Revenir à la racine"}
</button> </button>
)} )}
<button className="tool-split-action-button danger" type="button" onClick={resetCalculator} aria-label={textContent.resetTitle || "Réinitialiser"} title={textContent.resetTitle || "Réinitialiser"}> <button className="tool-split-action-button nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={resetCalculator} aria-label={textContent.resetTitle || "Réinitialiser"} title={textContent.resetTitle || "Réinitialiser"}>
<Icon name="rubber" /> <Icon name="rubber" />
</button> </button>
<button className="tool-split-action-button" type="button" onClick={copyChecklistImport} disabled={!data.entries.length} aria-label={textContent.copyTitle || "Copier pour checklist"} title={copied ? textContent.copiedTitle || "Copié" : textContent.copyTitle || "Copier pour checklist"}> <button className="tool-split-action-button nds-icon-button" data-size="sm" type="button" onClick={copyChecklistImport} disabled={!data.entries.length} aria-label={textContent.copyTitle || "Copier pour checklist"} title={copied ? textContent.copiedTitle || "Copié" : textContent.copyTitle || "Copier pour checklist"}>
<Icon name="copy" /> <Icon name="copy" />
</button> </button>
<button <button
className={`tool-split-scroll-toggle ${data.scrollResults ? "active" : ""}`} className={`tool-split-scroll-toggle nds-switch ${data.scrollResults ? "active" : ""}`}
data-size="md"
type="button" type="button"
onClick={() => setScrollResults(!data.scrollResults)} onClick={() => setScrollResults(!data.scrollResults)}
aria-pressed={data.scrollResults} aria-pressed={data.scrollResults}

View file

@ -130,7 +130,8 @@ function CalendarWeekOptions({ textContent, hideEmptyHours, onToggle }) {
<div className="calendar-week-options"> <div className="calendar-week-options">
<span>{textContent.hideEmptyHoursLabel || "Masquer les heures vides"}</span> <span>{textContent.hideEmptyHoursLabel || "Masquer les heures vides"}</span>
<button <button
className={`tool-split-scroll-toggle calendar-hide-hours-switch ${hideEmptyHours ? "active" : ""}`} className={`tool-split-scroll-toggle calendar-hide-hours-switch nds-switch ${hideEmptyHours ? "active" : ""}`}
data-size="md"
type="button" type="button"
onClick={onToggle} onClick={onToggle}
aria-pressed={hideEmptyHours} aria-pressed={hideEmptyHours}

View file

@ -2,19 +2,11 @@
import { Icon } from "../../../components/Icon.jsx"; import { Icon } from "../../../components/Icon.jsx";
import { import {
CALENDAR_ALERT_OFFSETS, CALENDAR_ALERT_OFFSETS,
CALENDAR_COLORS CALENDAR_COLORS,
CALENDAR_COLOR_VALUES
} from "./calendarUtils.js"; } from "./calendarUtils.js";
import { ALERT_MODES } from "./timerOptions.js"; import { ALERT_MODES } from "./timerOptions.js";
const CALENDAR_COLOR_VALUES = {
violet: "#8b5cf6",
gold: "#f6c453",
cyan: "#22d3ee",
pink: "#d946ef",
green: "#22c55e",
gray: "#7d879f"
};
export function MonthlyRepeatSwitch({ value, textContent, onChange }) { export function MonthlyRepeatSwitch({ value, textContent, onChange }) {
const options = [ const options = [
{ value: "monthDay", icon: "day-number", label: textContent.monthlyRepeatMonthDay || "Numéro du jour" }, { value: "monthDay", icon: "day-number", label: textContent.monthlyRepeatMonthDay || "Numéro du jour" },
@ -22,7 +14,7 @@ export function MonthlyRepeatSwitch({ value, textContent, onChange }) {
]; ];
return ( return (
<div className="layout-switch calendar-month-repeat-switch" role="group" aria-label={textContent.monthlyRepeatLabel || "Règle mensuelle"}> <div className="layout-switch calendar-month-repeat-switch nds-radio-button-group" data-size="sm" role="group" aria-label={textContent.monthlyRepeatLabel || "Règle mensuelle"}>
{options.map((option) => ( {options.map((option) => (
<button <button
key={option.value} key={option.value}
@ -49,7 +41,7 @@ export function MonthEventRepeatSwitch({ value, monthlyRepeatBy, textContent, on
const safeValue = monthlyRepeatBy === "monthDay" && value === "weekly" ? "none" : value; const safeValue = monthlyRepeatBy === "monthDay" && value === "weekly" ? "none" : value;
return ( return (
<div className="calendar-month-event-repeat-switch" role="group" aria-label={textContent.repeatLabel || "Répétition"}> <div className="calendar-month-event-repeat-switch nds-radio-button-group" data-size="sm" role="group" aria-label={textContent.repeatLabel || "Répétition"}>
{options.map((option) => ( {options.map((option) => (
<button <button
key={option.value} key={option.value}
@ -80,7 +72,7 @@ export function CalendarAlertOffsets({ selectedOffsets, textContent, onToggle })
<legend>{textContent.alertOffsetLabel || "Alerte"}</legend> <legend>{textContent.alertOffsetLabel || "Alerte"}</legend>
{CALENDAR_ALERT_OFFSETS.map((offset) => ( {CALENDAR_ALERT_OFFSETS.map((offset) => (
<label key={offset}> <label key={offset}>
<input type="checkbox" checked={selectedOffsets.includes(offset)} onChange={() => onToggle(offset)} /> <input className="nds-checkbox" data-size="sm" type="checkbox" checked={selectedOffsets.includes(offset)} onChange={() => onToggle(offset)} />
<span>{labels[offset]}</span> <span>{labels[offset]}</span>
</label> </label>
))} ))}
@ -94,7 +86,7 @@ export function CalendarColorPicker({ value, textContent, onChange }) {
<div className="calendar-color-picker notepad-color-dropdown"> <div className="calendar-color-picker notepad-color-dropdown">
<span>{label}</span> <span>{label}</span>
<button <button
className="calendar-color-toggle notepad-color-toggle" className="calendar-color-toggle notepad-color-toggle nds-swatch"
type="button" type="button"
title={label} title={label}
aria-label={label} aria-label={label}
@ -104,7 +96,7 @@ export function CalendarColorPicker({ value, textContent, onChange }) {
{CALENDAR_COLORS.map((color) => ( {CALENDAR_COLORS.map((color) => (
<button <button
key={color} key={color}
className={`notepad-swatch ${value === color ? "active" : ""}`} className={`notepad-swatch nds-swatch ${value === color ? "active" : ""}`}
type="button" type="button"
title={textContent[`color${color.charAt(0).toUpperCase()}${color.slice(1)}`] || color} title={textContent[`color${color.charAt(0).toUpperCase()}${color.slice(1)}`] || color}
aria-label={textContent[`color${color.charAt(0).toUpperCase()}${color.slice(1)}`] || color} aria-label={textContent[`color${color.charAt(0).toUpperCase()}${color.slice(1)}`] || color}
@ -126,7 +118,7 @@ export function CalendarAlertModeSwitch({ mode, textContent, onChange }) {
}; };
return ( return (
<div className="timer-alert-mode-switch calendar-alert-mode-switch" 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="xs" role="group" aria-label={textContent.alertModeTitle || "Mode d'alerte"}>
{ALERT_MODES.map((option) => ( {ALERT_MODES.map((option) => (
<button <button
key={option.mode} key={option.mode}

View file

@ -29,14 +29,14 @@ export function CalendarEventForm({
<div className="calendar-form-row calendar-form-main-row"> <div className="calendar-form-row calendar-form-main-row">
<label className="calendar-title-field"> <label className="calendar-title-field">
<span>{textContent.titleLabel || "Titre"}</span> <span>{textContent.titleLabel || "Titre"}</span>
<input value={draft.title} onChange={(event) => onDraftChange({ title: event.target.value })} placeholder={textContent.titlePlaceholder || "Raid, reset, event..."} /> <input className="nds-input" data-size="sm" type="text" value={draft.title} onChange={(event) => onDraftChange({ title: event.target.value })} placeholder={textContent.titlePlaceholder || "Raid, reset, event..."} />
</label> </label>
<CalendarColorPicker value={draft.color} textContent={textContent} onChange={(color) => onDraftChange({ color })} /> <CalendarColorPicker value={draft.color} textContent={textContent} onChange={(color) => onDraftChange({ color })} />
{draft.id && <div className="calendar-delete-actions"> {draft.id && <div className="calendar-delete-actions">
{data.viewMode === "month" && draft.repeat !== "none" && activeOccurrenceDate && ( {data.viewMode === "month" && draft.repeat !== "none" && activeOccurrenceDate && (
<button className="danger calendar-form-delete-button" type="button" onClick={() => onDeleteOccurrence(draft.id)} aria-label={textContent.deleteOccurrenceTitle || "Supprimer cette occurrence"} title={textContent.deleteOccurrenceTitle || "Supprimer cette occurrence"}><Icon name="trashcan-one" /></button> <button className="calendar-form-delete-button nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={() => onDeleteOccurrence(draft.id)} aria-label={textContent.deleteOccurrenceTitle || "Supprimer cette occurrence"} title={textContent.deleteOccurrenceTitle || "Supprimer cette occurrence"}><Icon name="trashcan-one" /></button>
)} )}
<button className="danger calendar-form-delete-button" type="button" onClick={() => onDeleteEvent(draft.id)} aria-label={data.viewMode === "month" && draft.repeat !== "none" ? textContent.deleteSeriesTitle || "Supprimer toutes les répétitions" : textContent.deleteTitle || "Supprimer"} title={data.viewMode === "month" && draft.repeat !== "none" ? textContent.deleteSeriesTitle || "Supprimer toutes les répétitions" : textContent.deleteTitle || "Supprimer"}><Icon name="trash" /></button> <button className="calendar-form-delete-button nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={() => onDeleteEvent(draft.id)} aria-label={data.viewMode === "month" && draft.repeat !== "none" ? textContent.deleteSeriesTitle || "Supprimer toutes les répétitions" : textContent.deleteTitle || "Supprimer"} title={data.viewMode === "month" && draft.repeat !== "none" ? textContent.deleteSeriesTitle || "Supprimer toutes les répétitions" : textContent.deleteTitle || "Supprimer"}><Icon name="trash" /></button>
</div>} </div>}
</div> </div>
<div className={`calendar-form-row ${data.viewMode === "month" ? "calendar-form-month-row" : "calendar-form-time-row"}`}> <div className={`calendar-form-row ${data.viewMode === "month" ? "calendar-form-month-row" : "calendar-form-time-row"}`}>
@ -67,6 +67,8 @@ export function CalendarEventForm({
/> />
) : ( ) : (
<select <select
className="nds-select"
data-size="sm"
value={draft.dayIndex} value={draft.dayIndex}
onChange={(event) => { onChange={(event) => {
const value = Number(event.target.value); const value = Number(event.target.value);
@ -97,11 +99,11 @@ export function CalendarEventForm({
<> <>
<label className="calendar-time-field"> <label className="calendar-time-field">
<span>{textContent.startLabel || "Début"}</span> <span>{textContent.startLabel || "Début"}</span>
<input type="time" step={WEEK_TIME_INPUT_STEP_MINUTES * 60} value={draft.startTime} onChange={(event) => onDraftChange({ startTime: event.target.value })} /> <input className="nds-time" data-size="xs" type="time" step={WEEK_TIME_INPUT_STEP_MINUTES * 60} value={draft.startTime} onChange={(event) => onDraftChange({ startTime: event.target.value })} />
</label> </label>
<label className="calendar-time-field"> <label className="calendar-time-field">
<span>{textContent.endLabel || "Fin"}</span> <span>{textContent.endLabel || "Fin"}</span>
<input type="time" step={WEEK_TIME_INPUT_STEP_MINUTES * 60} value={draft.endTime} onChange={(event) => onDraftChange({ endTime: event.target.value })} /> <input className="nds-time" data-size="xs" type="time" step={WEEK_TIME_INPUT_STEP_MINUTES * 60} value={draft.endTime} onChange={(event) => onDraftChange({ endTime: event.target.value })} />
</label> </label>
</> </>
)} )}
@ -113,7 +115,8 @@ export function CalendarEventForm({
) : <div className="calendar-repeat-field"> ) : <div className="calendar-repeat-field">
<span>{textContent.repeatableTitle || "Répétable"}</span> <span>{textContent.repeatableTitle || "Répétable"}</span>
<button <button
className={`tool-split-scroll-toggle calendar-repeat-switch ${draft.repeatable !== false ? "active" : ""}`} className={`tool-split-scroll-toggle calendar-repeat-switch nds-switch ${draft.repeatable !== false ? "active" : ""}`}
data-size="lg"
type="button" type="button"
onClick={() => onDraftChange({ repeatable: draft.repeatable === false })} onClick={() => onDraftChange({ repeatable: draft.repeatable === false })}
aria-pressed={draft.repeatable !== false} aria-pressed={draft.repeatable !== false}
@ -131,8 +134,8 @@ export function CalendarEventForm({
<CalendarAlertModeSwitch mode={draft.alertMode} textContent={textContent} onChange={onAlertModeChange} /> <CalendarAlertModeSwitch mode={draft.alertMode} textContent={textContent} onChange={onAlertModeChange} />
</div> </div>
<div className="calendar-form-actions"> <div className="calendar-form-actions">
<button className="primary" type="submit">{draft.id ? textContent.saveButton || "Enregistrer" : textContent.addButton || "Ajouter"}</button> <button className="nds-button" data-size="sm" data-variant="primary" type="submit">{draft.id ? textContent.saveButton || "Enregistrer" : textContent.addButton || "Ajouter"}</button>
<button type="button" onClick={onClose}>{textContent.cancelButton || "Annuler"}</button> <button className="nds-button" data-size="sm" type="button" onClick={onClose}>{textContent.cancelButton || "Annuler"}</button>
</div> </div>
</div> </div>
{error && <p className="timer-form-error calendar-form-error" role="alert"><Icon name="warning" /><span>{error}</span></p>} {error && <p className="timer-form-error calendar-form-error" role="alert"><Icon name="warning" /><span>{error}</span></p>}

View file

@ -48,7 +48,7 @@ export function MonthDateDropdown({ value, displayedMonth, minDate = "", mode, t
const safeValue = normalizeDraftDateKey(value) || formatLocalDateKey(displayedMonth); const safeValue = normalizeDraftDateKey(value) || formatLocalDateKey(displayedMonth);
return ( return (
<div className={`calendar-month-date-dropdown ${align === "end" ? "is-aligned-end" : ""}`}> <div className={`calendar-month-date-dropdown ${align === "end" ? "is-aligned-end" : ""}`}>
<button className="calendar-month-date-trigger" type="button" aria-label={label}> <button className="calendar-month-date-trigger nds-date-trigger" data-size="sm" type="button" aria-label={label}>
<Icon name={mode === "weekday" ? "day-name" : "day-number"} /> <Icon name={mode === "weekday" ? "day-name" : "day-number"} />
<span>{formatMonthDateLabel(safeValue, mode)}</span> <span>{formatMonthDateLabel(safeValue, mode)}</span>
<Icon name="chevron-down" /> <Icon name="chevron-down" />

View file

@ -4,6 +4,8 @@ import {
WEEK_SLOT_MINUTES, WEEK_SLOT_MINUTES,
addDays, addDays,
formatLocalDateKey, formatLocalDateKey,
getCalendarColorValue,
getCalendarEventSurfaceVariant,
minutesToTimeValue, minutesToTimeValue,
pad2 pad2
} from "./calendarUtils.js"; } from "./calendarUtils.js";
@ -137,19 +139,26 @@ function CalendarOccurrence({ occurrence, layout, active, onEdit }) {
const durationMinutes = Math.max(WEEK_SLOT_MINUTES, (occurrence.endMs - occurrence.startMs) / 60000 || WEEK_SLOT_MINUTES); const durationMinutes = Math.max(WEEK_SLOT_MINUTES, (occurrence.endMs - occurrence.startMs) / 60000 || WEEK_SLOT_MINUTES);
const startMinutes = occurrence.event.startMinutes; const startMinutes = occurrence.event.startMinutes;
const timeLabel = `${minutesToTimeValue(occurrence.event.startMinutes)}${occurrence.event.endMinutes ? ` - ${minutesToTimeValue(occurrence.event.endMinutes)}` : ""}`; const timeLabel = `${minutesToTimeValue(occurrence.event.startMinutes)}${occurrence.event.endMinutes ? ` - ${minutesToTimeValue(occurrence.event.endMinutes)}` : ""}`;
const compact = durationMinutes < 120;
const top = getWeekMinutePosition(layout, startMinutes); const top = getWeekMinutePosition(layout, startMinutes);
const bottom = getWeekMinutePosition(layout, Math.min(24 * 60, startMinutes + durationMinutes)); const bottom = getWeekMinutePosition(layout, Math.min(24 * 60, startMinutes + durationMinutes));
const height = compact ? WEEK_COMPACT_EVENT_HEIGHT : Math.max(WEEK_COMPACT_EVENT_HEIGHT, bottom - top); const naturalHeight = bottom - top;
const eventSurfaceSize = naturalHeight < WEEK_COMPACT_EVENT_HEIGHT ? "xs" : naturalHeight < 44 ? "sm" : "md";
const height = eventSurfaceSize === "xs" ? 10 : eventSurfaceSize === "sm" ? WEEK_COMPACT_EVENT_HEIGHT : Math.max(44, naturalHeight);
const color = occurrence.event.color || "violet";
return ( return (
<article <article
className={`calendar-event calendar-event-${occurrence.event.color || "violet"} ${active ? "is-active" : ""} ${compact ? "is-compact" : ""}`} className={`calendar-event nds-event-surface ${active ? "is-active" : ""} ${eventSurfaceSize !== "md" ? "is-compact" : ""}`}
data-color={getCalendarColorValue(color)}
data-size={eventSurfaceSize}
data-variant={getCalendarEventSurfaceVariant(color)}
style={{ style={{
top: `${top}px`, top: `${top}px`,
width: "calc((100% / var(--calendar-event-lane-count, 1)) - 2px)",
height: `${height}px`, height: `${height}px`,
"--calendar-event-lane": occurrence.lane || 0, "--calendar-event-lane": occurrence.lane || 0,
"--calendar-event-lane-count": occurrence.laneCount || 1 "--calendar-event-lane-count": occurrence.laneCount || 1,
"--nds-event-color": getCalendarColorValue(color)
}} }}
> >
<Tooltip <Tooltip
@ -161,7 +170,7 @@ function CalendarOccurrence({ occurrence, layout, active, onEdit }) {
onClick={onEdit} onClick={onEdit}
> >
<strong>{occurrence.event.title}</strong> <strong>{occurrence.event.title}</strong>
{!compact && <span>{timeLabel}</span>} <span>{timeLabel}</span>
</Tooltip> </Tooltip>
</article> </article>
); );
@ -188,10 +197,10 @@ export function CalendarMonthView({
return ( return (
<> <>
<div className="calendar-month-toolbar"> <div className="calendar-month-toolbar">
<button type="button" onClick={onPreviousMonth} title={textContent.previousMonthTitle || "Mois précédent"} aria-label={textContent.previousMonthTitle || "Mois précédent"}>{textContent.previousShortLabel || "Prec."}</button> <button className="nds-button" data-size="xs" type="button" onClick={onPreviousMonth} title={textContent.previousMonthTitle || "Mois précédent"} aria-label={textContent.previousMonthTitle || "Mois précédent"}>{textContent.previousShortLabel || "Prec."}</button>
<strong>{formatMonthTitle(monthStart)}</strong> <strong>{formatMonthTitle(monthStart)}</strong>
<button type="button" onClick={onCurrentMonth}>{textContent.currentMonthTitle || "Mois courant"}</button> <button className="nds-button" data-size="xs" type="button" onClick={onCurrentMonth}>{textContent.currentMonthTitle || "Mois courant"}</button>
<button type="button" onClick={onNextMonth} title={textContent.nextMonthTitle || "Mois suivant"} aria-label={textContent.nextMonthTitle || "Mois suivant"}>{textContent.nextShortLabel || "Suiv."}</button> <button className="nds-button" data-size="xs" type="button" onClick={onNextMonth} title={textContent.nextMonthTitle || "Mois suivant"} aria-label={textContent.nextMonthTitle || "Mois suivant"}>{textContent.nextShortLabel || "Suiv."}</button>
</div> </div>
<CalendarMonthGrid <CalendarMonthGrid
days={monthGridDays} days={monthGridDays}
@ -268,22 +277,29 @@ function CalendarMonthBar({ segment, active, onEdit }) {
const startColumn = segment.startIndex % WEEKDAY_LABELS.length + 1; const startColumn = segment.startIndex % WEEKDAY_LABELS.length + 1;
const endColumn = segment.endIndex % WEEKDAY_LABELS.length + 2; const endColumn = segment.endIndex % WEEKDAY_LABELS.length + 2;
const laneCount = Math.max(1, Math.min(3, segment.laneCount || 1)); const laneCount = Math.max(1, Math.min(3, segment.laneCount || 1));
const barHeight = laneCount === 1 ? 28 : laneCount === 2 ? 23 : 18; const barHeight = laneCount <= 2 ? 22 : 10;
const barGap = laneCount === 3 ? 3 : 4; const barGap = laneCount === 3 ? 2 : 4;
const eventSurfaceSize = barHeight <= 10 ? "xs" : barHeight <= 22 ? "sm" : "md";
const startLabel = new Date(segment.occurrence.startMs).toLocaleDateString(undefined, { day: "2-digit", month: "short", year: "numeric" }); const startLabel = new Date(segment.occurrence.startMs).toLocaleDateString(undefined, { day: "2-digit", month: "short", year: "numeric" });
const endDate = addDays(new Date(segment.occurrence.endMs), -1); const endDate = addDays(new Date(segment.occurrence.endMs), -1);
const endLabel = endDate.toLocaleDateString(undefined, { day: "2-digit", month: "short", year: "numeric" }); const endLabel = endDate.toLocaleDateString(undefined, { day: "2-digit", month: "short", year: "numeric" });
const dateLabel = startLabel === endLabel ? startLabel : `${startLabel} - ${endLabel}`; const dateLabel = startLabel === endLabel ? startLabel : `${startLabel} - ${endLabel}`;
const color = segment.occurrence.event.color || "violet";
return ( return (
<article <article
className={`calendar-month-bar calendar-event-${segment.occurrence.event.color || "violet"} ${active ? "is-active" : ""}`} className={`calendar-month-bar nds-event-surface ${active ? "is-active" : ""}`}
data-color={getCalendarColorValue(color)}
data-size={eventSurfaceSize}
data-variant={getCalendarEventSurfaceVariant(color)}
style={{ style={{
gridColumn: `${startColumn} / ${endColumn}`, gridColumn: `${startColumn} / ${endColumn}`,
gridRow, gridRow,
width: "auto",
"--calendar-event-lane": segment.lane || 0, "--calendar-event-lane": segment.lane || 0,
"--calendar-month-bar-height": `${barHeight}px`, "--calendar-month-bar-height": `${barHeight}px`,
"--calendar-month-bar-gap": `${barGap}px` "--calendar-month-bar-gap": `${barGap}px`,
"--nds-event-color": getCalendarColorValue(color)
}} }}
> >
<Tooltip <Tooltip

View file

@ -113,10 +113,10 @@ export function ChecklistModule({ toolboxId, moduleId, context, editing, textExc
{editing && ( {editing && (
<div className="module-add-panel"> <div className="module-add-panel">
<form className="inline-form checklist-add-form" onSubmit={addItem}> <form className="inline-form checklist-add-form" onSubmit={addItem}>
<input name="section" placeholder={textContent.sectionPlaceholder || "Catégorie"} value={sectionTitle} onChange={(event) => setSectionTitle(event.target.value)} /> <input className="nds-input" data-size="sm" name="section" type="text" placeholder={textContent.sectionPlaceholder || "Catégorie"} value={sectionTitle} onChange={(event) => setSectionTitle(event.target.value)} />
<input name="label" placeholder={textContent.itemPlaceholder || "Nouvel item"} value={label} onChange={(event) => setLabel(event.target.value)} /> <input className="nds-input" data-size="sm" name="label" type="text" placeholder={textContent.itemPlaceholder || "Nouvel item"} value={label} onChange={(event) => setLabel(event.target.value)} />
<input className="checklist-qty-input" name="qty" type="number" min="1" value={qty} onChange={(event) => setQty(event.target.value)} aria-label={textContent.quantityLabel || "Quantité cible"} /> <input className="checklist-qty-input nds-input" data-size="sm" name="qty" type="number" min="1" value={qty} onChange={(event) => setQty(event.target.value)} aria-label={textContent.quantityLabel || "Quantité cible"} />
<button className="primary">{textContent.addButton || "Ajouter"}</button> <button className="nds-button" data-size="sm" data-variant="primary">{textContent.addButton || "Ajouter"}</button>
</form> </form>
</div> </div>
)} )}
@ -126,7 +126,8 @@ export function ChecklistModule({ toolboxId, moduleId, context, editing, textExc
</div> </div>
)} )}
{namedSections.length > 0 && ( {namedSections.length > 0 && (
<div className="checklist-toolbar" role="radiogroup" aria-label={textContent.completedSectionsModeTitle || "Affichage des catégories terminées"}> <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"}>
{[ {[
{ mode: "visible", label: textContent.completedSectionsVisibleLabel || "Visible", icon: "eye-open" }, { mode: "visible", label: textContent.completedSectionsVisibleLabel || "Visible", icon: "eye-open" },
{ mode: "reduced", label: textContent.completedSectionsReducedLabel || "Réduit", icon: "hide" }, { mode: "reduced", label: textContent.completedSectionsReducedLabel || "Réduit", icon: "hide" },
@ -146,6 +147,7 @@ export function ChecklistModule({ toolboxId, moduleId, context, editing, textExc
</button> </button>
))} ))}
</div> </div>
</div>
)} )}
<ul className="checklist"> <ul className="checklist">
{visibleSections.map((section, index) => { {visibleSections.map((section, index) => {

View file

@ -67,7 +67,9 @@ export function ChecklistSection({ section, context, data, reorder, onRenameSect
{showTitle && ( {showTitle && (
<div className="checklist-section-header"> <div className="checklist-section-header">
<button <button
className="checklist-drag-handle" className="checklist-drag-handle nds-icon-button"
data-size="xs"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: section.id })} onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: section.id })}
aria-label={`${textContent.reorderSectionTitle || "Déplacer la catégorie"} ${section.title}`} aria-label={`${textContent.reorderSectionTitle || "Déplacer la catégorie"} ${section.title}`}
@ -79,6 +81,7 @@ export function ChecklistSection({ section, context, data, reorder, onRenameSect
<input <input
{...titleEdit.getInputProps({ {...titleEdit.getInputProps({
className: "checklist-section-title-input", className: "checklist-section-title-input",
type: "text",
title: textContent.sectionEditTitle || "Renommer la catégorie", title: textContent.sectionEditTitle || "Renommer la catégorie",
"aria-label": textContent.sectionEditTitle || "Renommer la catégorie" "aria-label": textContent.sectionEditTitle || "Renommer la catégorie"
})} })}
@ -87,7 +90,8 @@ export function ChecklistSection({ section, context, data, reorder, onRenameSect
<div className="checklist-section-actions"> <div className="checklist-section-actions">
<span>{section.items.filter((item) => context.clampQty(item.qtyCurrent, item.qtyTarget) >= item.qtyTarget).length} / {section.items.length}</span> <span>{section.items.filter((item) => context.clampQty(item.qtyCurrent, item.qtyTarget) >= item.qtyTarget).length} / {section.items.length}</span>
<button <button
className="checklist-section-collapse-button" className="checklist-section-collapse-button nds-icon-button"
data-size="xs"
type="button" type="button"
onClick={toggleSectionCollapsed} onClick={toggleSectionCollapsed}
aria-expanded={!isCollapsed} aria-expanded={!isCollapsed}
@ -160,7 +164,9 @@ function ChecklistItem({ item, section, context, reorder, items, save, onUpdateI
<li className={className} {...reorder.getItemProps({ itemId: item.id, groupId: section.id })}> <li className={className} {...reorder.getItemProps({ itemId: item.id, groupId: section.id })}>
<div className={`checklist-item-main ${editing ? "is-editing" : ""}`}> <div className={`checklist-item-main ${editing ? "is-editing" : ""}`}>
<button <button
className="checklist-drag-handle" className="checklist-drag-handle nds-icon-button"
data-size="xs"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, item.id)} onPointerDown={(event) => reorder.itemReorder.startDrag(event, item.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${item.label}`} aria-label={`${textContent.reorderTitle || "Déplacer"} ${item.label}`}
@ -170,17 +176,20 @@ function ChecklistItem({ item, section, context, reorder, items, save, onUpdateI
</button> </button>
{item.qtyTarget === 1 ? ( {item.qtyTarget === 1 ? (
<input <input
className="nds-checkbox"
data-size="sm"
type="checkbox" type="checkbox"
checked={done} checked={done}
onChange={(event) => updateItem((entry) => ({ ...entry, qtyTarget: 1, qtyCurrent: event.target.checked ? 1 : 0 }))} onChange={(event) => updateItem((entry) => ({ ...entry, qtyTarget: 1, qtyCurrent: event.target.checked ? 1 : 0 }))}
aria-label={`Terminer ${item.label}`} aria-label={`Terminer ${item.label}`}
/> />
) : ( ) : (
<div className="checklist-qty-controls" aria-label={`Quantité ${item.label}`}> <div className="checklist-qty-controls nds-quantity-stepper" data-size="md" aria-label={`Quantité ${item.label}`}>
<button type="button" onClick={() => updateItem((entry) => ({ ...entry, qtyCurrent: context.clampQty(entry.qtyCurrent - 1, entry.qtyTarget) }))} aria-label={textContent.decrementLabel || "Retirer une quantité"}>-</button> <button type="button" onClick={() => updateItem((entry) => ({ ...entry, qtyCurrent: context.clampQty(entry.qtyCurrent - 1, entry.qtyTarget) }))} aria-label={textContent.decrementLabel || "Retirer une quantité"}>-</button>
<label className="checklist-qty-current"> <label className="checklist-qty-current">
<input <input
type="number" type="number"
className="nds-quantity-stepper-input"
max={item.qtyTarget} max={item.qtyTarget}
value={context.clampQty(item.qtyCurrent, item.qtyTarget)} value={context.clampQty(item.qtyCurrent, item.qtyTarget)}
style={{ "--qty-current-digits": String(context.clampQty(item.qtyCurrent, item.qtyTarget)).length }} style={{ "--qty-current-digits": String(context.clampQty(item.qtyCurrent, item.qtyTarget)).length }}
@ -196,13 +205,15 @@ function ChecklistItem({ item, section, context, reorder, items, save, onUpdateI
<> <>
<input <input
{...labelEdit.getInputProps({ {...labelEdit.getInputProps({
className: "checklist-label-input", className: "checklist-label-input nds-inline-input",
type: "text",
"aria-label": textContent.itemEditLabel || "Libellé" "aria-label": textContent.itemEditLabel || "Libellé"
})} })}
/> />
<input <input
{...categoryEdit.getInputProps({ {...categoryEdit.getInputProps({
className: "checklist-category-input", className: "checklist-category-input nds-inline-input",
type: "text",
placeholder: textContent.sectionPlaceholder || "Catégorie", placeholder: textContent.sectionPlaceholder || "Catégorie",
"aria-label": textContent.categoryEditLabel || "Catégorie" "aria-label": textContent.categoryEditLabel || "Catégorie"
})} })}
@ -214,10 +225,10 @@ function ChecklistItem({ item, section, context, reorder, items, save, onUpdateI
</button> </button>
)} )}
</div> </div>
<button type="button" className="checklist-edit-button" onClick={onToggleEditing} aria-label={`${textContent.editTitle || "Modifier"} ${item.label}`} title={textContent.editTitle || "Modifier"}> <button type="button" className="checklist-edit-button nds-icon-button" data-size="sm" onClick={onToggleEditing} aria-label={`${textContent.editTitle || "Modifier"} ${item.label}`} title={textContent.editTitle || "Modifier"}>
<Icon name="edit" /> <Icon name="edit" />
</button> </button>
<button className="checklist-delete-button danger" onClick={() => onDeleteItem(item.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${item.label}`} title={textContent.deleteTitle || "Supprimer"}> <button className="checklist-delete-button nds-icon-button" data-size="sm" data-variant="danger" onClick={() => onDeleteItem(item.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${item.label}`} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</li> </li>

View file

@ -48,14 +48,14 @@ export function CombosEditor({
<div className="combos-device-row"> <div className="combos-device-row">
<label> <label>
<span>{textContent.deviceLabel || "Périphérique"}</span> <span>{textContent.deviceLabel || "Périphérique"}</span>
<select value={data.device} onChange={(event) => onDeviceChange(event.target.value)}> <select className="nds-select" data-size="sm" value={data.device} onChange={(event) => onDeviceChange(event.target.value)}>
{DEVICE_OPTIONS.map((device) => ( {DEVICE_OPTIONS.map((device) => (
<option key={device.value} value={device.value}>{device.label}</option> <option key={device.value} value={device.value}>{device.label}</option>
))} ))}
</select> </select>
</label> </label>
{data.device === "keyboardMouse" && ( {data.device === "keyboardMouse" && (
<div className="combos-keyboard-layout-switch" role="group" aria-label={textContent.keyboardLayoutLabel || "Disposition clavier"}> <div className="combos-keyboard-layout-switch nds-radio-button-group" data-size="sm" role="group" aria-label={textContent.keyboardLayoutLabel || "Disposition clavier"}>
{[ {[
{ value: "azerty", label: "AZERTY" }, { value: "azerty", label: "AZERTY" },
{ value: "qwerty", label: "QWERTY" } { value: "qwerty", label: "QWERTY" }
@ -78,11 +78,11 @@ export function CombosEditor({
<div className="combos-form-grid"> <div className="combos-form-grid">
<label> <label>
<span>{textContent.targetCategoryLabel || "Catégorie"}</span> <span>{textContent.targetCategoryLabel || "Catégorie"}</span>
<input value={comboCategory} onChange={(event) => onComboCategoryChange(event.target.value)} placeholder={textContent.noCategoryLabel || "Sans catégorie"} /> <input className="nds-input" data-size="sm" type="text" value={comboCategory} onChange={(event) => onComboCategoryChange(event.target.value)} placeholder={textContent.noCategoryLabel || "Sans catégorie"} />
</label> </label>
<label> <label>
<span>{textContent.comboNameLabel || "Nom"}</span> <span>{textContent.comboNameLabel || "Nom"}</span>
<input value={comboName} onChange={(event) => onComboNameChange(event.target.value)} placeholder={textContent.comboNamePlaceholder || "Nom du combo"} /> <input className="nds-input" data-size="sm" type="text" value={comboName} onChange={(event) => onComboNameChange(event.target.value)} placeholder={textContent.comboNamePlaceholder || "Nom du combo"} />
</label> </label>
</div> </div>
<div className="combos-palette" aria-label={textContent.paletteLabel || "Palette de touches"}> <div className="combos-palette" aria-label={textContent.paletteLabel || "Palette de touches"}>
@ -115,8 +115,8 @@ export function CombosEditor({
{data.device === "keyboardMouse" && ( {data.device === "keyboardMouse" && (
<div className="inline-form combos-custom-key-form"> <div className="inline-form combos-custom-key-form">
<input value={customKey} onChange={(event) => onCustomKeyChange(event.target.value)} placeholder={textContent.customKeyPlaceholder || "Touche personnalisée"} /> <input className="nds-input" data-size="sm" type="text" value={customKey} onChange={(event) => onCustomKeyChange(event.target.value)} placeholder={textContent.customKeyPlaceholder || "Touche personnalisée"} />
<button type="button" onClick={onCustomKeyAdd}>{textContent.addCustomKeyButton || "Ajouter touche"}</button> <button className="nds-button" data-size="sm" type="button" onClick={onCustomKeyAdd}>{textContent.addCustomKeyButton || "Ajouter touche"}</button>
</div> </div>
)} )}
@ -135,7 +135,7 @@ export function CombosEditor({
/> />
<div className="combo-draft-actions"> <div className="combo-draft-actions">
<label className="combo-hold-field"> <label className="combo-hold-field">
<span className="combo-hold-controls"> <span className="combo-hold-controls nds-compound-toggle-field">
<button <button
type="button" type="button"
className={selectedDraftInput?.hold || selectedDraftInput?.holdMs ? "active" : ""} className={selectedDraftInput?.hold || selectedDraftInput?.holdMs ? "active" : ""}
@ -146,6 +146,7 @@ export function CombosEditor({
{textContent.holdToggleLabel || "Hold"} {textContent.holdToggleLabel || "Hold"}
</button> </button>
<input <input
className="nds-compound-toggle-field-input"
type="number" type="number"
min="0" min="0"
max="99" max="99"
@ -161,7 +162,8 @@ export function CombosEditor({
</label> </label>
<button <button
type="button" type="button"
className={`combos-simultaneous-toggle ${simultaneousMode ? "active" : ""}`} className={`combos-simultaneous-toggle nds-radio-button-group ${simultaneousMode ? "active" : ""}`}
data-size="xs"
onClick={onSimultaneousModeToggle} onClick={onSimultaneousModeToggle}
aria-pressed={simultaneousMode} aria-pressed={simultaneousMode}
title={textContent.simultaneousModeTitle || "Ajout simultané"} title={textContent.simultaneousModeTitle || "Ajout simultané"}
@ -170,19 +172,19 @@ export function CombosEditor({
<span className={!simultaneousMode ? "active" : ""}>{textContent.successiveModeLabel || "A › B"}</span> <span className={!simultaneousMode ? "active" : ""}>{textContent.successiveModeLabel || "A › B"}</span>
<span className={simultaneousMode ? "active" : ""}>{textContent.simultaneousModeLabel || "A + B"}</span> <span className={simultaneousMode ? "active" : ""}>{textContent.simultaneousModeLabel || "A + B"}</span>
</button> </button>
<button type="button" className="notepad-toolbar-button" onClick={onLastInputRemove} disabled={!cleanDraftInputs.length} title={textContent.removeLastInputTitle || "Retirer la dernière touche"} aria-label={textContent.removeLastInputTitle || "Retirer la dernière touche"}> <button type="button" className="notepad-toolbar-button nds-icon-button" data-size="sm" onClick={onLastInputRemove} disabled={!cleanDraftInputs.length} title={textContent.removeLastInputTitle || "Retirer la dernière touche"} aria-label={textContent.removeLastInputTitle || "Retirer la dernière touche"}>
<Icon name="back" /> <Icon name="back" />
</button> </button>
<button type="button" className="notepad-toolbar-button danger" onClick={onActiveStepRemove} disabled={draftInputs.length <= 1} title={textContent.removeStepTitle || "Supprimer l'étape"} aria-label={textContent.removeStepTitle || "Supprimer l'étape"}> <button type="button" className="notepad-toolbar-button nds-icon-button" data-size="sm" data-variant="danger" onClick={onActiveStepRemove} disabled={draftInputs.length <= 1} title={textContent.removeStepTitle || "Supprimer l'étape"} aria-label={textContent.removeStepTitle || "Supprimer l'étape"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
<div className="combo-save-actions"> <div className="combo-save-actions">
{editingComboId && ( {editingComboId && (
<button type="button" className="combo-cancel-button" onClick={onComboEditCancel}> <button type="button" className="combo-cancel-button nds-button" data-size="sm" onClick={onComboEditCancel}>
{textContent.cancelEditButton || "Annuler"} {textContent.cancelEditButton || "Annuler"}
</button> </button>
)} )}
<button className="primary" disabled={!comboName.trim() && !cleanDraftInputs.length}> <button className="nds-button" data-size="sm" data-variant="primary" disabled={!comboName.trim() && !cleanDraftInputs.length}>
{editingComboId ? textContent.saveComboButton || "Enregistrer" : textContent.addComboButton || "Ajouter le combo"} {editingComboId ? textContent.saveComboButton || "Enregistrer" : textContent.addComboButton || "Ajouter le combo"}
</button> </button>
</div> </div>

View file

@ -27,7 +27,9 @@ export function CombosList({
return ( return (
<article className={comboClassName} key={combo.id} {...reorder.getItemProps({ itemId: combo.id, groupId: category })}> <article className={comboClassName} key={combo.id} {...reorder.getItemProps({ itemId: combo.id, groupId: category })}>
<button <button
className="task-planner-drag-handle combo-drag-handle" className="task-planner-drag-handle combo-drag-handle nds-icon-button"
data-size="xs"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, combo.id)} onPointerDown={(event) => reorder.itemReorder.startDrag(event, combo.id)}
aria-label={`${textContent.reorderComboTitle || "Déplacer le combo"} ${combo.name}`} aria-label={`${textContent.reorderComboTitle || "Déplacer le combo"} ${combo.name}`}
@ -45,10 +47,10 @@ export function CombosList({
<ComboSequence inputs={combo.inputs} device={combo.device || data.device} emptyLabel={textContent.emptySequence || "Combo sans touches"} /> <ComboSequence inputs={combo.inputs} device={combo.device || data.device} emptyLabel={textContent.emptySequence || "Combo sans touches"} />
</div> </div>
<div className="combo-card-actions"> <div className="combo-card-actions">
<button type="button" className="checklist-delete-button" onClick={() => onComboEdit(combo)} aria-label={`${textContent.editComboTitle || "Modifier le combo"} ${combo.name}`} title={textContent.editComboTitle || "Modifier le combo"}> <button type="button" className="checklist-delete-button nds-icon-button" data-size="sm" onClick={() => onComboEdit(combo)} aria-label={`${textContent.editComboTitle || "Modifier le combo"} ${combo.name}`} title={textContent.editComboTitle || "Modifier le combo"}>
<Icon name="edit" /> <Icon name="edit" />
</button> </button>
<button type="button" className="checklist-delete-button danger" onClick={() => onComboDelete(combo.id)} aria-label={`${textContent.deleteComboTitle || "Supprimer le combo"} ${combo.name}`} title={textContent.deleteComboTitle || "Supprimer le combo"}> <button type="button" className="checklist-delete-button nds-icon-button" data-size="sm" data-variant="danger" onClick={() => onComboDelete(combo.id)} aria-label={`${textContent.deleteComboTitle || "Supprimer le combo"} ${combo.name}`} title={textContent.deleteComboTitle || "Supprimer le combo"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>
@ -88,7 +90,9 @@ export function CombosList({
<div className="checklist-section-header combos-category-header"> <div className="checklist-section-header combos-category-header">
<div className="combos-category-title"> <div className="combos-category-title">
<button <button
className="task-planner-category-drag-handle" className="task-planner-category-drag-handle nds-icon-button"
data-size="xs"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: category })} onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: category })}
aria-label={`${textContent.reorderCategoryTitle || "Déplacer la catégorie"} ${category}`} aria-label={`${textContent.reorderCategoryTitle || "Déplacer la catégorie"} ${category}`}
@ -101,7 +105,8 @@ export function CombosList({
<div className="checklist-section-actions"> <div className="checklist-section-actions">
<span>{group.combos.length}</span> <span>{group.combos.length}</span>
<button <button
className="checklist-section-collapse-button" className="checklist-section-collapse-button nds-icon-button"
data-size="xs"
type="button" type="button"
onClick={() => onCategoryToggle(category)} onClick={() => onCategoryToggle(category)}
aria-expanded={!isCollapsed} aria-expanded={!isCollapsed}

View file

@ -83,7 +83,8 @@ export function InlineTextInput({ value, onCommit, className, ariaLabel, placeho
return ( return (
<input <input
{...inlineEdit.getInputProps({ {...inlineEdit.getInputProps({
className, className: `${className} nds-inline-input`.trim(),
type: "text",
placeholder, placeholder,
"aria-label": ariaLabel "aria-label": ariaLabel
})} })}

View file

@ -47,8 +47,8 @@ export function CountersModule({ toolboxId, moduleId, context, editing, textExch
{editing && ( {editing && (
<div className="module-add-panel"> <div className="module-add-panel">
<form className="inline-form counters-add-form" onSubmit={addCounter}> <form className="inline-form counters-add-form" onSubmit={addCounter}>
<input {...counterDraft.getFieldProps("label", { placeholder: textContent.labelPlaceholder || "Nom du compteur" })} /> <input {...counterDraft.getFieldProps("label", { className: "nds-input", "data-size": "sm", type: "text", placeholder: textContent.labelPlaceholder || "Nom du compteur" })} />
<button className="primary">{textContent.addButton || "Ajouter"}</button> <button className="nds-button" data-size="sm" data-variant="primary">{textContent.addButton || "Ajouter"}</button>
</form> </form>
</div> </div>
)} )}
@ -69,7 +69,9 @@ export function CountersModule({ toolboxId, moduleId, context, editing, textExch
return ( return (
<article className={className} key={counter.id} {...getItemProps({ itemId: counter.id, parentId: moduleId })}> <article className={className} key={counter.id} {...getItemProps({ itemId: counter.id, parentId: moduleId })}>
<button <button
className="counter-drag-handle" className="counter-drag-handle nds-icon-button"
data-size="sm"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => itemReorder.startDrag(event, counter.id)} onPointerDown={(event) => itemReorder.startDrag(event, counter.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${counter.label}`} aria-label={`${textContent.reorderTitle || "Déplacer"} ${counter.label}`}
@ -82,12 +84,12 @@ export function CountersModule({ toolboxId, moduleId, context, editing, textExch
<span>{counter.label}</span> <span>{counter.label}</span>
</div> </div>
<div className="counter-actions"> <div className="counter-actions">
<button type="button" onClick={() => updateCounter(counter.id, (item) => ({ ...item, value: item.value - 1 }))} aria-label={`${textContent.decrementLabel || "Décrémenter"} ${counter.label}`}>-</button> <button className="nds-icon-button" data-size="sm" type="button" onClick={() => updateCounter(counter.id, (item) => ({ ...item, value: item.value - 1 }))} aria-label={`${textContent.decrementLabel || "Décrémenter"} ${counter.label}`}>-</button>
<button type="button" onClick={() => updateCounter(counter.id, (item) => ({ ...item, value: item.value + 1 }))} aria-label={`${textContent.incrementLabel || "Incrémenter"} ${counter.label}`}>+</button> <button className="nds-icon-button" data-size="sm" type="button" onClick={() => updateCounter(counter.id, (item) => ({ ...item, value: item.value + 1 }))} aria-label={`${textContent.incrementLabel || "Incrémenter"} ${counter.label}`}>+</button>
<button type="button" onClick={() => updateCounter(counter.id, (item) => ({ ...item, value: 0 }))} aria-label={`${textContent.resetTitle || "Réinitialiser"} ${counter.label}`} title={textContent.resetTitle || "Réinitialiser"}> <button className="nds-icon-button" data-size="sm" type="button" onClick={() => updateCounter(counter.id, (item) => ({ ...item, value: 0 }))} aria-label={`${textContent.resetTitle || "Réinitialiser"} ${counter.label}`} title={textContent.resetTitle || "Réinitialiser"}>
<Icon name="rubber" /> <Icon name="rubber" />
</button> </button>
<button type="button" className="danger" onClick={() => save(data.counters.filter((item) => item.id !== counter.id))} aria-label={`${textContent.deleteTitle || "Supprimer"} ${counter.label}`} title={textContent.deleteTitle || "Supprimer"}> <button type="button" className="nds-icon-button" data-size="sm" data-variant="danger" onClick={() => save(data.counters.filter((item) => item.id !== counter.id))} aria-label={`${textContent.deleteTitle || "Supprimer"} ${counter.label}`} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>

View file

@ -7,13 +7,14 @@ const DEFAULT_WIDTHS = [2, 4, 8, 12];
export function DrawingControls({ textContent = {}, active, onToggleActive, eraseActive, onToggleErase, color, colors, onColorChange, width, widths = DEFAULT_WIDTHS, onWidthChange, onUndo, onClear, storageMode, onToggleStorageMode }) { export function DrawingControls({ textContent = {}, active, onToggleActive, eraseActive, onToggleErase, color, colors, onColorChange, width, widths = DEFAULT_WIDTHS, onWidthChange, onUndo, onClear, storageMode, onToggleStorageMode }) {
return ( return (
<div className={`drawing-controls-panel ${active ? "is-open" : ""}`} aria-label={textContent.drawingLabel || "Dessin"}> <div className={`drawing-controls-panel ${active ? "is-open" : ""}`} aria-label={textContent.drawingLabel || "Dessin"}>
<button className={`drawing-control-button ${active ? "active" : ""}`} type="button" aria-pressed={active} title={textContent.drawTitle || "Dessiner"} onClick={onToggleActive}> <button className={`drawing-control-button nds-icon-button ${active ? "active" : ""}`} data-size="sm" type="button" aria-pressed={active} title={textContent.drawTitle || "Dessiner"} onClick={onToggleActive}>
<Icon name="edit" /> <Icon name="edit" />
</button> </button>
{active && ( {active && (
<> <>
<button <button
className={`drawing-control-button ${eraseActive ? "active" : ""}`} className={`drawing-control-button nds-icon-button ${eraseActive ? "active" : ""}`}
data-size="sm"
type="button" type="button"
aria-pressed={eraseActive} aria-pressed={eraseActive}
title={textContent.eraseTitle || "Gommer"} title={textContent.eraseTitle || "Gommer"}
@ -33,15 +34,16 @@ export function DrawingControls({ textContent = {}, active, onToggleActive, eras
value={width} value={width}
onSelect={onWidthChange} onSelect={onWidthChange}
/> />
<button className="drawing-control-button" type="button" title={textContent.undoDrawingTitle || "Annuler le dernier trait"} onClick={onUndo}> <button className="drawing-control-button nds-icon-button" data-size="sm" type="button" title={textContent.undoDrawingTitle || "Annuler le dernier trait"} onClick={onUndo}>
<Icon name="back" /> <Icon name="back" />
</button> </button>
<button className="drawing-control-button danger" type="button" title={textContent.clearDrawingTitle || "Effacer les dessins"} onClick={onClear}> <button className="drawing-control-button nds-icon-button" data-size="sm" data-variant="danger" type="button" title={textContent.clearDrawingTitle || "Effacer les dessins"} onClick={onClear}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
{onToggleStorageMode && ( {onToggleStorageMode && (
<button <button
className={`module-scroll-button drawing-storage-switch ${storageMode === "permanent" ? "active" : ""}`} className={`module-scroll-button nds-switch drawing-storage-switch ${storageMode === "permanent" ? "active" : ""}`}
data-size="lg"
type="button" type="button"
aria-pressed={storageMode === "permanent"} aria-pressed={storageMode === "permanent"}
aria-label={storageMode === "permanent" ? textContent.permanentTitle || "Annotations permanentes" : textContent.temporaryTitle || "Annotations temporaires"} aria-label={storageMode === "permanent" ? textContent.permanentTitle || "Annotations permanentes" : textContent.temporaryTitle || "Annotations temporaires"}
@ -67,7 +69,7 @@ export function FormattingColorDropdown({ label, sections }) {
closeOnMouseLeave closeOnMouseLeave
preventMouseDown preventMouseDown
renderTrigger={({ toggle }) => ( renderTrigger={({ toggle }) => (
<button className="notepad-color-toggle is-rainbow" type="button" title={label} aria-label={label} onClick={toggle} /> <button className="notepad-color-toggle nds-swatch is-rainbow" data-variant="rainbow" type="button" title={label} aria-label={label} onClick={toggle} />
)} )}
> >
{({ close }) => ( {({ close }) => (
@ -79,7 +81,7 @@ export function FormattingColorDropdown({ label, sections }) {
{section.colors.map((color) => ( {section.colors.map((color) => (
<button <button
key={`${section.label}-${color}`} key={`${section.label}-${color}`}
className={`notepad-swatch ${section.value === color ? "active" : ""}`} className={`notepad-swatch nds-swatch ${section.value === color ? "active" : ""}`}
type="button" type="button"
title={`${section.label} ${color}`} title={`${section.label} ${color}`}
aria-label={`${section.label} ${color}`} aria-label={`${section.label} ${color}`}
@ -110,7 +112,8 @@ export function DrawingColorDropdown({ label, colors, value, onSelect }) {
preventMouseDown preventMouseDown
renderTrigger={({ toggle }) => ( renderTrigger={({ toggle }) => (
<button <button
className="notepad-color-toggle is-rainbow" className="notepad-color-toggle nds-swatch is-rainbow"
data-variant="rainbow"
type="button" type="button"
title={label} title={label}
aria-label={label} aria-label={label}
@ -124,7 +127,7 @@ export function DrawingColorDropdown({ label, colors, value, onSelect }) {
{colors.map((color) => ( {colors.map((color) => (
<button <button
key={color} key={color}
className={`notepad-swatch ${value === color ? "active" : ""}`} className={`notepad-swatch nds-swatch ${value === color ? "active" : ""}`}
type="button" type="button"
title={`${label} ${color}`} title={`${label} ${color}`}
aria-label={`${label} ${color}`} aria-label={`${label} ${color}`}
@ -151,7 +154,7 @@ export function DrawingWidthDropdown({ label, widths, value, onSelect }) {
closeOnMouseLeave closeOnMouseLeave
preventMouseDown preventMouseDown
renderTrigger={({ toggle }) => ( renderTrigger={({ toggle }) => (
<button className="drawing-width-toggle" type="button" title={label} aria-label={label} onClick={toggle}> <button className="drawing-width-toggle nds-icon-button" data-size="sm" type="button" title={label} aria-label={label} onClick={toggle}>
<span style={{ "--line-width": `${value}px` }} /> <span style={{ "--line-width": `${value}px` }} />
</button> </button>
)} )}
@ -161,7 +164,8 @@ export function DrawingWidthDropdown({ label, widths, value, onSelect }) {
{widths.map((lineWidth) => ( {widths.map((lineWidth) => (
<button <button
key={lineWidth} key={lineWidth}
className={`drawing-width-button ${value === lineWidth ? "active" : ""}`} className={`drawing-width-button nds-icon-button ${value === lineWidth ? "active" : ""}`}
data-size="sm"
type="button" type="button"
aria-pressed={value === lineWidth} aria-pressed={value === lineWidth}
title={`${label} ${lineWidth}`} title={`${label} ${lineWidth}`}

View file

@ -81,10 +81,10 @@ export function CharacteristicsEditor({ equipment, textContent, editing, onUpdat
options={TRAIT_ICONS.map((icon) => ({ icon, label: icon }))} options={TRAIT_ICONS.map((icon) => ({ icon, label: icon }))}
onChange={(icon) => setDraft((current) => ({ ...current, icon }))} onChange={(icon) => setDraft((current) => ({ ...current, icon }))}
/> />
<input value={draft.category} onChange={(event) => setDraft((current) => ({ ...current, category: event.target.value }))} placeholder={textContent.categoryPlaceholder || "Catégorie"} /> <input className="nds-input" data-size="sm" type="text" value={draft.category} onChange={(event) => setDraft((current) => ({ ...current, category: event.target.value }))} placeholder={textContent.categoryPlaceholder || "Catégorie"} />
<input value={draft.name} onChange={(event) => setDraft((current) => ({ ...current, name: event.target.value }))} placeholder={textContent.traitNamePlaceholder || "Nom"} /> <input className="nds-input" data-size="sm" type="text" value={draft.name} onChange={(event) => setDraft((current) => ({ ...current, name: event.target.value }))} placeholder={textContent.traitNamePlaceholder || "Nom"} />
<input value={draft.value} onChange={(event) => setDraft((current) => ({ ...current, value: event.target.value }))} placeholder={textContent.traitValuePlaceholder || "Valeur"} /> <input className="nds-input" data-size="sm" type="text" value={draft.value} onChange={(event) => setDraft((current) => ({ ...current, value: event.target.value }))} placeholder={textContent.traitValuePlaceholder || "Valeur"} />
<button>{textContent.addTraitButton || "Ajouter"}</button> <button className="nds-button" data-size="sm" data-variant="primary">{textContent.addTraitButton || "Ajouter"}</button>
</form> </form>
)} )}
<TraitCategoryList <TraitCategoryList
@ -145,12 +145,12 @@ function TraitCategoryList({ traits, categories, collapsedCategories, reorder, t
return ( return (
<section className={className} key={category} {...reorder.getGroupProps({ groupId: category })}> <section className={className} key={category} {...reorder.getGroupProps({ groupId: category })}>
<div className="equipment-planner-trait-category-header"> <div className="equipment-planner-trait-category-header">
<button type="button" className="task-planner-category-drag-handle" 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="ghost" onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: category })} title={textContent.reorderCategoryTitle || "Déplacer la catégorie"}>
<Icon name="drag" /> <Icon name="drag" />
</button> </button>
<strong>{category}</strong> <strong>{category}</strong>
<span>{categoryTraits.length}</span> <span>{categoryTraits.length}</span>
<button type="button" className="checklist-section-collapse-button" onClick={() => onToggleCategory(category)} aria-expanded={!collapsed}> <button type="button" className="checklist-section-collapse-button nds-icon-button" data-size="xs" onClick={() => onToggleCategory(category)} aria-expanded={!collapsed}>
<Icon name={collapsed ? "chevron-down" : "chevron-up"} /> <Icon name={collapsed ? "chevron-down" : "chevron-up"} />
</button> </button>
</div> </div>
@ -182,7 +182,7 @@ function EquipmentTraitRow({ trait, className, itemProps, editing, textContent,
<li className={className} {...itemProps}> <li className={className} {...itemProps}>
{editing ? ( {editing ? (
<> <>
<button type="button" className="task-planner-drag-handle" onPointerDown={onStartDrag} title={textContent.reorderTraitTitle || "Déplacer"}> <button type="button" className="task-planner-drag-handle nds-icon-button" data-size="xs" data-variant="ghost" onPointerDown={onStartDrag} title={textContent.reorderTraitTitle || "Déplacer"}>
<Icon name="drag" /> <Icon name="drag" />
</button> </button>
<IconChoiceDropdown <IconChoiceDropdown
@ -191,10 +191,10 @@ function EquipmentTraitRow({ trait, className, itemProps, editing, textContent,
options={TRAIT_ICONS.map((icon) => ({ icon, label: icon }))} options={TRAIT_ICONS.map((icon) => ({ icon, label: icon }))}
onChange={(icon) => onUpdateTrait(trait.id, (current) => ({ ...current, icon }))} onChange={(icon) => onUpdateTrait(trait.id, (current) => ({ ...current, icon }))}
/> />
<input {...categoryEdit.getInputProps({ placeholder: textContent.categoryPlaceholder || "Catégorie" })} /> <input {...categoryEdit.getInputProps({ className: "nds-inline-input", type: "text", placeholder: textContent.categoryPlaceholder || "Catégorie" })} />
<input {...nameEdit.getInputProps({ placeholder: textContent.traitNamePlaceholder || "Nom" })} /> <input {...nameEdit.getInputProps({ className: "nds-inline-input", type: "text", placeholder: textContent.traitNamePlaceholder || "Nom" })} />
<input {...valueEdit.getInputProps({ placeholder: textContent.traitValuePlaceholder || "Valeur" })} /> <input {...valueEdit.getInputProps({ className: "nds-inline-input", type: "text", placeholder: textContent.traitValuePlaceholder || "Valeur" })} />
<button type="button" className="task-planner-delete-button danger" onClick={() => onUpdateTrait(trait.id, () => ({ ...trait, name: "" }))} title={textContent.deleteTraitTitle || "Supprimer"}> <button type="button" className="task-planner-delete-button nds-icon-button" data-size="xs" data-variant="danger" onClick={() => onUpdateTrait(trait.id, () => ({ ...trait, name: "" }))} title={textContent.deleteTraitTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</> </>

View file

@ -30,7 +30,9 @@ export function EquipmentItem({ equipment, reorder, textContent, onUpdate, onDel
<li className={className} {...reorder.getItemProps({ itemId: equipment.id })}> <li className={className} {...reorder.getItemProps({ itemId: equipment.id })}>
<div className="equipment-planner-line"> <div className="equipment-planner-line">
<button <button
className="task-planner-drag-handle" className="task-planner-drag-handle nds-icon-button"
data-size="sm"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, equipment.id)} onPointerDown={(event) => reorder.itemReorder.startDrag(event, equipment.id)}
aria-label={`${textContent.reorderEquipmentTitle || "Déplacer"} ${equipment.name}`} aria-label={`${textContent.reorderEquipmentTitle || "Déplacer"} ${equipment.name}`}
@ -42,9 +44,9 @@ export function EquipmentItem({ equipment, reorder, textContent, onUpdate, onDel
<span className="equipment-planner-type-icon" aria-hidden="true"> <span className="equipment-planner-type-icon" aria-hidden="true">
<Icon name={equipment.icon || "shield"} /> <Icon name={equipment.icon || "shield"} />
</span> </span>
<strong {...nameEdit.getContentEditableProps({ className: "equipment-planner-name", title: textContent.renameEquipmentTitle || "Renommer" })}>{equipment.name}</strong> <strong {...nameEdit.getContentEditableProps({ className: "equipment-planner-name nds-inline-input", title: textContent.renameEquipmentTitle || "Renommer" })}>{equipment.name}</strong>
</div> </div>
<label className="equipment-planner-include-switch"> <label className="equipment-planner-include-switch nds-native-switch">
<input <input
type="checkbox" type="checkbox"
checked={equipment.active} checked={equipment.active}
@ -55,21 +57,22 @@ export function EquipmentItem({ equipment, reorder, textContent, onUpdate, onDel
</label> </label>
<button <button
type="button" type="button"
className={`task-planner-settings-button equipment-planner-row-edit-button ${detailEditing ? "active" : ""}`} className={`task-planner-settings-button equipment-planner-row-edit-button nds-icon-button ${detailEditing ? "active" : ""}`}
data-size="sm"
onClick={() => setDetailEditing((value) => !value)} onClick={() => setDetailEditing((value) => !value)}
aria-pressed={detailEditing} aria-pressed={detailEditing}
title={detailEditing ? textContent.viewModeTitle || "Mode affichage" : textContent.editModeTitle || "Mode édition"} title={detailEditing ? textContent.viewModeTitle || "Mode affichage" : textContent.editModeTitle || "Mode édition"}
> >
<Icon name="edit" /> <Icon name="edit" />
</button> </button>
<button type="button" className="task-planner-delete-button danger" onClick={onDelete} aria-label={`${textContent.deleteEquipmentTitle || "Supprimer"} ${equipment.name}`} title={textContent.deleteEquipmentTitle || "Supprimer"}> <button type="button" className="task-planner-delete-button nds-icon-button" data-size="sm" data-variant="danger" onClick={onDelete} aria-label={`${textContent.deleteEquipmentTitle || "Supprimer"} ${equipment.name}`} title={textContent.deleteEquipmentTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>
<div className="equipment-planner-details"> <div className="equipment-planner-details">
{detailEditing ? ( {detailEditing ? (
<> <>
<div className="equipment-planner-tabs" role="tablist" aria-label={textContent.tabsLabel || "Ajout de caractéristiques"}> <div className="equipment-planner-tabs nds-radio-button-group" data-size="sm" role="tablist" aria-label={textContent.tabsLabel || "Ajout de caractéristiques"}>
{DETAIL_TABS.map((tab) => ( {DETAIL_TABS.map((tab) => (
<button <button
key={tab} key={tab}
@ -91,7 +94,7 @@ export function EquipmentItem({ equipment, reorder, textContent, onUpdate, onDel
<section className="equipment-planner-craft-tab"> <section className="equipment-planner-craft-tab">
<label className="equipment-planner-field"> <label className="equipment-planner-field">
<span>{textContent.obtainLabel || "Obtention"}</span> <span>{textContent.obtainLabel || "Obtention"}</span>
<textarea {...obtainEdit.getInputProps({ placeholder: textContent.obtainPlaceholder || "Comment obtenir ou créer cet équipement" })} /> <textarea {...obtainEdit.getInputProps({ className: "nds-textarea", "data-size": "sm", placeholder: textContent.obtainPlaceholder || "Comment obtenir ou créer cet équipement" })} />
</label> </label>
<MaterialsEditor equipment={equipment} textContent={textContent} editing onUpdate={onUpdate} /> <MaterialsEditor equipment={equipment} textContent={textContent} editing onUpdate={onUpdate} />
</section> </section>

View file

@ -23,9 +23,9 @@ export function MaterialsEditor({ equipment, textContent, editing, onUpdate }) {
<h4>{textContent.materialsTitle || "Matériaux"}</h4> <h4>{textContent.materialsTitle || "Matériaux"}</h4>
{editing && ( {editing && (
<form className="inline-form equipment-planner-material-form" onSubmit={addMaterial}> <form className="inline-form equipment-planner-material-form" onSubmit={addMaterial}>
<input value={draft.name} onChange={(event) => setDraft((current) => ({ ...current, name: event.target.value }))} placeholder={textContent.materialNamePlaceholder || "Matériau"} /> <input className="nds-input" data-size="sm" type="text" value={draft.name} onChange={(event) => setDraft((current) => ({ ...current, name: event.target.value }))} placeholder={textContent.materialNamePlaceholder || "Matériau"} />
<input type="number" min="1" value={draft.qty} onChange={(event) => setDraft((current) => ({ ...current, qty: event.target.value }))} aria-label={textContent.materialQtyLabel || "Quantité"} /> <input className="nds-input" data-size="sm" type="number" min="1" value={draft.qty} onChange={(event) => setDraft((current) => ({ ...current, qty: event.target.value }))} aria-label={textContent.materialQtyLabel || "Quantité"} />
<button>{textContent.addMaterialButton || "Ajouter"}</button> <button className="nds-button" data-size="sm" data-variant="primary">{textContent.addMaterialButton || "Ajouter"}</button>
</form> </form>
)} )}
<ul className="equipment-planner-materials"> <ul className="equipment-planner-materials">
@ -54,9 +54,9 @@ function MaterialRow({ material, editing, textContent, onUpdate }) {
<li> <li>
{editing ? ( {editing ? (
<> <>
<input {...nameEdit.getInputProps()} /> <input {...nameEdit.getInputProps({ className: "nds-inline-input", type: "text" })} />
<input type="number" min="1" value={material.qty} onChange={(event) => onUpdate((current) => ({ ...current, materials: current.materials.map((item) => item.id === material.id ? { ...item, qty: Math.max(1, Number.parseInt(event.target.value, 10) || 1) } : item) }))} /> <input className="nds-input" data-size="xs" type="number" min="1" value={material.qty} onChange={(event) => onUpdate((current) => ({ ...current, materials: current.materials.map((item) => item.id === material.id ? { ...item, qty: Math.max(1, Number.parseInt(event.target.value, 10) || 1) } : item) }))} />
<button type="button" className="task-planner-delete-button danger" onClick={() => onUpdate((current) => ({ ...current, materials: current.materials.filter((item) => item.id !== material.id) }))} title={textContent.deleteMaterialTitle || "Supprimer"}> <button type="button" className="task-planner-delete-button nds-icon-button" data-size="xs" data-variant="danger" onClick={() => onUpdate((current) => ({ ...current, materials: current.materials.filter((item) => item.id !== material.id) }))} title={textContent.deleteMaterialTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</> </>

View file

@ -109,8 +109,8 @@ export function EquipmentPlannerModule({ toolboxId, moduleId, context, editing,
options={EQUIPMENT_ICONS} options={EQUIPMENT_ICONS}
onChange={setEquipmentIcon} onChange={setEquipmentIcon}
/> />
<input value={equipmentName} onChange={(event) => setEquipmentName(event.target.value)} placeholder={textContent.equipmentPlaceholder || "Nouvel équipement"} /> <input className="nds-input" data-size="sm" type="text" value={equipmentName} onChange={(event) => setEquipmentName(event.target.value)} placeholder={textContent.equipmentPlaceholder || "Nouvel équipement"} />
<button className="primary">{textContent.addEquipmentButton || "Ajouter"}</button> <button className="nds-button" data-size="sm" data-variant="primary">{textContent.addEquipmentButton || "Ajouter"}</button>
</form> </form>
</div> </div>
)} )}

View file

@ -21,7 +21,9 @@ export function IconChoiceDropdown({ className = "", label, value, options, onCh
columns={Math.min(options.length, 5)} columns={Math.min(options.length, 5)}
renderTrigger={({ open, toggle }) => ( renderTrigger={({ open, toggle }) => (
<button <button
className={`equipment-planner-picker-trigger ${open ? "active" : ""}`} className={`equipment-planner-picker-trigger nds-select-trigger ${open ? "active" : ""}`}
data-size="sm"
data-variant="icon-picker"
type="button" type="button"
onClick={toggle} onClick={toggle}
aria-label={label} aria-label={label}
@ -29,7 +31,6 @@ export function IconChoiceDropdown({ className = "", label, value, options, onCh
title={selected.label} title={selected.label}
> >
<Icon name={selected.icon} /> <Icon name={selected.icon} />
<Icon name="dropdown" />
</button> </button>
)} )}
> >
@ -63,9 +64,8 @@ export function ShapeChoiceDropdown({ label, value, onChange }) {
label={label} label={label}
columns={SOCKET_SHAPES.length} columns={SOCKET_SHAPES.length}
renderTrigger={({ open, toggle }) => ( renderTrigger={({ open, toggle }) => (
<button className={`equipment-planner-picker-trigger ${open ? "active" : ""}`} type="button" onClick={toggle} aria-label={label} aria-expanded={open} title={label}> <button className={`equipment-planner-picker-trigger nds-select-trigger ${open ? "active" : ""}`} data-size="sm" data-variant="icon-picker" type="button" onClick={toggle} aria-label={label} aria-expanded={open} title={label}>
<SocketShapeIcon shape={value} /> <SocketShapeIcon shape={value} />
<Icon name="dropdown" />
</button> </button>
)} )}
> >
@ -98,9 +98,8 @@ export function ColorChoiceDropdown({ label, value, onChange }) {
label={label} label={label}
columns={6} columns={6}
renderTrigger={({ open, toggle }) => ( renderTrigger={({ open, toggle }) => (
<button className={`equipment-planner-picker-trigger ${open ? "active" : ""}`} type="button" onClick={toggle} aria-label={label} aria-expanded={open} title={value}> <button className={`equipment-planner-picker-trigger nds-select-trigger ${open ? "active" : ""}`} data-size="sm" data-variant="icon-picker" type="button" onClick={toggle} aria-label={label} aria-expanded={open} title={value}>
<span className={`equipment-planner-color-swatch color-${value}`} aria-hidden="true" /> <span className={`equipment-planner-color-swatch color-${value}`} aria-hidden="true" />
<Icon name="dropdown" />
</button> </button>
)} )}
> >

View file

@ -53,10 +53,10 @@ export function SocketItemsEditor({ equipment, textContent, editing, onUpdate })
<h4>{textContent.socketItemsTitle || "Objets sertis"}</h4> <h4>{textContent.socketItemsTitle || "Objets sertis"}</h4>
{editing && ( {editing && (
<form className="inline-form equipment-planner-socket-form" onSubmit={addSocketItem}> <form className="inline-form equipment-planner-socket-form" onSubmit={addSocketItem}>
<input value={draft.name} onChange={(event) => setDraft((current) => ({ ...current, name: event.target.value }))} placeholder={textContent.socketNamePlaceholder || "Joyau, materia..."} /> <input className="nds-input" data-size="sm" type="text" value={draft.name} onChange={(event) => setDraft((current) => ({ ...current, name: event.target.value }))} placeholder={textContent.socketNamePlaceholder || "Joyau, materia..."} />
<ShapeChoiceDropdown label={textContent.socketShapeLabel || "Forme"} value={draft.shape} onChange={(shape) => setDraft((current) => ({ ...current, shape }))} /> <ShapeChoiceDropdown label={textContent.socketShapeLabel || "Forme"} value={draft.shape} onChange={(shape) => setDraft((current) => ({ ...current, shape }))} />
<ColorChoiceDropdown label={textContent.socketColorLabel || "Couleur"} value={draft.color} onChange={(color) => setDraft((current) => ({ ...current, color }))} /> <ColorChoiceDropdown label={textContent.socketColorLabel || "Couleur"} value={draft.color} onChange={(color) => setDraft((current) => ({ ...current, color }))} />
<button>{textContent.addSocketButton || "Ajouter"}</button> <button className="nds-button" data-size="sm" data-variant="primary">{textContent.addSocketButton || "Ajouter"}</button>
</form> </form>
)} )}
<div className="equipment-planner-socket-grid"> <div className="equipment-planner-socket-grid">
@ -104,8 +104,8 @@ function SocketItemEditor({ equipment, socketItem, textContent, editing, onUpdat
<SocketShapeIcon shape={socketItem.shape} color={socketItem.color} /> <SocketShapeIcon shape={socketItem.shape} color={socketItem.color} />
{editing ? ( {editing ? (
<> <>
<input {...nameEdit.getInputProps()} /> <input {...nameEdit.getInputProps({ className: "nds-inline-input", type: "text" })} />
<button type="button" className="task-planner-delete-button danger" onClick={onDelete} title={textContent.deleteSocketTitle || "Supprimer"}> <button type="button" className="task-planner-delete-button nds-icon-button" data-size="xs" data-variant="danger" onClick={onDelete} title={textContent.deleteSocketTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</> </>
@ -119,16 +119,16 @@ function SocketItemEditor({ equipment, socketItem, textContent, editing, onUpdat
<ShapeChoiceDropdown label={textContent.socketShapeLabel || "Forme"} value={socketItem.shape} onChange={(shape) => onUpdate((current) => ({ ...current, shape }))} /> <ShapeChoiceDropdown label={textContent.socketShapeLabel || "Forme"} value={socketItem.shape} onChange={(shape) => onUpdate((current) => ({ ...current, shape }))} />
<ColorChoiceDropdown label={textContent.socketColorLabel || "Couleur"} value={socketItem.color} onChange={(color) => onUpdate((current) => ({ ...current, color }))} /> <ColorChoiceDropdown label={textContent.socketColorLabel || "Couleur"} value={socketItem.color} onChange={(color) => onUpdate((current) => ({ ...current, color }))} />
{equipment.socketItems.length > 1 && ( {equipment.socketItems.length > 1 && (
<select defaultValue="" onChange={(event) => { onAddLink(event.target.value); event.target.value = ""; }} aria-label={textContent.addSocketLinkLabel || "Lier à"}> <select className="nds-select" data-size="xs" defaultValue="" onChange={(event) => { onAddLink(event.target.value); event.target.value = ""; }} aria-label={textContent.addSocketLinkLabel || "Lier à"}>
<option value="">{textContent.addSocketLinkLabel || "Lier à"}</option> <option value="">{textContent.addSocketLinkLabel || "Lier à"}</option>
{equipment.socketItems.filter((item) => item.id !== socketItem.id).map((item) => <option key={item.id} value={item.id}>{item.name}</option>)} {equipment.socketItems.filter((item) => item.id !== socketItem.id).map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
</select> </select>
)} )}
</div> </div>
<form className="inline-form equipment-planner-bonus-form" onSubmit={addBonus}> <form className="inline-form equipment-planner-bonus-form" onSubmit={addBonus}>
<input value={bonusDraft.name} onChange={(event) => setBonusDraft((current) => ({ ...current, name: event.target.value }))} placeholder={textContent.bonusNamePlaceholder || "Bonus"} /> <input className="nds-input" data-size="sm" type="text" value={bonusDraft.name} onChange={(event) => setBonusDraft((current) => ({ ...current, name: event.target.value }))} placeholder={textContent.bonusNamePlaceholder || "Bonus"} />
<input value={bonusDraft.value} onChange={(event) => setBonusDraft((current) => ({ ...current, value: event.target.value }))} placeholder={textContent.traitValuePlaceholder || "Valeur"} /> <input className="nds-input" data-size="sm" type="text" value={bonusDraft.value} onChange={(event) => setBonusDraft((current) => ({ ...current, value: event.target.value }))} placeholder={textContent.traitValuePlaceholder || "Valeur"} />
<button aria-label={textContent.addBonusButton || "Ajouter"} title={textContent.addBonusButton || "Ajouter"}>+</button> <button className="nds-button" data-size="sm" data-variant="primary" aria-label={textContent.addBonusButton || "Ajouter"} title={textContent.addBonusButton || "Ajouter"}>+</button>
</form> </form>
</> </>
)} )}
@ -138,7 +138,7 @@ function SocketItemEditor({ equipment, socketItem, textContent, editing, onUpdat
<span>{bonus.category ? `${bonus.category} · ` : ""}{bonus.name}</span> <span>{bonus.category ? `${bonus.category} · ` : ""}{bonus.name}</span>
<b>{formatValue(bonus.value)}</b> <b>{formatValue(bonus.value)}</b>
{editing && ( {editing && (
<button type="button" className="task-planner-delete-button danger" onClick={() => onUpdate((current) => ({ ...current, bonuses: current.bonuses.filter((item) => item.id !== bonus.id) }))} title={textContent.deleteBonusTitle || "Supprimer"}> <button type="button" className="task-planner-delete-button nds-icon-button" data-size="xs" data-variant="danger" onClick={() => onUpdate((current) => ({ ...current, bonuses: current.bonuses.filter((item) => item.id !== bonus.id) }))} title={textContent.deleteBonusTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
)} )}

View file

@ -49,17 +49,19 @@ export function GoalTreeBranch({ nodes, parentId, selectedNodeId, editing, reord
<article className={`goal-tree-node-card ${editing ? "is-editing" : "is-viewing"} ${hasNodeActions ? "has-actions" : ""}`} data-diagram-node={node.id} onClick={selectFromCard}> <article className={`goal-tree-node-card ${editing ? "is-editing" : "is-viewing"} ${hasNodeActions ? "has-actions" : ""}`} data-diagram-node={node.id} onClick={selectFromCard}>
{!editing && ( {!editing && (
<div className="goal-tree-node-hover-actions" aria-label={textContent.cardActionsLabel || "Actions de l'objectif"}> <div className="goal-tree-node-hover-actions" aria-label={textContent.cardActionsLabel || "Actions de l'objectif"}>
<button type="button" onClick={() => onEditNode(node.id)} aria-label={`${textContent.editTitle || "Modifier"} ${node.title}`} title={textContent.editTitle || "Modifier"}> <button className="nds-icon-button" data-size="tiny" type="button" onClick={() => onEditNode(node.id)} aria-label={`${textContent.editTitle || "Modifier"} ${node.title}`} title={textContent.editTitle || "Modifier"}>
<Icon name="edit" /> <Icon name="edit" />
</button> </button>
<button className="danger" type="button" onClick={() => onDeleteNode(node.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${node.title}`} title={textContent.deleteTitle || "Supprimer"}> <button className="nds-icon-button" data-size="tiny" data-variant="danger" type="button" onClick={() => onDeleteNode(node.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${node.title}`} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>
)} )}
{editing && ( {editing && (
<button <button
className="goal-tree-drag-handle" className="goal-tree-drag-handle nds-icon-button"
data-size="micro"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, node.id)} onPointerDown={(event) => reorder.itemReorder.startDrag(event, node.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${node.title}`} aria-label={`${textContent.reorderTitle || "Déplacer"} ${node.title}`}
@ -109,13 +111,14 @@ export function GoalTreeBranch({ nodes, parentId, selectedNodeId, editing, reord
{hasNodeActions && ( {hasNodeActions && (
<div className="goal-tree-node-actions"> <div className="goal-tree-node-actions">
{editing && ( {editing && (
<button type="button" onClick={() => onAdd(node.id)} aria-label={`${textContent.addChildTitle || "Ajouter une sous-étape"} ${node.title}`} title={textContent.addChildTitle || "Ajouter une sous-étape"}> <button className="nds-icon-button" data-size="micro" type="button" onClick={() => onAdd(node.id)} aria-label={`${textContent.addChildTitle || "Ajouter une sous-étape"} ${node.title}`} title={textContent.addChildTitle || "Ajouter une sous-étape"}>
<Icon name="add" /> <Icon name="add" />
</button> </button>
)} )}
{hasBranchToggle && ( {hasBranchToggle && (
<button <button
className="goal-tree-branch-toggle" className="goal-tree-branch-toggle nds-icon-button"
data-size="micro"
type="button" type="button"
onClick={() => onToggleBranch(node.id)} onClick={() => onToggleBranch(node.id)}
aria-expanded={!branchCollapsed} aria-expanded={!branchCollapsed}
@ -126,7 +129,7 @@ export function GoalTreeBranch({ nodes, parentId, selectedNodeId, editing, reord
</button> </button>
)} )}
{editing && ( {editing && (
<button className="danger" type="button" onClick={() => onDeleteNode(node.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${node.title}`} title={textContent.deleteTitle || "Supprimer"}> <button className="nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={() => onDeleteNode(node.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${node.title}`} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
)} )}

View file

@ -94,11 +94,11 @@ export function GoalTreeEditor({
)} )}
<div className="goal-tree-form-actions"> <div className="goal-tree-form-actions">
<button type="button" onClick={onClose}> <button className="nds-button" data-size="sm" type="button" onClick={onClose}>
<Icon name="close" /> <Icon name="close" />
<span>{textContent.closeButton || "Fermer"}</span> <span>{textContent.closeButton || "Fermer"}</span>
</button> </button>
<button className="danger" type="button" onClick={() => onDeleteNode(selectedNode.id)}> <button className="nds-button" data-size="sm" data-variant="danger" type="button" onClick={() => onDeleteNode(selectedNode.id)}>
<Icon name="trash" /> <Icon name="trash" />
<span>{textContent.deleteButton || "Supprimer la branche"}</span> <span>{textContent.deleteButton || "Supprimer la branche"}</span>
</button> </button>
@ -113,7 +113,7 @@ function GoalTreeBasePanel({ selectedNode, selectedNodeIsStep, textContent, onSe
<div className="goal-tree-form-panel"> <div className="goal-tree-form-panel">
<label> <label>
<span>{textContent.kindLabel || "Type de l'objectif"}</span> <span>{textContent.kindLabel || "Type de l'objectif"}</span>
<select value={selectedNode.kind} onChange={(event) => onSetKind(selectedNode.id, event.target.value)}> <select className="nds-select" data-size="sm" value={selectedNode.kind} onChange={(event) => onSetKind(selectedNode.id, event.target.value)}>
{GOAL_TREE_KINDS.map((kind) => <option key={kind} value={kind}>{textContent[`${kind}Kind`] || KIND_LABELS[kind]}</option>)} {GOAL_TREE_KINDS.map((kind) => <option key={kind} value={kind}>{textContent[`${kind}Kind`] || KIND_LABELS[kind]}</option>)}
</select> </select>
</label> </label>
@ -139,7 +139,7 @@ function GoalTreeBasePanel({ selectedNode, selectedNodeIsStep, textContent, onSe
{GOAL_TREE_COLORS.map((color) => ( {GOAL_TREE_COLORS.map((color) => (
<button <button
key={color} key={color}
className={`goal-tree-color-swatch ${selectedNode.color === color ? "active" : ""}`} className={`goal-tree-color-swatch nds-swatch ${selectedNode.color === color ? "active" : ""}`}
type="button" type="button"
onClick={() => onUpdateNode(selectedNode.id, (node) => ({ ...node, color }))} onClick={() => onUpdateNode(selectedNode.id, (node) => ({ ...node, color }))}
aria-pressed={selectedNode.color === color} aria-pressed={selectedNode.color === color}
@ -164,20 +164,20 @@ function GoalTreeAdvancedPanel({ selectedNode, selectedNodeIsStep, selectedProgr
<div className="goal-tree-form-panel"> <div className="goal-tree-form-panel">
<label> <label>
<span>{textContent.resetTypeLabel || "Type de reset"}</span> <span>{textContent.resetTypeLabel || "Type de reset"}</span>
<select value={selectedNode.resetType} onChange={(event) => onUpdateNode(selectedNode.id, (node) => ({ ...node, resetType: event.target.value }))}> <select className="nds-select" data-size="sm" value={selectedNode.resetType} onChange={(event) => onUpdateNode(selectedNode.id, (node) => ({ ...node, resetType: event.target.value }))}>
{GOAL_TREE_RESET_TYPES.map((resetType) => <option key={resetType} value={resetType}>{textContent[`${resetType}Reset`] || RESET_LABELS[resetType]}</option>)} {GOAL_TREE_RESET_TYPES.map((resetType) => <option key={resetType} value={resetType}>{textContent[`${resetType}Reset`] || RESET_LABELS[resetType]}</option>)}
</select> </select>
</label> </label>
{selectedNode.resetType !== "none" && ( {selectedNode.resetType !== "none" && (
<label> <label>
<span>{textContent.resetTimeLabel || "Heure de reset"}</span> <span>{textContent.resetTimeLabel || "Heure de reset"}</span>
<input type="time" value={selectedNode.resetTime || "00:00"} onChange={(event) => onUpdateNode(selectedNode.id, (node) => ({ ...node, resetTime: event.target.value || "00:00" }))} /> <input className="nds-time" data-size="sm" type="time" value={selectedNode.resetTime || "00:00"} onChange={(event) => onUpdateNode(selectedNode.id, (node) => ({ ...node, resetTime: event.target.value || "00:00" }))} />
</label> </label>
)} )}
{selectedNode.resetType === "weekly" && ( {selectedNode.resetType === "weekly" && (
<label> <label>
<span>{textContent.weeklyResetDayLabel || "Jour de reset"}</span> <span>{textContent.weeklyResetDayLabel || "Jour de reset"}</span>
<select value={selectedNode.weeklyResetDay ?? 1} onChange={(event) => onUpdateNode(selectedNode.id, (node) => ({ ...node, weeklyResetDay: Number(event.target.value) }))}> <select className="nds-select" data-size="sm" value={selectedNode.weeklyResetDay ?? 1} onChange={(event) => onUpdateNode(selectedNode.id, (node) => ({ ...node, weeklyResetDay: Number(event.target.value) }))}>
{GOAL_TREE_WEEK_DAYS.map((day) => <option key={day.value} value={day.value}>{day.label}</option>)} {GOAL_TREE_WEEK_DAYS.map((day) => <option key={day.value} value={day.value}>{day.label}</option>)}
</select> </select>
</label> </label>
@ -199,7 +199,8 @@ function GoalTreeAdvancedPanel({ selectedNode, selectedNodeIsStep, selectedProgr
</label> </label>
<div className="goal-tree-form-section-title">{textContent.progressSectionTitle || "Progression"}</div> <div className="goal-tree-form-section-title">{textContent.progressSectionTitle || "Progression"}</div>
<button <button
className={`tool-split-scroll-toggle goal-tree-progress-mode-toggle ${selectedProgressMode === "auto" ? "active" : ""}`} className={`tool-split-scroll-toggle goal-tree-progress-mode-toggle nds-switch ${selectedProgressMode === "auto" ? "active" : ""}`}
data-size="lg"
type="button" type="button"
onClick={() => onUpdateNode(selectedNode.id, (node) => ({ ...node, progressMode: selectedProgressMode === "auto" ? "manual" : "auto" }))} onClick={() => onUpdateNode(selectedNode.id, (node) => ({ ...node, progressMode: selectedProgressMode === "auto" ? "manual" : "auto" }))}
aria-pressed={selectedProgressMode === "auto"} aria-pressed={selectedProgressMode === "auto"}

View file

@ -22,13 +22,13 @@ export function GoalTreePrerequisitePicker({ nodes, node, textContent, onToggle
<div className="goal-tree-prerequisite-list themed-scrollbar"> <div className="goal-tree-prerequisite-list themed-scrollbar">
{primaryParent && ( {primaryParent && (
<label className="goal-tree-prerequisite-item is-primary"> <label className="goal-tree-prerequisite-item is-primary">
<input type="checkbox" checked disabled readOnly /> <input className="nds-checkbox" data-size="xs" type="checkbox" checked disabled readOnly />
<span>{primaryParent.title}</span> <span>{primaryParent.title}</span>
</label> </label>
)} )}
{options.map((candidate) => ( {options.map((candidate) => (
<label className="goal-tree-prerequisite-item" key={candidate.id}> <label className="goal-tree-prerequisite-item" key={candidate.id}>
<input type="checkbox" checked={parentIds.has(candidate.id)} onChange={() => onToggle(candidate.id)} /> <input className="nds-checkbox" data-size="xs" type="checkbox" checked={parentIds.has(candidate.id)} onChange={() => onToggle(candidate.id)} />
<span>{candidate.title}</span> <span>{candidate.title}</span>
</label> </label>
))} ))}
@ -41,6 +41,7 @@ export function GoalTreeTitleInput({ value, onChange }) {
const [draft, setDraft] = useState(value || ""); const [draft, setDraft] = useState(value || "");
return ( return (
<input <input
type="text"
value={draft} value={draft}
onChange={(event) => { onChange={(event) => {
const title = event.target.value; const title = event.target.value;
@ -54,7 +55,7 @@ export function GoalTreeTitleInput({ value, onChange }) {
export function GoalTreeFormulaInput({ value, onChange, hasParent, textContent, placeholder, title, className = "", "aria-label": ariaLabel }) { export function GoalTreeFormulaInput({ value, onChange, hasParent, textContent, placeholder, title, className = "", "aria-label": ariaLabel }) {
const normalizedValue = String(value ?? ""); const normalizedValue = String(value ?? "");
if (!hasParent) { if (!hasParent) {
return <input aria-label={ariaLabel} className={className} inputMode="decimal" onChange={(event) => onChange(event.target.value.replaceAll(",", "."))} placeholder={placeholder} title={title} value={normalizedValue} />; return <input type="text" aria-label={ariaLabel} className={`${className} nds-inline-input`.trim()} inputMode="decimal" onChange={(event) => onChange(event.target.value.replaceAll(",", "."))} placeholder={placeholder} title={title} value={normalizedValue} />;
} }
return ( return (
@ -87,7 +88,7 @@ export function GoalTreeContentEditor({ node, textContent, onAdd, onUpdate, onDe
<div className="goal-tree-form-section-title">{textContent.contentSectionTitle || "Contenu"}</div> <div className="goal-tree-form-section-title">{textContent.contentSectionTitle || "Contenu"}</div>
{(node.items || []).map((item) => ( {(node.items || []).map((item) => (
<div className={`goal-tree-content-editor-row is-${node.kind}`} key={item.id}> <div className={`goal-tree-content-editor-row is-${node.kind}`} key={item.id}>
<input className="goal-tree-content-label-input" value={item.label} placeholder={textContent.itemLabelPlaceholder || "Élément"} onChange={(event) => onUpdate(item.id, (current) => ({ ...current, label: event.target.value }))} aria-label={textContent.itemLabelLabel || "Libellé"} /> <input className="goal-tree-content-label-input nds-input" data-size="sm" type="text" value={item.label} placeholder={textContent.itemLabelPlaceholder || "Élément"} onChange={(event) => onUpdate(item.id, (current) => ({ ...current, label: event.target.value }))} aria-label={textContent.itemLabelLabel || "Libellé"} />
{node.kind === "checklist" && ( {node.kind === "checklist" && (
<GoalTreeFormulaInput <GoalTreeFormulaInput
aria-label={textContent.itemTotalLabel || "Total"} aria-label={textContent.itemTotalLabel || "Total"}
@ -100,12 +101,12 @@ export function GoalTreeContentEditor({ node, textContent, onAdd, onUpdate, onDe
title={textContent.itemTotalHelp || "Saisissez un nombre ou une formule avec base pour référencer la cible du parent."} title={textContent.itemTotalHelp || "Saisissez un nombre ou une formule avec base pour référencer la cible du parent."}
/> />
)} )}
<button className="danger" type="button" onClick={() => onDelete(item.id)} aria-label={textContent.deleteTitle || "Supprimer"} title={textContent.deleteTitle || "Supprimer"}> <button className="nds-icon-button" data-size="xs" data-variant="danger" type="button" onClick={() => onDelete(item.id)} aria-label={textContent.deleteTitle || "Supprimer"} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>
))} ))}
<button className="goal-tree-content-add-button" type="button" onClick={onAdd}> <button className="goal-tree-content-add-button nds-button" data-size="xs" data-variant="dashed" type="button" onClick={onAdd}>
<Icon name="add" /> <Icon name="add" />
<span>{textContent.addContentItemButton || "Ajouter une ligne"}</span> <span>{textContent.addContentItemButton || "Ajouter une ligne"}</span>
</button> </button>
@ -122,12 +123,9 @@ export function GoalTreeIconPicker({ label, value, textContent, onChange }) {
menuClassName="goal-tree-icon-picker-menu" menuClassName="goal-tree-icon-picker-menu"
label={label} label={label}
renderTrigger={({ open, toggle }) => ( renderTrigger={({ open, toggle }) => (
<button className="goal-tree-icon-picker-button" type="button" onClick={toggle} aria-label={label} aria-expanded={open} title={label}> <button className="goal-tree-icon-picker-button nds-select-trigger" data-size="sm" data-variant="icon-picker" type="button" onClick={toggle} aria-label={label} aria-expanded={open} title={label}>
<span className="goal-tree-icon-preview" aria-hidden="true">
<Icon name={value} /> <Icon name={value} />
</span>
<span>{textContent[`icon${value}`] || value}</span> <span>{textContent[`icon${value}`] || value}</span>
<Icon name="dropdown" />
</button> </button>
)} )}
> >
@ -136,7 +134,8 @@ export function GoalTreeIconPicker({ label, value, textContent, onChange }) {
{GOAL_TREE_ICON_NAMES.map((icon) => ( {GOAL_TREE_ICON_NAMES.map((icon) => (
<button <button
key={icon} key={icon}
className={icon === value ? "active" : ""} className={`nds-icon-button ${icon === value ? "active" : ""}`.trim()}
data-size="sm"
type="button" type="button"
onClick={() => { onClick={() => {
onChange(icon); onChange(icon);

View file

@ -7,10 +7,10 @@ function GoalTreeChecklistInlineControl({ item, textContent, onUpdate }) {
const target = Math.max(1, item.qtyTarget || 1); const target = Math.max(1, item.qtyTarget || 1);
const current = Math.min(Math.max(0, item.qtyCurrent || 0), target); const current = Math.min(Math.max(0, item.qtyCurrent || 0), target);
if (target <= 1) { if (target <= 1) {
return <input className="goal-tree-node-check" type="checkbox" checked={current >= 1} onChange={(event) => onUpdate((entry) => ({ ...entry, qtyCurrent: event.target.checked ? 1 : 0 }))} aria-label={textContent.itemCheckedLabel || "État"} />; return <input className="goal-tree-node-check nds-checkbox" data-size="xs" type="checkbox" checked={current >= 1} onChange={(event) => onUpdate((entry) => ({ ...entry, qtyCurrent: event.target.checked ? 1 : 0 }))} aria-label={textContent.itemCheckedLabel || "État"} />;
} }
return ( return (
<span className="goal-tree-content-qty-stepper"> <span className="goal-tree-content-qty-stepper nds-quantity-stepper" data-size="sm">
<button type="button" onClick={() => onUpdate((entry) => ({ ...entry, qtyCurrent: Math.max(0, (entry.qtyCurrent || 0) - 1) }))} aria-label={textContent.decrementItemTitle || "Retirer"} title={textContent.decrementItemTitle || "Retirer"}> <button type="button" onClick={() => onUpdate((entry) => ({ ...entry, qtyCurrent: Math.max(0, (entry.qtyCurrent || 0) - 1) }))} aria-label={textContent.decrementItemTitle || "Retirer"} title={textContent.decrementItemTitle || "Retirer"}>
<span aria-hidden="true">-</span> <span aria-hidden="true">-</span>
</button> </button>
@ -26,10 +26,10 @@ export function GoalTreeStepLeafControl({ node, textContent, onChange }) {
const target = Math.max(1, node.computedQtyTarget || node.qtyTarget || 1); const target = Math.max(1, node.computedQtyTarget || node.qtyTarget || 1);
const current = Math.min(Math.max(0, node.qtyCurrent || 0), target); const current = Math.min(Math.max(0, node.qtyCurrent || 0), target);
if (target <= 1) { if (target <= 1) {
return <input className="goal-tree-step-check" type="checkbox" checked={current >= 1} onChange={(event) => onChange(event.target.checked ? 1 : 0)} aria-label={textContent.itemCheckedLabel || "État"} />; return <input className="goal-tree-step-check nds-checkbox" data-size="xs" type="checkbox" checked={current >= 1} onChange={(event) => onChange(event.target.checked ? 1 : 0)} aria-label={textContent.itemCheckedLabel || "État"} />;
} }
return ( return (
<span className="goal-tree-step-qty-stepper goal-tree-content-qty-stepper"> <span className="goal-tree-step-qty-stepper goal-tree-content-qty-stepper nds-quantity-stepper" data-size="xs">
<button type="button" onClick={() => onChange(Math.max(0, current - 1))} aria-label={textContent.decrementItemTitle || "Retirer"} title={textContent.decrementItemTitle || "Retirer"}> <button type="button" onClick={() => onChange(Math.max(0, current - 1))} aria-label={textContent.decrementItemTitle || "Retirer"} title={textContent.decrementItemTitle || "Retirer"}>
<span aria-hidden="true">-</span> <span aria-hidden="true">-</span>
</button> </button>

View file

@ -87,7 +87,8 @@ export function GoalTreeView({
return ( return (
<section className={`goal-tree-view tool-split-results ${verticalView ? "is-vertical" : "is-horizontal"} ${editing ? "is-editing" : ""}`} aria-label={textContent.treeTitle || "Arbre d'objectifs"}> <section className={`goal-tree-view tool-split-results ${verticalView ? "is-vertical" : "is-horizontal"} ${editing ? "is-editing" : ""}`} aria-label={textContent.treeTitle || "Arbre d'objectifs"}>
<button <button
className={`module-scroll-button goal-tree-orientation-switch ${verticalView ? "active" : ""}`} className={`module-scroll-button goal-tree-orientation-switch nds-switch ${verticalView ? "active" : ""}`}
data-size="lg"
type="button" type="button"
onClick={onToggleOrientation} onClick={onToggleOrientation}
aria-label={verticalView ? textContent.horizontalViewTitle || "Afficher horizontalement" : textContent.verticalViewTitle || "Afficher verticalement"} aria-label={verticalView ? textContent.horizontalViewTitle || "Afficher horizontalement" : textContent.verticalViewTitle || "Afficher verticalement"}
@ -100,7 +101,7 @@ export function GoalTreeView({
<div className="goal-tree-scroll-area themed-scrollbar"> <div className="goal-tree-scroll-area themed-scrollbar">
<div className="goal-tree-view-controls"> <div className="goal-tree-view-controls">
{editing && ( {editing && (
<button className="goal-tree-add-root-button" type="button" onClick={() => onAdd("")}> <button className="goal-tree-add-root-button nds-button" data-size="sm" data-variant="dashed" type="button" onClick={() => onAdd("")}>
<Icon name="add" /> <Icon name="add" />
<span>{textContent.addRootSchemaButton || "Ajouter un objectif a la racine"}</span> <span>{textContent.addRootSchemaButton || "Ajouter un objectif a la racine"}</span>
</button> </button>

View file

@ -123,7 +123,8 @@ export function ImageAnnotationModule({ toolboxId, moduleId, context, editing, t
<img src={data.image} alt={textContent.imageAlt || "Image annotée"} draggable="false" /> <img src={data.image} alt={textContent.imageAlt || "Image annotée"} draggable="false" />
<div className="annotation-preview-actions"> <div className="annotation-preview-actions">
<button <button
className="tool-split-action-button" className="tool-split-action-button nds-icon-button"
data-size="sm"
type="button" type="button"
onClick={() => context.setImage({ onClick={() => context.setImage({
dataUrl: data.image, dataUrl: data.image,
@ -164,7 +165,7 @@ export function ImageAnnotationModule({ toolboxId, moduleId, context, editing, t
<Icon name="zoom" /> <Icon name="zoom" />
</button> </button>
{editing && ( {editing && (
<button className="tool-split-action-button danger" type="button" onClick={removeImage} aria-label={textContent.deleteImageTitle || "Supprimer l'image"} title={textContent.deleteImageTitle || "Supprimer l'image"}> <button className="tool-split-action-button nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={removeImage} aria-label={textContent.deleteImageTitle || "Supprimer l'image"} title={textContent.deleteImageTitle || "Supprimer l'image"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
)} )}

View file

@ -104,7 +104,9 @@ export function ImagesModule({ toolboxId, moduleId, context, editing, textExchan
return ( return (
<figure key={image.id} className={className} {...getItemProps({ itemId: image.id, parentId: moduleId })}> <figure key={image.id} className={className} {...getItemProps({ itemId: image.id, parentId: moduleId })}>
<button <button
className="image-drag-handle" className="image-drag-handle nds-icon-button"
data-size="sm"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => itemReorder.startDrag(event, image.id)} onPointerDown={(event) => itemReorder.startDrag(event, image.id)}
aria-label={textContent.reorderAriaLabel || "Déplacer l'image"} aria-label={textContent.reorderAriaLabel || "Déplacer l'image"}
@ -117,7 +119,8 @@ export function ImagesModule({ toolboxId, moduleId, context, editing, textExchan
</button> </button>
{editing ? ( {editing ? (
<input <input
className="image-label-input" className="image-label-input nds-inline-input"
type="text"
value={image.label || ""} value={image.label || ""}
placeholder={textContent.labelPlaceholder || "Libellé de l'image"} placeholder={textContent.labelPlaceholder || "Libellé de l'image"}
onChange={(event) => updateImage(image.id, { label: event.target.value })} onChange={(event) => updateImage(image.id, { label: event.target.value })}
@ -128,7 +131,8 @@ export function ImagesModule({ toolboxId, moduleId, context, editing, textExchan
) : null} ) : null}
<div className="image-actions"> <div className="image-actions">
<button <button
className="image-annotate-button" className="image-annotate-button nds-icon-button"
data-size="sm"
type="button" type="button"
onClick={() => context.createImageAnnotationModule?.(image.dataUrl)} onClick={() => context.createImageAnnotationModule?.(image.dataUrl)}
aria-label={textContent.annotateAriaLabel || "Annoter l'image"} aria-label={textContent.annotateAriaLabel || "Annoter l'image"}
@ -136,7 +140,7 @@ export function ImagesModule({ toolboxId, moduleId, context, editing, textExchan
> >
<Icon name="map" /> <Icon name="map" />
</button> </button>
<button className="image-delete-button danger" type="button" onClick={() => { <button className="image-delete-button nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={() => {
context.setModuleData(toolboxId, moduleId, { images: data.images.filter((item) => item.id !== image.id) }); context.setModuleData(toolboxId, moduleId, { images: data.images.filter((item) => item.id !== image.id) });
}} aria-label={textContent.deleteAriaLabel || "Supprimer l'image"} title={textContent.deleteTitle || "Supprimer"}> }} aria-label={textContent.deleteAriaLabel || "Supprimer l'image"} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />

View file

@ -59,9 +59,9 @@ export function LinksModule({ toolboxId, moduleId, context, editing, textExchang
{editing && ( {editing && (
<div className="module-add-panel"> <div className="module-add-panel">
<form className="inline-form links-add-form" onSubmit={addLink}> <form className="inline-form links-add-form" onSubmit={addLink}>
<input {...linkDraft.getFieldProps("title", { placeholder: textContent.titlePlaceholder || "Nom du lien" })} /> <input {...linkDraft.getFieldProps("title", { className: "nds-input", "data-size": "sm", type: "text", placeholder: textContent.titlePlaceholder || "Nom du lien" })} />
<input {...linkDraft.getFieldProps("url", { placeholder: textContent.urlPlaceholder || "https://..." })} /> <input {...linkDraft.getFieldProps("url", { className: "nds-input", "data-size": "sm", type: "url", placeholder: textContent.urlPlaceholder || "https://..." })} />
<button className="primary">{textContent.addButton || "Ajouter"}</button> <button className="nds-button" data-size="sm" data-variant="primary">{textContent.addButton || "Ajouter"}</button>
</form> </form>
</div> </div>
)} )}
@ -82,7 +82,9 @@ export function LinksModule({ toolboxId, moduleId, context, editing, textExchang
return ( return (
<li className={className} key={link.id} {...getItemProps({ itemId: link.id, parentId: moduleId })}> <li className={className} key={link.id} {...getItemProps({ itemId: link.id, parentId: moduleId })}>
<button <button
className="link-drag-handle" className="link-drag-handle nds-icon-button"
data-size="sm"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => itemReorder.startDrag(event, link.id)} onPointerDown={(event) => itemReorder.startDrag(event, link.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${link.title || link.url}`} aria-label={`${textContent.reorderTitle || "Déplacer"} ${link.title || link.url}`}
@ -95,10 +97,10 @@ export function LinksModule({ toolboxId, moduleId, context, editing, textExchang
<span>{link.url}</span> <span>{link.url}</span>
</a> </a>
<div> <div>
<button type="button" onClick={() => copyUrl(link)} aria-label={`${textContent.copyTitle || "Copier"} ${link.title || link.url}`} title={copiedId === link.id ? textContent.copiedTitle || "Copié" : textContent.copyTitle || "Copier"}> <button type="button" className="nds-icon-button" data-size="sm" onClick={() => copyUrl(link)} aria-label={`${textContent.copyTitle || "Copier"} ${link.title || link.url}`} title={copiedId === link.id ? textContent.copiedTitle || "Copié" : textContent.copyTitle || "Copier"}>
<Icon name="copy" /> <Icon name="copy" />
</button> </button>
<button type="button" className="danger" onClick={() => save(data.links.filter((item) => item.id !== link.id))} aria-label={`${textContent.deleteTitle || "Supprimer"} ${link.title || link.url}`} title={textContent.deleteTitle || "Supprimer"}> <button type="button" className="nds-icon-button" data-size="sm" data-variant="danger" onClick={() => save(data.links.filter((item) => item.id !== link.id))} aria-label={`${textContent.deleteTitle || "Supprimer"} ${link.title || link.url}`} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>

View file

@ -40,7 +40,7 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
function resizeMemoTextarea(element) { function resizeMemoTextarea(element) {
if (!element) return; if (!element) return;
element.style.height = "auto"; element.style.height = "auto";
element.style.height = `${element.scrollHeight}px`; element.style.height = `${element.scrollHeight + 2}px`;
} }
function save(nextData) { function save(nextData) {
@ -93,7 +93,7 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
{editing && ( {editing && (
<div className="module-add-panel"> <div className="module-add-panel">
<button className="primary memos-add-button" type="button" onClick={addMemo}> <button className="memos-add-button nds-button" data-size="sm" data-variant="primary" type="button" onClick={addMemo}>
<Icon name="add" /> <Icon name="add" />
<span>{textContent.addButton || "Ajouter"}</span> <span>{textContent.addButton || "Ajouter"}</span>
</button> </button>
@ -102,7 +102,8 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
<div className="memos-toolbar"> <div className="memos-toolbar">
<button <button
className={`memos-ticker-toggle ${data.tickerEnabled ? "active" : ""}`} className={`memos-ticker-toggle nds-icon-button ${data.tickerEnabled ? "active" : ""}`}
data-size="sm"
type="button" type="button"
onClick={() => save({ ...data, tickerEnabled: !data.tickerEnabled })} onClick={() => save({ ...data, tickerEnabled: !data.tickerEnabled })}
aria-pressed={data.tickerEnabled} aria-pressed={data.tickerEnabled}
@ -135,7 +136,9 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
return ( return (
<article className={className} key={memo.id} style={{ "--memo-color": memo.color }} {...getItemProps({ itemId: memo.id, parentId: moduleId })}> <article className={className} key={memo.id} style={{ "--memo-color": memo.color }} {...getItemProps({ itemId: memo.id, parentId: moduleId })}>
<button <button
className="memo-drag-handle" className="memo-drag-handle nds-icon-button"
data-size="sm"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => itemReorder.startDrag(event, memo.id)} onPointerDown={(event) => itemReorder.startDrag(event, memo.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${memo.text}`} aria-label={`${textContent.reorderTitle || "Déplacer"} ${memo.text}`}
@ -144,6 +147,9 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
<Icon name="drag" /> <Icon name="drag" />
</button> </button>
<textarea <textarea
className="nds-textarea"
data-size="sm"
data-variant="ghost"
ref={(element) => { ref={(element) => {
if (element) { if (element) {
textareaRefs.current.set(memo.id, element); textareaRefs.current.set(memo.id, element);
@ -169,7 +175,7 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
label={textContent.colorLabel || "Couleur du mémo"} label={textContent.colorLabel || "Couleur du mémo"}
renderTrigger={({ toggle }) => ( renderTrigger={({ toggle }) => (
<button <button
className="notepad-color-toggle" className="notepad-color-toggle nds-swatch"
type="button" type="button"
style={{ "--swatch-color": memo.color }} style={{ "--swatch-color": memo.color }}
onClick={toggle} onClick={toggle}
@ -183,7 +189,7 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
{TEXT_COLORS.map((color) => ( {TEXT_COLORS.map((color) => (
<button <button
key={color} key={color}
className={`notepad-swatch ${memo.color === color ? "active" : ""}`} className={`notepad-swatch nds-swatch ${memo.color === color ? "active" : ""}`}
type="button" type="button"
style={{ "--swatch-color": color }} style={{ "--swatch-color": color }}
onClick={() => { onClick={() => {
@ -196,14 +202,14 @@ export function MemosModule({ toolboxId, moduleId, context, editing, textExchang
</> </>
)} )}
</CompactDropdown> </CompactDropdown>
<button className="memo-delete-button danger" type="button" onClick={() => deleteMemo(memo.id)} aria-label={textContent.deleteTitle || "Supprimer le mémo"} title={textContent.deleteTitle || "Supprimer"}> <button className="memo-delete-button nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={() => deleteMemo(memo.id)} aria-label={textContent.deleteTitle || "Supprimer le mémo"} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</footer> </footer>
</article> </article>
); );
}) : ( }) : (
<button className="memos-empty" type="button" onClick={addMemo}> <button className="memos-empty nds-button" data-size="sm" data-variant="dashed" type="button" onClick={addMemo}>
<Icon name="add" /> <Icon name="add" />
<span>{textContent.emptyLabel || "Ajouter un premier mémo"}</span> <span>{textContent.emptyLabel || "Ajouter un premier mémo"}</span>
</button> </button>

View file

@ -295,7 +295,11 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
label={textContent.pagesMenuLabel || "Pages du bloc note"} label={textContent.pagesMenuLabel || "Pages du bloc note"}
renderTrigger={({ open, toggle }) => ( renderTrigger={({ open, toggle }) => (
<button <button
className={`notepad-pages-toggle ${open ? "active" : ""}`} className={`notepad-pages-toggle nds-icon-button ${open ? "active" : ""}`}
data-size="sm"
data-variant="panel-toggle"
data-edge="right"
data-border="joined"
type="button" type="button"
onClick={toggle} onClick={toggle}
aria-label={textContent.pagesToggleLabel || "Afficher les pages du bloc note"} aria-label={textContent.pagesToggleLabel || "Afficher les pages du bloc note"}
@ -311,10 +315,12 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
<div className="notepad-pages-list"> <div className="notepad-pages-list">
{normalizedData.pages.map((page) => ( {normalizedData.pages.map((page) => (
<div className={`notepad-page-row ${page.id === normalizedData.activePageId ? "active" : ""}`} key={page.id}> <div className={`notepad-page-row ${page.id === normalizedData.activePageId ? "active" : ""}`} key={page.id}>
<div className="notepad-page-main"> <div className="notepad-page-main nds-list-row">
{editingPageId === page.id ? ( {editingPageId === page.id ? (
<input <input
className="notepad-page-title-input" className="notepad-page-title-input nds-input"
data-size="xs"
type="text"
value={pageTitleDrafts[page.id] ?? page.title} value={pageTitleDrafts[page.id] ?? page.title}
aria-label={`${textContent.renamePageLabel || "Renommer"} ${page.title}`} aria-label={`${textContent.renamePageLabel || "Renommer"} ${page.title}`}
autoFocus autoFocus
@ -339,7 +345,8 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
</div> </div>
<div className="notepad-page-actions"> <div className="notepad-page-actions">
<button <button
className="notepad-page-edit" className="notepad-page-edit nds-icon-button"
data-size="xxs"
type="button" type="button"
onClick={() => { onClick={() => {
setPageTitleDrafts((current) => ({ ...current, [page.id]: page.title })); setPageTitleDrafts((current) => ({ ...current, [page.id]: page.title }));
@ -351,7 +358,9 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
<Icon name="edit" /> <Icon name="edit" />
</button> </button>
<button <button
className="notepad-page-delete danger" className="notepad-page-delete nds-icon-button"
data-size="xxs"
data-variant="danger"
type="button" type="button"
onClick={() => deletePage(page.id, close)} onClick={() => deletePage(page.id, close)}
disabled={normalizedData.pages.length <= 1} disabled={normalizedData.pages.length <= 1}
@ -364,7 +373,7 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
</div> </div>
))} ))}
</div> </div>
<button className="notepad-page-add" type="button" onClick={() => addPage(close)}> <button className="notepad-page-add nds-button" data-size="xs" data-variant="dashed" type="button" onClick={() => addPage(close)}>
<Icon name="add" /> <Icon name="add" />
<span>{textContent.addPageLabel || "Ajouter une page"}</span> <span>{textContent.addPageLabel || "Ajouter une page"}</span>
</button> </button>

View file

@ -52,7 +52,8 @@ export function NotepadToolbar({
function ToolbarButton({ active, title, children, onClick }) { function ToolbarButton({ active, title, children, onClick }) {
return ( return (
<button <button
className={`notepad-toolbar-button ${active ? "active" : ""}`} className={`notepad-toolbar-button nds-icon-button ${active ? "active" : ""}`}
data-size="sm"
type="button" type="button"
aria-pressed={active} aria-pressed={active}
title={title} title={title}
@ -74,7 +75,8 @@ function ListTypeDropdown({ textContent, activeFormats, onCommand }) {
preventMouseDown preventMouseDown
renderTrigger={({ toggle }) => ( renderTrigger={({ toggle }) => (
<button <button
className={`notepad-toolbar-button ${activeFormats.bulletList || activeFormats.numberList ? "active" : ""}`} className={`notepad-toolbar-button nds-icon-button ${activeFormats.bulletList || activeFormats.numberList ? "active" : ""}`}
data-size="sm"
type="button" type="button"
aria-pressed={activeFormats.bulletList || activeFormats.numberList} aria-pressed={activeFormats.bulletList || activeFormats.numberList}
title={textContent.listGroupLabel || "Listes"} title={textContent.listGroupLabel || "Listes"}
@ -87,7 +89,8 @@ function ListTypeDropdown({ textContent, activeFormats, onCommand }) {
{({ close }) => ( {({ close }) => (
<> <>
<button <button
className={`notepad-list-option ${activeFormats.bulletList ? "active" : ""}`} className={`notepad-list-option nds-icon-button ${activeFormats.bulletList ? "active" : ""}`}
data-size="sm"
type="button" type="button"
aria-pressed={activeFormats.bulletList} aria-pressed={activeFormats.bulletList}
onClick={() => { onClick={() => {
@ -98,7 +101,8 @@ function ListTypeDropdown({ textContent, activeFormats, onCommand }) {
• •
</button> </button>
<button <button
className={`notepad-list-option ${activeFormats.numberList ? "active" : ""}`} className={`notepad-list-option nds-icon-button ${activeFormats.numberList ? "active" : ""}`}
data-size="sm"
type="button" type="button"
aria-pressed={activeFormats.numberList} aria-pressed={activeFormats.numberList}
onClick={() => { onClick={() => {

View file

@ -33,6 +33,7 @@ export function TableRow({ rowIndex, columns, cells, evaluatedCells, editingCell
return ( return (
<input <input
type="text"
key={address} key={address}
ref={(element) => registerInput(address, element)} ref={(element) => registerInput(address, element)}
className={className} className={className}
@ -82,6 +83,7 @@ export function HeaderLabelInput({ className, value, fallback, onSave, ...props
<input <input
{...inlineEdit.getInputProps({ {...inlineEdit.getInputProps({
...props, ...props,
type: "text",
className, className,
onFocus: (event) => { onFocus: (event) => {
if (value === fallback) window.requestAnimationFrame(() => event.target.select()); if (value === fallback) window.requestAnimationFrame(() => event.target.select());

View file

@ -8,7 +8,8 @@ export function TableToolbar({ rows, columns, textContent, onResize, onCopy }) {
<div className="notepad-format-controls table-controls"> <div className="notepad-format-controls table-controls">
<div className="notepad-toolbar-group" role="group" aria-label={textContent.rowActionsLabel || "Lignes"}> <div className="notepad-toolbar-group" role="group" aria-label={textContent.rowActionsLabel || "Lignes"}>
<button <button
className="notepad-toolbar-button" className="notepad-toolbar-button nds-icon-button"
data-size="sm"
type="button" type="button"
onClick={() => onResize(Math.min(MAX_ROWS, rows + 1), columns)} onClick={() => onResize(Math.min(MAX_ROWS, rows + 1), columns)}
disabled={rows >= MAX_ROWS} disabled={rows >= MAX_ROWS}
@ -18,7 +19,9 @@ export function TableToolbar({ rows, columns, textContent, onResize, onCopy }) {
<Icon name="add-row" /> <Icon name="add-row" />
</button> </button>
<button <button
className="notepad-toolbar-button danger" className="notepad-toolbar-button nds-icon-button"
data-size="sm"
data-variant="danger"
type="button" type="button"
onClick={() => onResize(Math.max(1, rows - 1), columns)} onClick={() => onResize(Math.max(1, rows - 1), columns)}
disabled={rows <= 1} disabled={rows <= 1}
@ -30,7 +33,8 @@ export function TableToolbar({ rows, columns, textContent, onResize, onCopy }) {
</div> </div>
<div className="notepad-toolbar-group" role="group" aria-label={textContent.columnActionsLabel || "Colonnes"}> <div className="notepad-toolbar-group" role="group" aria-label={textContent.columnActionsLabel || "Colonnes"}>
<button <button
className="notepad-toolbar-button" className="notepad-toolbar-button nds-icon-button"
data-size="sm"
type="button" type="button"
onClick={() => onResize(rows, Math.min(MAX_COLUMNS, columns + 1))} onClick={() => onResize(rows, Math.min(MAX_COLUMNS, columns + 1))}
disabled={columns >= MAX_COLUMNS} disabled={columns >= MAX_COLUMNS}
@ -40,7 +44,9 @@ export function TableToolbar({ rows, columns, textContent, onResize, onCopy }) {
<Icon name="add-column" /> <Icon name="add-column" />
</button> </button>
<button <button
className="notepad-toolbar-button danger" className="notepad-toolbar-button nds-icon-button"
data-size="sm"
data-variant="danger"
type="button" type="button"
onClick={() => onResize(rows, Math.max(1, columns - 1))} onClick={() => onResize(rows, Math.max(1, columns - 1))}
disabled={columns <= 1} disabled={columns <= 1}
@ -56,7 +62,8 @@ export function TableToolbar({ rows, columns, textContent, onResize, onCopy }) {
</div> </div>
<div className="notepad-toolbar-group table-copy-group"> <div className="notepad-toolbar-group table-copy-group">
<button <button
className="notepad-toolbar-button table-copy-button" className="notepad-toolbar-button table-copy-button nds-button"
data-size="sm"
type="button" type="button"
onClick={onCopy} onClick={onCopy}
aria-label={textContent.copyTitle || "Copier les valeurs"} aria-label={textContent.copyTitle || "Copier les valeurs"}

View file

@ -78,6 +78,8 @@ export function TaskPlannerBranch({
<li className="task-planner-hidden-parent" key={`${task.id}:visible-children`}> <li className="task-planner-hidden-parent" key={`${task.id}:visible-children`}>
<div className="task-planner-hidden-parent-line"> <div className="task-planner-hidden-parent-line">
<input <input
className="nds-checkbox"
data-size="sm"
type="checkbox" type="checkbox"
checked={task.checked} checked={task.checked}
onChange={(event) => onUpdateTask(task.id, (current) => ({ onChange={(event) => onUpdateTask(task.id, (current) => ({
@ -158,7 +160,9 @@ export function TaskPlannerBranch({
<div className="checklist-section-header task-planner-category-header"> <div className="checklist-section-header task-planner-category-header">
<div className="task-planner-category-title"> <div className="task-planner-category-title">
<button <button
className="task-planner-category-drag-handle" className="task-planner-category-drag-handle nds-icon-button"
data-size="xs"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: group.category, parentId })} onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: group.category, parentId })}
aria-label={`${textContent.categoryReorderTitle || "Déplacer la catégorie"} ${group.category}`} aria-label={`${textContent.categoryReorderTitle || "Déplacer la catégorie"} ${group.category}`}
@ -171,7 +175,8 @@ export function TaskPlannerBranch({
<div className="task-planner-category-actions"> <div className="task-planner-category-actions">
<span>{groupCompletedCount} / {groupCount}</span> <span>{groupCompletedCount} / {groupCount}</span>
<button <button
className="checklist-section-collapse-button" className="checklist-section-collapse-button nds-icon-button"
data-size="xs"
type="button" type="button"
onClick={() => onToggleCategoryCollapsed(group.category)} onClick={() => onToggleCategoryCollapsed(group.category)}
aria-expanded={!isCollapsed} aria-expanded={!isCollapsed}

View file

@ -65,7 +65,9 @@ export function TaskPlannerItem({
<li className={className} {...reorder.getItemProps({ itemId: task.id, groupId: getTaskCategoryDataset(task, tasks, parentMap), parentId })}> <li className={className} {...reorder.getItemProps({ itemId: task.id, groupId: getTaskCategoryDataset(task, tasks, parentMap), parentId })}>
<div className="task-planner-line"> <div className="task-planner-line">
<button <button
className="task-planner-drag-handle" className="task-planner-drag-handle nds-icon-button"
data-size="xs"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, task.id)} onPointerDown={(event) => reorder.itemReorder.startDrag(event, task.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${task.title}`} aria-label={`${textContent.reorderTitle || "Déplacer"} ${task.title}`}
@ -74,6 +76,8 @@ export function TaskPlannerItem({
<Icon name="drag" /> <Icon name="drag" />
</button> </button>
<input <input
className="nds-checkbox"
data-size="sm"
type="checkbox" type="checkbox"
checked={task.checked} checked={task.checked}
onChange={updateChecked} onChange={updateChecked}
@ -81,12 +85,14 @@ export function TaskPlannerItem({
/> />
<input <input
{...titleEdit.getInputProps({ {...titleEdit.getInputProps({
className: "tool-split-entry-label task-planner-title-input", className: "tool-split-entry-label task-planner-title-input nds-inline-input",
type: "text",
"aria-label": textContent.titleLabel || "Titre" "aria-label": textContent.titleLabel || "Titre"
})} })}
/> />
<select <select
className="task-planner-type-select" className="task-planner-type-select nds-select"
data-size="xs"
value={task.type} value={task.type}
onChange={(event) => onUpdateTask(task.id, (current) => { onChange={(event) => onUpdateTask(task.id, (current) => {
const nextType = event.target.value; const nextType = event.target.value;
@ -104,7 +110,8 @@ export function TaskPlannerItem({
)} )}
<div className="task-planner-actions"> <div className="task-planner-actions">
<button <button
className={`task-planner-dropdown-button ${descriptionOpen ? "active" : ""}`} className={`task-planner-dropdown-button nds-icon-button ${descriptionOpen ? "active" : ""}`}
data-size="xs"
type="button" type="button"
onClick={onToggleDescription} onClick={onToggleDescription}
aria-expanded={descriptionOpen} aria-expanded={descriptionOpen}
@ -114,7 +121,8 @@ export function TaskPlannerItem({
<Icon name={descriptionOpen ? "chevron-up" : "chevron-down"} /> <Icon name={descriptionOpen ? "chevron-up" : "chevron-down"} />
</button> </button>
<button <button
className={`task-planner-settings-button ${settingsOpen ? "active" : ""}`} className={`task-planner-settings-button nds-icon-button ${settingsOpen ? "active" : ""}`}
data-size="xs"
type="button" type="button"
onClick={onToggleSettings} onClick={onToggleSettings}
aria-expanded={settingsOpen} aria-expanded={settingsOpen}
@ -123,7 +131,7 @@ export function TaskPlannerItem({
> >
<Icon name="settings" /> <Icon name="settings" />
</button> </button>
<button className="task-planner-delete-button danger" type="button" onClick={() => onDeleteTask(task.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${task.title}`} title={textContent.deleteTitle || "Supprimer"}> <button className="task-planner-delete-button nds-icon-button" data-size="xs" data-variant="danger" type="button" onClick={() => onDeleteTask(task.id)} aria-label={`${textContent.deleteTitle || "Supprimer"} ${task.title}`} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>

View file

@ -110,7 +110,9 @@ function TaskDescriptionPanel({ task, tasks, parentMap, parentId, textContent, o
<span>{textContent.categoryLabel || "Catégorie"}</span> <span>{textContent.categoryLabel || "Catégorie"}</span>
<input <input
{...categoryEdit.getInputProps({ {...categoryEdit.getInputProps({
className: "nds-inline-input",
disabled: !categoryEditable, disabled: !categoryEditable,
type: "text",
placeholder: textContent.categoryPlaceholder || "Nom de catégorie", placeholder: textContent.categoryPlaceholder || "Nom de catégorie",
"aria-label": `${textContent.categoryLabel || "Catégorie"} ${task.title}` "aria-label": `${textContent.categoryLabel || "Catégorie"} ${task.title}`
})} })}
@ -120,7 +122,8 @@ function TaskDescriptionPanel({ task, tasks, parentMap, parentId, textContent, o
<span>{textContent.descriptionLabel || "Description"}</span> <span>{textContent.descriptionLabel || "Description"}</span>
<textarea <textarea
{...descriptionEdit.getInputProps({ {...descriptionEdit.getInputProps({
className: "task-planner-description", className: "task-planner-description nds-textarea",
"data-size": "sm",
placeholder: textContent.descriptionPlaceholder || "Description", placeholder: textContent.descriptionPlaceholder || "Description",
"aria-label": `${textContent.descriptionLabel || "Description"} ${task.title}` "aria-label": `${textContent.descriptionLabel || "Description"} ${task.title}`
})} })}
@ -138,6 +141,8 @@ function TaskResetSettings({ task, data, textContent, onUpdateTask }) {
<span>{textContent.taskDailyResetTimeLabel || "Heure quotidienne"}</span> <span>{textContent.taskDailyResetTimeLabel || "Heure quotidienne"}</span>
<div className="task-planner-reset-control"> <div className="task-planner-reset-control">
<input <input
className="nds-time"
data-size="xs"
type="time" type="time"
value={task.dailyResetTime || data.resetTime} value={task.dailyResetTime || data.resetTime}
onChange={(event) => onUpdateTask(task.id, (current) => { onChange={(event) => onUpdateTask(task.id, (current) => {
@ -149,6 +154,8 @@ function TaskResetSettings({ task, data, textContent, onUpdateTask }) {
aria-label={`${textContent.taskDailyResetTimeLabel || "Heure quotidienne"} ${task.title}`} aria-label={`${textContent.taskDailyResetTimeLabel || "Heure quotidienne"} ${task.title}`}
/> />
<button <button
className="task-planner-reset-button nds-icon-button"
data-size="xs"
type="button" type="button"
onClick={() => onUpdateTask(task.id, (current) => { onClick={() => onUpdateTask(task.id, (current) => {
const nextTask = { ...current }; const nextTask = { ...current };
@ -171,6 +178,8 @@ function TaskResetSettings({ task, data, textContent, onUpdateTask }) {
<label className="task-planner-reset-override"> <label className="task-planner-reset-override">
<span>{textContent.taskWeeklyResetDayLabel || "Jour hebdo"}</span> <span>{textContent.taskWeeklyResetDayLabel || "Jour hebdo"}</span>
<select <select
className="nds-select"
data-size="xs"
value={task.weeklyResetDay ?? ""} value={task.weeklyResetDay ?? ""}
onChange={(event) => onUpdateTask(task.id, (current) => { onChange={(event) => onUpdateTask(task.id, (current) => {
const nextTask = { ...current }; const nextTask = { ...current };
@ -212,6 +221,8 @@ function LinksEditor({ label, relations, task, tasks, parentMap, relationTargets
<div className="task-planner-relations"> <div className="task-planner-relations">
<span>{label}</span> <span>{label}</span>
<select <select
className="nds-select"
data-size="sm"
value="" value=""
onChange={(event) => { onChange={(event) => {
onAddLinkTarget(task.id, event.target.value); onAddLinkTarget(task.id, event.target.value);
@ -228,6 +239,8 @@ function LinksEditor({ label, relations, task, tasks, parentMap, relationTargets
<span>{getTaskTitle(tasks, relation.toTaskId)}</span> <span>{getTaskTitle(tasks, relation.toTaskId)}</span>
<label title={prerequisiteLabel}> <label title={prerequisiteLabel}>
<input <input
className="nds-checkbox"
data-size="sm"
type="checkbox" type="checkbox"
checked={relation.prerequisite} checked={relation.prerequisite}
onChange={(event) => onSetPrerequisite(task.id, relation.toTaskId, event.target.checked)} onChange={(event) => onSetPrerequisite(task.id, relation.toTaskId, event.target.checked)}
@ -235,7 +248,7 @@ function LinksEditor({ label, relations, task, tasks, parentMap, relationTargets
/> />
<span>{prerequisiteLabel}</span> <span>{prerequisiteLabel}</span>
</label> </label>
<button type="button" onClick={() => onRemoveLinkTarget(task.id, relation.toTaskId)} title={removeLabel} aria-label={`${removeLabel} ${getTaskTitle(tasks, relation.toTaskId)}`}> <button className="nds-icon-button" data-size="xs" data-variant="danger" type="button" onClick={() => onRemoveLinkTarget(task.id, relation.toTaskId)} title={removeLabel} aria-label={`${removeLabel} ${getTaskTitle(tasks, relation.toTaskId)}`}>
<Icon name="close" /> <Icon name="close" />
</button> </button>
</div> </div>

View file

@ -7,17 +7,19 @@ export function TaskPlannerSettings({ data, textContent, onWeeklyResetDayChange,
<section className="task-planner-settings" aria-label={textContent.settingsTitle || "Réglages du planificateur"}> <section className="task-planner-settings" aria-label={textContent.settingsTitle || "Réglages du planificateur"}>
<label> <label>
<span>{textContent.globalWeeklyResetDayLabel || "Reset hebdo"}</span> <span>{textContent.globalWeeklyResetDayLabel || "Reset hebdo"}</span>
<select value={data.weeklyResetDay} onChange={(event) => onWeeklyResetDayChange(Number(event.target.value))}> <select className="nds-select" data-size="sm" value={data.weeklyResetDay} onChange={(event) => onWeeklyResetDayChange(Number(event.target.value))}>
{WEEK_DAYS.map((day) => <option key={day.value} value={day.value}>{day.label}</option>)} {WEEK_DAYS.map((day) => <option key={day.value} value={day.value}>{day.label}</option>)}
</select> </select>
</label> </label>
<label> <label>
<span>{textContent.resetTimeLabel || "Heure de reset"}</span> <span>{textContent.resetTimeLabel || "Heure de reset"}</span>
<input type="time" value={data.resetTime} onChange={(event) => onResetTimeChange(event.target.value || "00:00")} /> <input className="nds-time" data-size="sm" type="time" value={data.resetTime} onChange={(event) => onResetTimeChange(event.target.value || "00:00")} />
</label> </label>
<div className="task-planner-settings-actions"> <div className="task-planner-settings-actions">
<button <button
className="task-planner-clear-completed-button danger" className="task-planner-clear-completed-button nds-button"
data-size="sm"
data-variant="danger"
type="button" type="button"
onClick={onClearCompletedUniqueTasks} onClick={onClearCompletedUniqueTasks}
disabled={!data.tasks.some((task) => task.type === "unique" && task.checked)} disabled={!data.tasks.some((task) => task.type === "unique" && task.checked)}

View file

@ -23,7 +23,8 @@ export function TaskPlannerTopbar({
<div className="task-planner-topbar"> <div className="task-planner-topbar">
<div className="task-planner-global-controls"> <div className="task-planner-global-controls">
<button <button
className={`tool-split-scroll-toggle task-planner-hide-completed-toggle ${data.hideCompleted ? "active" : ""}`} className={`tool-split-scroll-toggle task-planner-hide-completed-toggle nds-switch ${data.hideCompleted ? "active" : ""}`}
data-size="lg"
type="button" type="button"
onClick={onHideCompletedToggle} onClick={onHideCompletedToggle}
aria-pressed={data.hideCompleted} aria-pressed={data.hideCompleted}
@ -34,7 +35,8 @@ export function TaskPlannerTopbar({
<i aria-hidden="true" /> <i aria-hidden="true" />
</button> </button>
<button <button
className={`task-planner-settings-toggle ${settingsOpen ? "active" : ""}`} className={`task-planner-settings-toggle nds-button ${settingsOpen ? "active" : ""}`}
data-size="sm"
type="button" type="button"
onClick={onSettingsToggle} onClick={onSettingsToggle}
aria-expanded={settingsOpen} aria-expanded={settingsOpen}
@ -48,11 +50,11 @@ export function TaskPlannerTopbar({
{editing && ( {editing && (
<div className="task-planner-add-stack"> <div className="task-planner-add-stack">
<form className="inline-form task-planner-add-form" onSubmit={onAddTask}> <form className="inline-form task-planner-add-form" onSubmit={onAddTask}>
<input {...taskDraft.getFieldProps("title", { placeholder: textContent.titlePlaceholder || "Nouvelle tâche" })} /> <input {...taskDraft.getFieldProps("title", { className: "nds-input", "data-size": "sm", type: "text", placeholder: textContent.titlePlaceholder || "Nouvelle tâche" })} />
<select {...taskDraft.getFieldProps("type", { "aria-label": textContent.typeLabel || "Type" })}> <select {...taskDraft.getFieldProps("type", { className: "nds-select", "data-size": "sm", "aria-label": textContent.typeLabel || "Type" })}>
{TASK_TYPES.map((taskType) => <option key={taskType} value={taskType}>{getTaskTypeLabel(taskType, textContent)}</option>)} {TASK_TYPES.map((taskType) => <option key={taskType} value={taskType}>{getTaskTypeLabel(taskType, textContent)}</option>)}
</select> </select>
<button className="primary">{textContent.addButton || "Ajouter"}</button> <button className="nds-button" data-size="sm" data-variant="primary">{textContent.addButton || "Ajouter"}</button>
</form> </form>
</div> </div>
)} )}

View file

@ -9,11 +9,11 @@ export function TextExchangeActions({ type, data, context, onImport, textContent
return ( return (
<> <>
<div className="text-import-actions"> <div className="text-import-actions">
<button type="button" onClick={exchange.openImport}> <button className="nds-button" data-size="sm" type="button" onClick={exchange.openImport}>
<Icon name="import" /> <Icon name="import" />
<span>{textContent.importOpenButton || "Importer du texte"}</span> <span>{textContent.importOpenButton || "Importer du texte"}</span>
</button> </button>
<button type="button" onClick={exchange.openExport} disabled={!exchange.canExport}> <button className="nds-button" data-size="sm" type="button" onClick={exchange.openExport} disabled={!exchange.canExport}>
<Icon name="export" /> <Icon name="export" />
<span>{textContent.exportOpenButton || exportLabel || "Exporter en format texte"}</span> <span>{textContent.exportOpenButton || exportLabel || "Exporter en format texte"}</span>
</button> </button>

View file

@ -24,14 +24,15 @@ export function TextImportModal({ title, value, placeholder, submitLabel, readOn
<section className="confirm-modal text-import-modal-dialog" role="dialog" aria-modal="true" aria-labelledby="text-import-title"> <section className="confirm-modal text-import-modal-dialog" role="dialog" aria-modal="true" aria-labelledby="text-import-title">
<header> <header>
<h2 id="text-import-title">{title}</h2> <h2 id="text-import-title">{title}</h2>
<button className="drawer-close-button" type="button" onClick={onClose} aria-label="Fermer" title="Fermer"> <button className="drawer-close-button nds-icon-button" data-size="sm" type="button" onClick={onClose} aria-label="Fermer" title="Fermer">
<Icon name="close" /> <Icon name="close" />
</button> </button>
</header> </header>
<form className="text-import-modal-form" onSubmit={onSubmit}> <form className="text-import-modal-form" onSubmit={onSubmit}>
<textarea <textarea
autoFocus autoFocus
className={error ? "has-error" : ""} className={`nds-textarea ${error ? "has-error" : ""}`.trim()}
data-size="sm"
readOnly={readOnly} readOnly={readOnly}
value={value} value={value}
onChange={(event) => onChange?.(event.target.value)} onChange={(event) => onChange?.(event.target.value)}
@ -53,11 +54,11 @@ export function TextImportModal({ title, value, placeholder, submitLabel, readOn
</p> </p>
)} )}
<footer> <footer>
<button type="button" onClick={onClose}>Annuler</button> <button className="nds-button" data-size="sm" type="button" onClick={onClose}>Annuler</button>
{readOnly ? ( {readOnly ? (
<button className="primary" type="button" onClick={onCopy}>{submitLabel}</button> <button className="nds-button" data-size="sm" data-variant="primary" type="button" onClick={onCopy}>{submitLabel}</button>
) : ( ) : (
<button className="primary" type="submit">{submitLabel}</button> <button className="nds-button" data-size="sm" data-variant="primary" type="submit">{submitLabel}</button>
)} )}
</footer> </footer>
</form> </form>

View file

@ -18,11 +18,11 @@ export function CountdownControls({ textContent, form, error, onChange, onSubmit
<form className="timer-control-panel timer-countdown-form" role="tabpanel" onSubmit={onSubmit} aria-describedby={error ? "timer-countdown-error" : undefined}> <form className="timer-control-panel timer-countdown-form" role="tabpanel" onSubmit={onSubmit} aria-describedby={error ? "timer-countdown-error" : undefined}>
<label> <label>
<span>{textContent.countdownLabel || "Libellé"}</span> <span>{textContent.countdownLabel || "Libellé"}</span>
<input value={form.label} onChange={(event) => onChange({ label: event.target.value })} placeholder={textContent.countdownLabelPlaceholder || "Boss, event, craft..."} /> <input className="nds-input" data-size="sm" type="text" value={form.label} onChange={(event) => onChange({ label: event.target.value })} placeholder={textContent.countdownLabelPlaceholder || "Boss, event, craft..."} />
</label> </label>
<label> <label>
<span>{textContent.countdownTypeLabel || "Type"}</span> <span>{textContent.countdownTypeLabel || "Type"}</span>
<select value={form.type} onChange={(event) => onChange({ type: event.target.value })}> <select className="nds-select" data-size="sm" value={form.type} onChange={(event) => onChange({ type: event.target.value })}>
<option value="duration">{textContent.durationType || "Durée"}</option> <option value="duration">{textContent.durationType || "Durée"}</option>
<option value="daily_time">{textContent.dailyTimeType || "Heure précise"}</option> <option value="daily_time">{textContent.dailyTimeType || "Heure précise"}</option>
<option value="time_pattern">{textContent.timePatternType || "Pattern horaire"}</option> <option value="time_pattern">{textContent.timePatternType || "Pattern horaire"}</option>
@ -68,7 +68,7 @@ export function CountdownControls({ textContent, form, error, onChange, onSubmit
<span>{error}</span> <span>{error}</span>
</p> </p>
)} )}
<button className="primary" type="submit">{textContent.addCountdownButton || "Ajouter"}</button> <button className="nds-button" data-size="sm" data-variant="primary" type="submit">{textContent.addCountdownButton || "Ajouter"}</button>
</form> </form>
); );
} }
@ -102,7 +102,8 @@ export function CountdownList({ countdowns, nowMs, textContent, reorder, reorder
<div className={`tool-split-entry timer-entry has-single-action ${reorderEnabled ? "has-drag-handle" : ""} ${editingId !== countdown.id ? "has-inline-controls" : ""} ${(canReset || canToggleRefresh) && editingId !== countdown.id ? "has-inline-reset" : ""}`}> <div className={`tool-split-entry timer-entry has-single-action ${reorderEnabled ? "has-drag-handle" : ""} ${editingId !== countdown.id ? "has-inline-controls" : ""} ${(canReset || canToggleRefresh) && editingId !== countdown.id ? "has-inline-reset" : ""}`}>
{reorderEnabled && ( {reorderEnabled && (
<button <button
className="timer-drag-handle" className="timer-drag-handle nds-icon-button"
data-size="xs"
type="button" type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, countdown.id)} onPointerDown={(event) => reorder.itemReorder.startDrag(event, countdown.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${countdown.label}`} aria-label={`${textContent.reorderTitle || "Déplacer"} ${countdown.label}`}
@ -135,7 +136,8 @@ export function CountdownList({ countdowns, nowMs, textContent, reorder, reorder
<span className="timer-inline-controls"> <span className="timer-inline-controls">
{canReset && ( {canReset && (
<button <button
className="timer-inline-reset" className="timer-inline-reset nds-icon-button"
data-size="xxs"
type="button" type="button"
onClick={(event) => { onClick={(event) => {
event.stopPropagation(); event.stopPropagation();
@ -149,7 +151,8 @@ export function CountdownList({ countdowns, nowMs, textContent, reorder, reorder
)} )}
{canToggleRefresh && ( {canToggleRefresh && (
<button <button
className={`timer-inline-refresh-toggle ${countdown.autoRefresh ? "active" : ""} ${refreshBlocked ? "is-blocked" : ""}`} className={`timer-inline-refresh-toggle nds-switch ${countdown.autoRefresh ? "active" : ""} ${refreshBlocked ? "is-blocked" : ""}`}
data-size="xs"
type="button" type="button"
onClick={(event) => { onClick={(event) => {
event.stopPropagation(); event.stopPropagation();
@ -174,7 +177,7 @@ export function CountdownList({ countdowns, nowMs, textContent, reorder, reorder
</span> </span>
</span> </span>
)} )}
<button className="danger" type="button" onClick={() => onDelete(countdown.id)} aria-label={textContent.deleteTitle || "Supprimer"} title={textContent.deleteTitle || "Supprimer"}> <button className="nds-icon-button" data-size="xs" data-variant="danger" type="button" onClick={() => onDelete(countdown.id)} aria-label={textContent.deleteTitle || "Supprimer"} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>

View file

@ -286,12 +286,13 @@ export function TimerModule({ toolboxId, moduleId, context, textExchangeOpen })
</section> </section>
<section className={`tool-split-results timer-results-card ${data.scrollResults ? "is-scrollable" : ""}`}> <section className={`tool-split-results timer-results-card ${data.scrollResults ? "is-scrollable" : ""}`}>
<div className="tool-split-actions"> <div className="tool-split-actions">
<button className="tool-split-action-button danger" type="button" onClick={resetActiveResults} aria-label={textContent.resetTitle || "Réinitialiser"} title={textContent.resetTitle || "Réinitialiser"}> <button className="tool-split-action-button nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={resetActiveResults} aria-label={textContent.resetTitle || "Réinitialiser"} title={textContent.resetTitle || "Réinitialiser"}>
<Icon name="rubber" /> <Icon name="rubber" />
</button> </button>
{activeTab === "countdown" && ( {activeTab === "countdown" && (
<button <button
className={`tool-split-scroll-toggle timer-sort-toggle ${data.sortResults ? "active" : ""}`} className={`tool-split-scroll-toggle timer-sort-toggle nds-switch ${data.sortResults ? "active" : ""}`}
data-size="md"
type="button" type="button"
onClick={() => setSortResults(!data.sortResults)} onClick={() => setSortResults(!data.sortResults)}
aria-pressed={data.sortResults} aria-pressed={data.sortResults}
@ -303,7 +304,8 @@ export function TimerModule({ toolboxId, moduleId, context, textExchangeOpen })
</button> </button>
)} )}
<button <button
className={`tool-split-scroll-toggle timer-scroll-toggle ${data.scrollResults ? "active" : ""}`} className={`tool-split-scroll-toggle timer-scroll-toggle nds-switch ${data.scrollResults ? "active" : ""}`}
data-size="md"
type="button" type="button"
onClick={() => setScrollResults(!data.scrollResults)} onClick={() => setScrollResults(!data.scrollResults)}
aria-pressed={data.scrollResults} aria-pressed={data.scrollResults}

View file

@ -26,7 +26,8 @@ export function TimerAlertModeSwitch({ mode, blocked, textContent, onChange }) {
return ( return (
<div <div
className={`timer-alert-mode-switch ${blocked ? "is-blocked" : ""}`} className={`timer-alert-mode-switch nds-radio-button-group ${blocked ? "is-blocked" : ""}`}
data-size="xs"
role="group" role="group"
aria-label={textContent.alertModeTitle || "Mode d'alerte"} aria-label={textContent.alertModeTitle || "Mode d'alerte"}
title={blocked ? blockedTitle : undefined} title={blocked ? blockedTitle : undefined}
@ -63,11 +64,11 @@ export function TimePartsInput({ value, onChange, allowWildcard = false }) {
return ( return (
<div className="time-parts-input" role="group" aria-label="Format heure minute seconde"> <div className="time-parts-input" role="group" aria-label="Format heure minute seconde">
<input value={value.hours} onChange={(event) => updatePart("hours", event.target.value)} placeholder="hh" inputMode={allowWildcard ? "text" : "numeric"} /> <input type="text" value={value.hours} onChange={(event) => updatePart("hours", event.target.value)} placeholder="hh" inputMode={allowWildcard ? "text" : "numeric"} />
<span>:</span> <span>:</span>
<input value={value.minutes} onChange={(event) => updatePart("minutes", event.target.value)} placeholder="mm" inputMode={allowWildcard ? "text" : "numeric"} /> <input type="text" value={value.minutes} onChange={(event) => updatePart("minutes", event.target.value)} placeholder="mm" inputMode={allowWildcard ? "text" : "numeric"} />
<span>:</span> <span>:</span>
<input value={value.seconds} onChange={(event) => updatePart("seconds", event.target.value)} placeholder="ss" inputMode={allowWildcard ? "text" : "numeric"} /> <input type="text" value={value.seconds} onChange={(event) => updatePart("seconds", event.target.value)} placeholder="ss" inputMode={allowWildcard ? "text" : "numeric"} />
</div> </div>
); );
} }
@ -87,6 +88,7 @@ export function EditableTimerLabel({ value, placeholder, ariaLabel, children, on
<input <input
{...inlineEdit.getInputProps({ {...inlineEdit.getInputProps({
className: "tool-split-entry-label timer-lap-label", className: "tool-split-entry-label timer-lap-label",
type: "text",
placeholder, placeholder,
"aria-label": ariaLabel "aria-label": ariaLabel
})} })}

View file

@ -10,13 +10,13 @@ export function StopwatchControls({ textContent, elapsedMs, running, onStart, on
<span>{textContent.stopwatchTitle || "Chronomètre à étapes"}</span> <span>{textContent.stopwatchTitle || "Chronomètre à étapes"}</span>
<strong><TimerValue ms={elapsedMs} compact /></strong> <strong><TimerValue ms={elapsedMs} compact /></strong>
<div className="timer-actions"> <div className="timer-actions">
<button className="primary timer-action-button" type="button" onClick={running ? onPause : onStart} aria-label={running ? textContent.pauseButton || "Pause" : textContent.startButton || "Lancer"} title={running ? textContent.pauseButton || "Pause" : textContent.startButton || "Lancer"}> <button className="timer-action-button nds-icon-button" data-size="sm" data-variant="primary" type="button" onClick={running ? onPause : onStart} aria-label={running ? textContent.pauseButton || "Pause" : textContent.startButton || "Lancer"} title={running ? textContent.pauseButton || "Pause" : textContent.startButton || "Lancer"}>
<Icon name={running ? "pause-circle" : "play-circle"} /> <Icon name={running ? "pause-circle" : "play-circle"} />
</button> </button>
<button className="timer-action-button" type="button" onClick={onLap} disabled={!elapsedMs} aria-label={textContent.lapButton || "Étape"} title={textContent.lapButton || "Étape"}> <button className="timer-action-button nds-icon-button" data-size="sm" type="button" onClick={onLap} disabled={!elapsedMs} aria-label={textContent.lapButton || "Étape"} title={textContent.lapButton || "Étape"}>
<Icon name="record-circle" /> <Icon name="record-circle" />
</button> </button>
<button className="danger timer-action-button" type="button" onClick={onReset} aria-label={textContent.resetTitle || "Réinitialiser"} title={textContent.resetTitle || "Réinitialiser"}> <button className="timer-action-button nds-icon-button" data-size="sm" data-variant="danger" type="button" onClick={onReset} aria-label={textContent.resetTitle || "Réinitialiser"} title={textContent.resetTitle || "Réinitialiser"}>
<Icon name="stop-circle" /> <Icon name="stop-circle" />
</button> </button>
</div> </div>
@ -56,7 +56,7 @@ export function StopwatchLapList({ laps, textContent, onRename, onDelete }) {
</span> </span>
</span> </span>
)} )}
<button className="danger" type="button" onClick={() => onDelete(lap.id)} aria-label={textContent.deleteTitle || "Supprimer"} title={textContent.deleteTitle || "Supprimer"}> <button className="nds-icon-button" data-size="xs" data-variant="danger" type="button" onClick={() => onDelete(lap.id)} aria-label={textContent.deleteTitle || "Supprimer"} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" /> <Icon name="trash" />
</button> </button>
</div> </div>

View file

@ -188,7 +188,9 @@ function ModuleShell({ toolbox, module, context, reorder, itemGroup = "", onRena
<header> <header>
<div> <div>
<button <button
className="module-drag-handle" className="module-drag-handle nds-icon-button"
data-size="md"
data-variant="ghost"
type="button" type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, module.id)} onPointerDown={(event) => reorder.itemReorder.startDrag(event, module.id)}
aria-label={`Déplacer ${module.title || label}`} aria-label={`Déplacer ${module.title || label}`}
@ -204,7 +206,8 @@ function ModuleShell({ toolbox, module, context, reorder, itemGroup = "", onRena
<div> <div>
{definition.scrollable && ( {definition.scrollable && (
<button <button
className={`module-scroll-button ${scrollable ? "active" : ""}`} className={`module-scroll-button nds-switch ${scrollable ? "active" : ""}`}
data-size="lg"
onClick={() => onUpdateModule(module.id, (currentModule) => { onClick={() => onUpdateModule(module.id, (currentModule) => {
const nextModule = { ...currentModule }; const nextModule = { ...currentModule };
if (scrollable) { if (scrollable) {
@ -224,7 +227,8 @@ function ModuleShell({ toolbox, module, context, reorder, itemGroup = "", onRena
)} )}
{hasAddPanel && ( {hasAddPanel && (
<button <button
className={`module-edit-button ${editing ? "active" : ""}`} className={`module-edit-button nds-icon-button ${editing ? "active" : ""}`}
data-size="md"
onClick={() => setEditing((value) => { onClick={() => setEditing((value) => {
const nextEditing = !value; const nextEditing = !value;
if (nextEditing) setTextExchangeOpen(false); if (nextEditing) setTextExchangeOpen(false);
@ -239,7 +243,8 @@ function ModuleShell({ toolbox, module, context, reorder, itemGroup = "", onRena
)} )}
{hasTextExchange && ( {hasTextExchange && (
<button <button
className={`module-exchange-button ${textExchangeOpen ? "active" : ""}`} className={`module-exchange-button nds-icon-button ${textExchangeOpen ? "active" : ""}`}
data-size="md"
onClick={() => setTextExchangeOpen((value) => { onClick={() => setTextExchangeOpen((value) => {
const nextOpen = !value; const nextOpen = !value;
if (nextOpen) setEditing(false); if (nextOpen) setEditing(false);
@ -252,7 +257,7 @@ function ModuleShell({ toolbox, module, context, reorder, itemGroup = "", onRena
<Icon name="import" /> <Icon name="import" />
</button> </button>
)} )}
<button className="module-delete-button danger" onClick={() => onDelete(module.id)} aria-label={`Retirer ${module.title || label}`} data-toolbox-tutorial-target={module.type === "links" ? "deleteTool" : undefined} title="Retirer"> <button className="module-delete-button nds-icon-button" data-size="md" data-variant="danger" onClick={() => onDelete(module.id)} aria-label={`Retirer ${module.title || label}`} data-toolbox-tutorial-target={module.type === "links" ? "deleteTool" : undefined} title="Retirer">
<span className="ui-icon ui-icon-trash" aria-hidden="true" /> <span className="ui-icon ui-icon-trash" aria-hidden="true" />
</button> </button>
</div> </div>

View file

@ -6,7 +6,7 @@ export const HOUR_HALF_SLOT_COUNT = 2;
export const WEEK_SLOT_COUNT = DAY_HOUR_COUNT * HOUR_HALF_SLOT_COUNT; export const WEEK_SLOT_COUNT = DAY_HOUR_COUNT * HOUR_HALF_SLOT_COUNT;
export const WEEK_HOUR_ROW_HEIGHT = 27; export const WEEK_HOUR_ROW_HEIGHT = 27;
export const WEEK_HIDDEN_ROW_HEIGHT = 20; export const WEEK_HIDDEN_ROW_HEIGHT = 20;
export const WEEK_COMPACT_EVENT_HEIGHT = WEEK_HOUR_ROW_HEIGHT / HOUR_HALF_SLOT_COUNT; export const WEEK_COMPACT_EVENT_HEIGHT = 22;
export const WEEKDAY_LABELS = ["lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"]; export const WEEKDAY_LABELS = ["lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"];
function occurrencesOverlap(first, second) { function occurrencesOverlap(first, second) {

View file

@ -6,6 +6,15 @@ export const CALENDAR_MONTH_REPEAT_MODES = ["none", "weekly", "monthly"];
export const CALENDAR_ALERT_MODES = ["off", "visible", "site"]; export const CALENDAR_ALERT_MODES = ["off", "visible", "site"];
export const CALENDAR_ALERT_OFFSETS = [10, 5, 0]; export const CALENDAR_ALERT_OFFSETS = [10, 5, 0];
export const CALENDAR_COLORS = ["violet", "gold", "cyan", "pink", "green", "gray"]; export const CALENDAR_COLORS = ["violet", "gold", "cyan", "pink", "green", "gray"];
export const CALENDAR_COLOR_VALUES = {
violet: "#8b5cf6",
gold: "#f6c453",
cyan: "#22d3ee",
pink: "#d946ef",
green: "#22c55e",
gray: "#7d879f"
};
export const CALENDAR_DARK_TEXT_COLORS = new Set(["gold", "cyan", "green"]);
export const WEEK_SLOT_MINUTES = 30; export const WEEK_SLOT_MINUTES = 30;
export const WEEK_TIME_INPUT_STEP_MINUTES = 5; export const WEEK_TIME_INPUT_STEP_MINUTES = 5;
export const WEEK_SLOT_COUNT = 48; export const WEEK_SLOT_COUNT = 48;
@ -13,6 +22,14 @@ export const DAY_MS = 24 * 60 * 60 * 1000;
export const MINUTE_MS = 60 * 1000; export const MINUTE_MS = 60 * 1000;
export const WEEK_DAY_COUNT = 7; export const WEEK_DAY_COUNT = 7;
export function getCalendarColorValue(color) {
return CALENDAR_COLOR_VALUES[color] || CALENDAR_COLOR_VALUES.violet;
}
export function getCalendarEventSurfaceVariant(color) {
return CALENDAR_DARK_TEXT_COLORS.has(color) ? "dark" : "default";
}
export function pad2(value) { export function pad2(value) {
return String(value).padStart(2, "0"); return String(value).padStart(2, "0");
} }

View file

@ -112,7 +112,7 @@ export function AboutPage({ siteContent }) {
</article> </article>
))} ))}
</div> </div>
<a className="button primary about-tools-library-button" href="#/library"><Icon name="book" />{content.toolsLibraryLink}</a> <a className="about-tools-library-button nds-button" data-size="md" data-variant="primary" href="#/library"><Icon name="book" />{content.toolsLibraryLink}</a>
</section> </section>
{content.contribute && ( {content.contribute && (
<section className="about-contribute-section nebula-panel" id="contribuer" aria-labelledby="about-contribute-title"> <section className="about-contribute-section nebula-panel" id="contribuer" aria-labelledby="about-contribute-title">

View file

@ -0,0 +1,641 @@
// Rôle : expose en développement un catalogue vivant et interactif des contrôles NDS.
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"],
button: ["xs", "sm", "md"],
iconButton: ["micro", "xxs", "xs", "sm", "md"],
group: ["xs", "sm", "md"],
switch: ["xs", "sm", "md", "lg"],
none: ["default"]
};
const VARIANT_VALUES = {
button: ["secondary", "primary", "ghost", "dashed", "danger"],
iconButton: ["secondary", "primary", "ghost", "panel-toggle", "danger"],
selectTrigger: ["default", "icon-picker"],
textarea: ["default", "ghost"],
swatch: ["color", "rainbow"],
event: ["default", "dark"],
none: ["default"]
};
const EDGE_VALUES = ["none", "left", "middle", "right", "top", "center", "bottom", "top-left", "top-right", "bottom-right", "bottom-left"];
const BORDER_VALUES = ["default", "joined", "none"];
const SCROLL_AXIS_VALUES = ["horizontal", "vertical"];
const EVENT_COLOR_VALUES = ["#8b5cf6", "#f6c453", "#22d3ee", "#d946ef", "#22c55e", "#7d879f"];
const STATE_VALUES = ["normal", "active", "disabled"];
const FIELD_STATE_VALUES = ["normal", "disabled"];
const DEFAULT_STATE_VALUES = ["normal"];
const CONTENT_VALUES = ["text", "icon", "mixed"];
const MIX_TAG_TOKENS = [
{ value: "base", label: "Base", title: "Valeur base" },
{ value: "bonus", label: "Bonus", title: "Valeur bonus" }
];
const COLOR_TOKENS = [
["Page", "var(--color-bg-page)"],
["Surface", "var(--color-bg-surface)"],
["Primary", "var(--color-primary)"],
["Gold", "var(--color-accent-gold)"],
["Cyan", "var(--color-accent-cyan)"],
["Danger", "var(--color-danger)"]
];
const RADIO_OPTIONS = {
text: [
{ value: "a", label: "A" },
{ value: "b", label: "B" },
{ value: "c", label: "C" }
],
icon: [
{ value: "rows", label: "Rows", icon: "rows" },
{ value: "columns", label: "Columns", icon: "columns" }
],
mixed: [
{ value: "one", label: "One", icon: "eye-open" },
{ value: "two", label: "Two", icon: "home" },
{ value: "three", label: "Three", icon: "close" }
]
};
const COMPONENTS = [
{
id: "nds-input",
name: "Input",
className: "nds-input",
sizes: SIZE_VALUES.field,
variants: VARIANT_VALUES.none,
attributes: { "data-size": SIZE_VALUES.field, "data-state": FIELD_STATE_VALUES },
render: ({ attributes, state, label, size }) => <input className="nds-input" {...attributes} type="text" defaultValue={state === "disabled" ? "Disabled" : size.toUpperCase()} disabled={state === "disabled"} aria-label={label} />
},
{
id: "nds-select",
name: "Select",
className: "nds-select",
sizes: SIZE_VALUES.field,
variants: VARIANT_VALUES.none,
attributes: { "data-size": SIZE_VALUES.field, "data-state": FIELD_STATE_VALUES },
render: ({ attributes, state, label }) => (
<select className="nds-select" {...attributes} defaultValue="b" disabled={state === "disabled"} aria-label={label}>
<option value="a">Option A</option>
<option value="b">{state === "disabled" ? "Disabled" : "Option B"}</option>
<option value="c">Option C</option>
</select>
)
},
{
id: "nds-select-trigger",
name: "Select Trigger",
className: "nds-select-trigger",
sizes: SIZE_VALUES.field,
variants: VARIANT_VALUES.selectTrigger,
attributes: { "data-size": SIZE_VALUES.field, "data-variant": VARIANT_VALUES.selectTrigger, "data-state": STATE_VALUES },
render: ({ attributes, state, variant }) => (
<button className="nds-select-trigger" {...attributes} type="button" disabled={state === "disabled"} aria-pressed={state === "active"}>
<Icon name={variant === "icon-picker" ? "shield" : "calendar"} />
{variant !== "icon-picker" && <span>{state === "disabled" ? "Disabled" : "27 août"}</span>}
</button>
)
},
{
id: "nds-time",
name: "Time Input",
className: "nds-time",
sizes: SIZE_VALUES.field,
variants: VARIANT_VALUES.none,
attributes: { "data-size": SIZE_VALUES.field, "data-state": FIELD_STATE_VALUES },
render: ({ attributes, state, label }) => <input className="nds-time" {...attributes} type="time" defaultValue="21:30" disabled={state === "disabled"} aria-label={label} />
},
{
id: "nds-textarea",
name: "Textarea",
className: "nds-textarea",
sizes: SIZE_VALUES.field,
variants: VARIANT_VALUES.textarea,
attributes: { "data-size": SIZE_VALUES.field, "data-variant": VARIANT_VALUES.textarea, "data-state": FIELD_STATE_VALUES },
render: ({ attributes, state, variant, label }) => <textarea className="nds-textarea" {...attributes} defaultValue={state === "disabled" ? "Disabled" : `${variant || "default"}\nscrollbar commune\nresize`} disabled={state === "disabled"} aria-label={label} />
},
{
id: "nds-inline-input",
name: "Inline Input",
className: "nds-inline-input",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
attributes: { "data-state": FIELD_STATE_VALUES },
render: ({ state, label }) => <input className="nds-inline-input" type="text" defaultValue={state === "disabled" ? "Disabled" : "Inline edit"} disabled={state === "disabled"} aria-label={label} />
},
{
id: "nds-button",
name: "Button",
className: "nds-button",
sizes: SIZE_VALUES.button,
variants: VARIANT_VALUES.button,
configurable: { "data-edge": EDGE_VALUES, "data-border": BORDER_VALUES },
attributes: { "data-size": SIZE_VALUES.button, "data-variant": VARIANT_VALUES.button, "data-edge": EDGE_VALUES, "data-border": BORDER_VALUES, "data-state": STATE_VALUES },
render: ({ attributes, state, variant, label }) => <button className="nds-button" {...attributes} type="button" disabled={state === "disabled"} aria-pressed={state === "active"}>{variant === "secondary" ? "secondary" : variant || label}</button>
},
{
id: "nds-icon-button",
name: "Icon Button",
className: "nds-icon-button",
sizes: SIZE_VALUES.iconButton,
variants: VARIANT_VALUES.iconButton,
configurable: { "data-edge": EDGE_VALUES, "data-border": BORDER_VALUES },
attributes: { "data-size": SIZE_VALUES.iconButton, "data-variant": VARIANT_VALUES.iconButton, "data-edge": EDGE_VALUES, "data-border": BORDER_VALUES, "data-state": STATE_VALUES },
render: ({ attributes, state, variant, label }) => {
const icon = variant === "danger" ? "trash" : variant === "primary" ? "export" : "settings";
return <button className="nds-icon-button" {...attributes} type="button" disabled={state === "disabled"} aria-pressed={state === "active"} aria-label={label}><Icon name={icon} /></button>;
}
},
{
id: "nds-radio-button-group",
name: "Radio Button Group",
className: "nds-radio-button-group",
sizes: SIZE_VALUES.group,
variants: VARIANT_VALUES.none,
configurable: { content: CONTENT_VALUES },
attributes: { "data-size": SIZE_VALUES.group, content: CONTENT_VALUES, "data-state": ["normal", "disabled"] },
render: ({ attributes, config, state }) => state === "disabled"
? <div className="nds-radio-button-group" {...attributes} aria-disabled="true"><span>A</span><span>B</span></div>
: <InteractiveRadioButtonGroup attributes={attributes} options={RADIO_OPTIONS[config.content || "text"]} iconOnly={config.content === "icon"} />
},
{
id: "nds-checkbox",
name: "Checkbox",
className: "nds-checkbox",
sizes: ["md", "sm", "xs"],
variants: VARIANT_VALUES.none,
attributes: { "data-size": ["md", "sm", "xs"], "data-state": STATE_VALUES },
render: ({ attributes, state, label }) => <input className="nds-checkbox" {...attributes} type="checkbox" defaultChecked={state === "active"} disabled={state === "disabled"} aria-label={label} />
},
{
id: "nds-quantity-stepper",
name: "Quantity Stepper",
className: "nds-quantity-stepper",
sizes: SIZE_VALUES.group,
variants: VARIANT_VALUES.none,
configurable: { content: ["value", "input"] },
attributes: { "data-size": SIZE_VALUES.group, content: ["value", "input"] },
render: ({ attributes, config }) => <InteractiveStepper attributes={attributes} input={config.content === "input"} />
},
{
id: "nds-switch",
name: "Switch",
className: "nds-switch",
sizes: SIZE_VALUES.switch,
variants: VARIANT_VALUES.none,
attributes: { "data-size": SIZE_VALUES.switch, "data-state": STATE_VALUES },
render: ({ attributes, state }) => state === "disabled"
? <button className="nds-switch" {...attributes} type="button" disabled aria-pressed="true"><Icon name="scrollable" /><i aria-hidden="true" /></button>
: <InteractiveSwitch attributes={attributes} initialActive={state === "active"} />
},
{
id: "nds-native-switch",
name: "Native Switch",
className: "nds-native-switch",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
attributes: { "data-state": STATE_VALUES },
render: ({ state }) => <NativeSwitchPreview disabled={state === "disabled"} initialChecked={state === "active"} />
},
{
id: "nds-swatch",
name: "Swatch",
className: "nds-swatch",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.swatch,
attributes: { "data-variant": VARIANT_VALUES.swatch, "data-state": STATE_VALUES },
render: ({ attributes, state, variant, label }) => <button className="nds-swatch" {...attributes} type="button" style={variant === "rainbow" ? undefined : { "--swatch-color": "#22d3ee" }} disabled={state === "disabled"} aria-pressed={state === "active"} aria-label={label} />
},
{
id: "nds-compound-toggle-field",
name: "Compound Toggle Field",
className: "nds-compound-toggle-field",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
attributes: { "data-state": STATE_VALUES },
render: ({ state }) => <div className="nds-compound-toggle-field" aria-disabled={state === "disabled"}><button type="button" disabled={state === "disabled"} aria-pressed={state === "active"}>Hold</button><input className="nds-compound-toggle-field-input" type="number" defaultValue="2" disabled={state === "disabled"} aria-label="Hold seconds" /></div>
},
{
id: "nds-list-row",
name: "List Row",
className: "nds-list-row",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
attributes: { "data-state": ["normal", "active", "disabled"] },
render: ({ state }) => <div className={`nds-list-row ${state === "active" ? "is-active" : ""} ${state === "disabled" ? "is-disabled" : ""}`.trim()}><button type="button" disabled={state === "disabled"} aria-pressed={state === "active"}>{state === "active" ? "Page active" : "Page"}</button></div>
},
{
id: "nds-large-icon-picker-trigger",
name: "Large Icon Picker Trigger",
className: "nds-large-icon-picker-trigger",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
attributes: { "data-state": STATE_VALUES },
render: ({ state, label }) => <button className="nds-large-icon-picker-trigger" type="button" disabled={state === "disabled"} aria-pressed={state === "active"} aria-label={label}><Icon name="toolbox" /></button>
},
{
id: "nds-event-surface",
name: "Event Surface",
className: "nds-event-surface",
sizes: ["xs", "sm", "md"],
variants: VARIANT_VALUES.event,
configurable: { "data-color": EVENT_COLOR_VALUES },
attributes: { "data-size": ["xs", "sm", "md"], "data-variant": VARIANT_VALUES.event, "data-color": EVENT_COLOR_VALUES },
render: ({ attributes, config, variant, size }) => (
<div className="nds-event-surface" {...attributes} style={{ "--nds-event-color": config["data-color"], height: size === "xs" ? "10px" : size === "sm" ? "22px" : "44px" }}>
<button type="button"><strong>{variant === "dark" ? "Market" : "Raid"}</strong><span>21:30 - 22:45</span></button>
</div>
)
},
{
id: "nds-scrollbox",
name: "Scrollbox",
className: "nds-scrollbox",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
configurable: { "data-axis": SCROLL_AXIS_VALUES },
attributes: { "data-axis": SCROLL_AXIS_VALUES },
render: ({ attributes, config }) => config["data-axis"] === "horizontal"
? <div className="nds-scrollbox" {...attributes}><div className="nds-scrollbox-content-wide"><span>Alpha sector</span><span>Beta sector</span><span>Gamma sector</span><span>Delta sector</span><span>Epsilon sector</span><span>Zeta sector</span><span>Eta sector</span></div></div>
: <div className="nds-scrollbox" {...attributes}><p>Zone scrollable</p><p>Track transparent</p><p>Thumb commun</p><p>Contenu supplémentaire</p><p>Encore une ligne</p></div>
}
];
const NAV_ITEMS = [
{ id: "foundations", label: "Foundations" },
...COMPONENTS.map(({ id, name }) => ({ id, label: name })),
{ id: "date-picker-buttons", label: "Date Picker Buttons" },
{ id: "specialized-components", label: "Specialized Components" },
{ id: "audit", label: "Audit" }
];
function dataProps(attributes = {}) {
return Object.fromEntries(Object.entries(attributes).filter(([, value]) => value !== undefined && value !== null && value !== ""));
}
function attributeLabel(attributes = {}) {
const entries = Object.entries(attributes).filter(([, value]) => value && value !== "default" && value !== "secondary" && value !== "md" && value !== "vertical" && value !== "none" && value !== "normal");
return entries.length ? entries.map(([key, value]) => `${key}="${value}"`).join(" ") : "default";
}
function DesignSystemNav() {
const scrollToSection = (id) => {
document.getElementById(id)?.scrollIntoView({ behavior: "smooth", block: "start" });
};
return (
<aside className="design-system-nav" aria-label="Navigation design system">
<div>
<p className="eyebrow">Sokko G</p>
<h1>Nebula Design System</h1>
<p>Catalogue vivant des classes publiques, attributs, tailles, variants et états des contrôles interactifs Nebula.</p>
</div>
<div className="design-system-token-stack">
<SpecPill label="Theme" value="Nebula Command" />
<SpecPill label="Focus" value="gold outline" />
<SpecPill label="API" value="class + data-*" />
</div>
<nav>
{NAV_ITEMS.map((item) => <button key={item.id} type="button" onClick={() => scrollToSection(item.id)}>{item.label}</button>)}
</nav>
</aside>
);
}
function SpecPill({ label, value }) {
return <span className="design-system-spec-pill"><b>{label}</b><span>{value}</span></span>;
}
function ComponentSection({ id, title, meta, children }) {
return (
<section id={id} className="design-system-section">
<header>
<div>
<p className="eyebrow">{meta}</p>
<h2>{title}</h2>
</div>
</header>
{children}
</section>
);
}
function AttributeSummary({ component }) {
return (
<article className="design-system-component-intro">
<div className="design-system-doc-summary">
<span>Element</span>
<strong>{component.name}</strong>
<span>Classe</span>
<code>{component.className}</code>
</div>
<div className="design-system-attributes-doc">
<span>Attributes</span>
<dl>
{Object.entries(component.attributes).map(([name, values]) => (
<div key={name}>
<dt><code>{name}</code></dt>
<dd>{values.map((value) => <code key={value}>{value}</code>)}</dd>
</div>
))}
</dl>
</div>
</article>
);
}
function VariantMatrix({ columns, rows, controls, children }) {
return (
<article className="design-system-matrix">
{controls && <header>{controls}</header>}
<div className="design-system-matrix-grid" style={{ "--nds-matrix-columns": columns.length }}>
<div className="design-system-matrix-corner" aria-hidden="true" />
{columns.map((column) => <div key={column} className="design-system-matrix-heading">{column}</div>)}
{children || rows.map((row) => <StatePreview key={row.label} label={row.label} cells={row.cells} />)}
</div>
</article>
);
}
function StatePreview({ label, cells }) {
const [attribute, value] = label.includes("=") ? label.split("=") : [null, label];
return (
<>
<div className="design-system-matrix-row-label">
{attribute ? (
<>
<span>{attribute}</span>
<strong>{value}</strong>
</>
) : <strong>{value}</strong>}
</div>
{cells.map((cell, index) => <div key={`${label}-${index}`} className="design-system-matrix-cell">{cell}</div>)}
</>
);
}
function ConfigSelect({ name, values, value, onChange }) {
return (
<label className="design-system-config-field">
<span>{name}</span>
<select className="nds-select" data-size="xs" value={value} onChange={(event) => onChange(name, event.target.value)}>
{values.map((option) => <option key={option} value={option}>{option}</option>)}
</select>
</label>
);
}
function ControlSample({ children }) {
return (
<div className="design-system-control-sample">
<div className="design-system-sample-preview">{children}</div>
</div>
);
}
function ComponentCatalogSection({ component }) {
const [config, setConfig] = useState(() => Object.fromEntries(Object.entries(component.configurable || {}).map(([name, values]) => [name, values[0]])));
const sizes = component.sizes;
const variants = component.variants;
const stateValues = component.attributes["data-state"] || DEFAULT_STATE_VALUES;
const stateRows = stateValues.filter((state) => state !== "normal");
const hasConfigurableAttributes = Boolean(component.configurable && Object.keys(component.configurable).length);
const defaultStateVariant = variants.includes("secondary") ? "secondary" : variants[0];
const setConfigValue = (name, value) => setConfig((current) => ({ ...current, [name]: value }));
const renderCell = ({ size, variant, state = "normal" }) => {
const attrs = {
...Object.fromEntries(Object.entries(config).filter(([name]) => name.startsWith("data-"))),
"data-size": size,
"data-variant": variant,
"data-edge": config["data-edge"],
"data-border": config["data-border"],
"data-axis": config["data-axis"],
"data-state": state
};
const attributes = dataProps(attrs);
const label = `${component.className} ${attributeLabel(attributes)}`;
return <ControlSample key={`${variant}-${size}-${state}`} label={label}>{component.render({ attributes, config, state, variant, label, size })}</ControlSample>;
};
return (
<ComponentSection id={component.id} title={component.name} meta="NDS element">
<AttributeSummary component={component} />
<VariantMatrix
title="data-size x data-variant"
note="Les colonnes pilotent data-size. Les lignes affichent d'abord data-variant, puis data-state. Le formulaire applique les autres attributs à toute la matrice."
columns={sizes.map((size) => size === "default" ? "default" : `data-size=${size}`)}
controls={hasConfigurableAttributes && (
<div className="design-system-config-panel">
{Object.entries(component.configurable).map(([name, values]) => <ConfigSelect key={name} name={name} values={values} value={config[name]} onChange={setConfigValue} />)}
</div>
)}
>
{variants.map((variant) => (
<StatePreview
key={variant}
label={variant === "default" ? "default" : `data-variant=${variant}`}
cells={sizes.map((size) => renderCell({ size, variant }))}
/>
))}
{stateRows.length > 0 && <div className="design-system-matrix-separator">data-state</div>}
{stateRows.map((state) => (
<StatePreview
key={state}
label={`data-state=${state}`}
cells={sizes.map((size) => renderCell({ size, variant: defaultStateVariant, state }))}
/>
))}
</VariantMatrix>
</ComponentSection>
);
}
function ToggleButton({ children, className = "nds-button", attributes, ariaLabel }) {
const [pressed, setPressed] = useState(false);
return (
<button className={className} {...dataProps(attributes)} type="button" aria-pressed={pressed} aria-label={ariaLabel} onClick={() => setPressed((value) => !value)}>
{children}
</button>
);
}
function InteractiveRadioButtonGroup({ attributes, options, iconOnly = false }) {
const [value, setValue] = useState(options[0]?.value);
return (
<div className="nds-radio-button-group" {...dataProps(attributes)} role="radiogroup" aria-label="Groupe interactif">
{options.map((option) => {
const active = option.value === value;
return (
<button key={option.value} className={active ? "is-active active" : ""} type="button" role="radio" aria-checked={active} aria-label={iconOnly ? option.label : undefined} onClick={() => setValue(option.value)}>
{option.icon && <Icon name={option.icon} />}
{!iconOnly && <span>{option.label}</span>}
</button>
);
})}
</div>
);
}
function InteractiveStepper({ attributes, input = false }) {
const [value, setValue] = useState(2);
const increment = (step) => setValue((current) => Math.min(20, Math.max(0, current + step)));
return (
<span className="nds-quantity-stepper" {...dataProps(attributes)} aria-label="Stepper interactif">
<button type="button" aria-label="Retirer" onClick={() => increment(-1)}><span aria-hidden="true">-</span></button>
<span className="nds-quantity-stepper-value">
{input ? <input className="nds-quantity-stepper-input" type="number" min="0" max="20" value={value} onChange={(event) => setValue(Math.min(20, Math.max(0, Number.parseInt(event.target.value, 10) || 0)))} aria-label="Quantité actuelle" /> : value}
<span>/ 20</span>
</span>
<button type="button" aria-label="Ajouter" onClick={() => increment(1)}><Icon name="add" /></button>
</span>
);
}
function InteractiveSwitch({ attributes, initialActive = false }) {
const [active, setActive] = useState(initialActive);
return (
<button className="nds-switch" {...dataProps(attributes)} type="button" aria-pressed={active} onClick={() => setActive((value) => !value)}>
<Icon name="scrollable" />
<i aria-hidden="true" />
</button>
);
}
function NativeSwitchPreview({ disabled = false, initialChecked = false }) {
const [checked, setChecked] = useState(initialChecked);
return (
<label className="nds-native-switch">
<input type="checkbox" checked={checked} disabled={disabled} onChange={(event) => setChecked(event.target.checked)} aria-label="Inclure" />
<span aria-hidden="true" />
<b>{disabled ? "Disabled" : "Inclus"}</b>
</label>
);
}
function DatePickerPreview() {
const [activeDay, setActiveDay] = useState(2);
const days = Array.from({ length: 35 }, (_, index) => {
const day = index - 2;
return { id: index, label: day > 0 && day <= 31 ? day : "", outside: day <= 0 || day > 31 };
});
return (
<div className="nds-date-picker-preview" role="group" aria-label="Mini calendrier interactif">
{["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"].map((day) => <span key={day} className="nds-date-picker-preview-day">{day}</span>)}
{days.map((day) => (
<button key={day.id} className={`${day.label === activeDay ? "active" : ""} ${day.outside ? "is-outside" : ""}`.trim()} type="button" aria-pressed={day.label === activeDay} disabled={day.outside} onClick={() => setActiveDay(day.label)}>
{day.label}
</button>
))}
</div>
);
}
function FoundationsSection() {
return (
<ComponentSection id="foundations" title="Foundations" meta="tokens">
<div className="design-system-foundations">
<article className="design-system-type-card"><p>Typography</p><strong>Aa</strong><span>Inter, system-ui</span></article>
<article className="design-system-color-card">
<p>Colors</p>
<div className="design-system-swatches">{COLOR_TOKENS.map(([label, color]) => <span key={label} style={{ "--nds-swatch-color": color }} title={label} />)}</div>
</article>
<article className="design-system-radius-card">
<p>Radius</p>
<div><span style={{ borderRadius: "var(--radius-sm)" }}>sm</span><span style={{ borderRadius: "var(--radius-md)" }}>md</span><span style={{ borderRadius: "var(--radius-lg)" }}>lg</span></div>
</article>
<article className="design-system-focus-card"><p>Focus</p><button className="nds-button" data-variant="primary" type="button">Focus visible</button></article>
</div>
</ComponentSection>
);
}
function DatePickerButtonsSection() {
return (
<ComponentSection id="date-picker-buttons" title="Date Picker Buttons" meta="internal NDS pattern">
<VariantMatrix title="month grid" note="Boutons internes du mini calendrier, affichés avec un mois complet." columns={["interactive"]} rows={[{ label: "buttons", cells: [<ControlSample label="date-picker-button mixin"><DatePickerPreview /></ControlSample>] }]} />
</ComponentSection>
);
}
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"]}
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>
]
}]}
/>
</ComponentSection>
);
}
function AuditSection() {
return (
<ComponentSection id="audit" title="Audit" meta="api conventions">
<VariantMatrix
title="NDS Attribute API"
note="Une classe publique correspond à une famille. Les attributs data-* configurent les variations. Les états restent natifs, ARIA ou métier."
columns={["family class", "configuration", "state"]}
rows={[
{
label: "rule",
cells: [
<code>className=&quot;nds-button&quot;</code>,
<code>data-size data-variant data-edge data-border data-axis</code>,
<code>disabled aria-pressed .active .is-active</code>
]
},
{
label: "global",
cells: [
<code>Home / About / Games CTAs</code>,
<code>Shell / drawer / modales / filters</code>,
<code>Image viewer / library actions</code>
]
},
{
label: "sample",
cells: [
<button className="nds-icon-button" data-size="sm" data-variant="primary" type="button" aria-label="Exemple"><Icon name="export" /></button>,
<button className="nds-button" data-size="xs" data-variant="dashed" data-edge="left" data-border="joined" type="button"><Icon name="add" />XS</button>,
<ToggleButton attributes={{ "data-variant": "primary" }}>Actif</ToggleButton>
]
}
]}
/>
</ComponentSection>
);
}
export function DesignSystemPage() {
return (
<div className="design-system-page">
<DesignSystemNav />
<main className="design-system-main">
<FoundationsSection />
{COMPONENTS.map((component) => <ComponentCatalogSection key={component.id} component={component} />)}
<DatePickerButtonsSection />
<SpecializedComponentsSection />
<AuditSection />
</main>
</div>
);
}

View file

@ -47,7 +47,7 @@ export function HomeOnboardingSection({ content, className = "", onCreateToolbox
</ol> </ol>
{onCreateToolbox && content.primaryAction && ( {onCreateToolbox && content.primaryAction && (
<div className="home-onboarding-steps-action"> <div className="home-onboarding-steps-action">
<button className="primary home-onboarding-create-button" type="button" onClick={onCreateToolbox}> <button className="home-onboarding-create-button nds-button" data-size="md" data-variant="primary" type="button" onClick={onCreateToolbox}>
{content.primaryAction} {content.primaryAction}
</button> </button>
</div> </div>

View file

@ -15,8 +15,8 @@ export function HomePage({ siteContent, toolboxes, actions }) {
<h1>{content.hero.title}</h1> <h1>{content.hero.title}</h1>
<p>{content.hero.description}</p> <p>{content.hero.description}</p>
<div className="actions"> <div className="actions">
<a className="button primary" href="#/toolboxes">{content.hero.primaryAction}</a> <a className="nds-button" data-size="md" data-variant="primary" href="#/toolboxes">{content.hero.primaryAction}</a>
<a className="button" href="#/games">{content.hero.secondaryAction}</a> <a className="nds-button" data-size="md" href="#/games">{content.hero.secondaryAction}</a>
</div> </div>
</div> </div>
<div className="home-stats hero-stats" aria-label="Statistiques locales du site"> <div className="home-stats hero-stats" aria-label="Statistiques locales du site">

View file

@ -356,7 +356,8 @@ function LibraryToolExample({ module, sourceData, description, categoryTitle, do
<div className="library-example-actions"> <div className="library-example-actions">
{definition.scrollable && ( {definition.scrollable && (
<button <button
className={`module-scroll-button ${scrollable ? "active" : ""}`} className={`module-scroll-button nds-switch ${scrollable ? "active" : ""}`}
data-size="md"
type="button" type="button"
onClick={() => setScrollable((value) => !value)} onClick={() => setScrollable((value) => !value)}
aria-label={`${scrollable ? "Désactiver" : "Activer"} le scroll de ${title}`} aria-label={`${scrollable ? "Désactiver" : "Activer"} le scroll de ${title}`}
@ -369,7 +370,8 @@ function LibraryToolExample({ module, sourceData, description, categoryTitle, do
)} )}
{definition.editable && definition.addable !== false && ( {definition.editable && definition.addable !== false && (
<button <button
className={`module-edit-button ${editing ? "active" : ""}`} className={`module-edit-button nds-icon-button ${editing ? "active" : ""}`}
data-size="sm"
type="button" type="button"
onClick={() => setEditing((value) => !value)} onClick={() => setEditing((value) => !value)}
aria-label={`${editing ? "Masquer" : "Afficher"} l'ajout de ${title}`} aria-label={`${editing ? "Masquer" : "Afficher"} l'ajout de ${title}`}
@ -379,7 +381,7 @@ function LibraryToolExample({ module, sourceData, description, categoryTitle, do
<Icon name="add" /> <Icon name="add" />
</button> </button>
)} )}
<button className="library-reset-button" type="button" onClick={() => context.setModuleData(LIBRARY_TOOLBOX_ID, module.id, cloneData(sourceData))} aria-label={`Réinitialiser ${title}`} title="Réinitialiser l'exemple"> <button className="library-reset-button nds-icon-button" data-size="sm" type="button" onClick={() => context.setModuleData(LIBRARY_TOOLBOX_ID, module.id, cloneData(sourceData))} aria-label={`Réinitialiser ${title}`} title="Réinitialiser l'exemple">
<Icon name="refresh" /> <Icon name="refresh" />
</button> </button>
</div> </div>
@ -506,7 +508,12 @@ function ControlPreview({ item }) {
return ( return (
<span className="library-control-preview" aria-hidden="true"> <span className="library-control-preview" aria-hidden="true">
{buttons.map((button, index) => ( {buttons.map((button, index) => (
<span className={`notepad-toolbar-button ${button.label && String(button.label).length > 2 ? "is-wide" : ""} ${button.active ? "active" : ""} ${button.danger ? "danger" : ""}`} key={`${button.label || button.icon || index}-${index}`}> <span
className={`notepad-toolbar-button nds-icon-button ${button.label && String(button.label).length > 2 ? "is-wide" : ""} ${button.active ? "active" : ""}`.trim()}
data-size="sm"
data-variant={button.danger ? "danger" : undefined}
key={`${button.label || button.icon || index}-${index}`}
>
{button.icon ? <Icon name={button.icon} /> : button.label} {button.icon ? <Icon name={button.icon} /> : button.label}
</span> </span>
))} ))}
@ -516,7 +523,7 @@ function ControlPreview({ item }) {
return ( return (
<span className="library-control-preview" aria-hidden="true"> <span className="library-control-preview" aria-hidden="true">
<span className={`notepad-toolbar-button ${preview.active ? "active" : ""} ${preview.danger ? "danger" : ""}`}> <span className={`notepad-toolbar-button nds-icon-button ${preview.active ? "active" : ""}`.trim()} data-size="sm" data-variant={preview.danger ? "danger" : undefined}>
<Icon name={preview.icon || item.icon || "settings"} /> <Icon name={preview.icon || item.icon || "settings"} />
</span> </span>
</span> </span>
@ -582,7 +589,7 @@ function ToolAdvancedFeatureList({ docs, heading, copyLabel, copySuccess, contex
<p>{docs.importFormat.text}</p> <p>{docs.importFormat.text}</p>
<div className="library-tool-import-example"> <div className="library-tool-import-example">
<pre><code>{docs.importFormat.example}</code></pre> <pre><code>{docs.importFormat.example}</code></pre>
<button type="button" onClick={copyExample} title={copyLabel || "Copier l'exemple"}> <button className="nds-button" data-size="sm" type="button" onClick={copyExample} title={copyLabel || "Copier l'exemple"}>
<Icon name="copy" /> <Icon name="copy" />
<span>{copied ? copySuccess || "Exemple copié." : copyLabel || "Copier l'exemple"}</span> <span>{copied ? copySuccess || "Exemple copié." : copyLabel || "Copier l'exemple"}</span>
</button> </button>

View file

@ -9,6 +9,9 @@ const GamesPage = lazy(() => import("../features/games/GamesPage.jsx").then((mod
const LibraryPage = lazy(() => import("../pages/LibraryPage.jsx").then((module) => ({ default: module.LibraryPage }))); const LibraryPage = lazy(() => import("../pages/LibraryPage.jsx").then((module) => ({ default: module.LibraryPage })));
const ToolboxPage = lazy(() => import("../features/toolboxes/ToolboxPages.jsx").then((module) => ({ default: module.ToolboxPage }))); const ToolboxPage = lazy(() => import("../features/toolboxes/ToolboxPages.jsx").then((module) => ({ default: module.ToolboxPage })));
const ToolboxesPage = lazy(() => import("../features/toolboxes/ToolboxPages.jsx").then((module) => ({ default: module.ToolboxesPage }))); const ToolboxesPage = lazy(() => import("../features/toolboxes/ToolboxPages.jsx").then((module) => ({ default: module.ToolboxesPage })));
const DesignSystemPage = import.meta.env.DEV
? lazy(() => import("../pages/DesignSystemPage.jsx").then((module) => ({ default: module.DesignSystemPage })))
: null;
function RouteLoader() { function RouteLoader() {
return <section className="empty"><p>Chargement...</p></section>; return <section className="empty"><p>Chargement...</p></section>;
@ -19,6 +22,7 @@ export function RouteContent(props) {
if (route === "/") return <HomePage {...props} />; if (route === "/") return <HomePage {...props} />;
if (route === "/about") return <AboutPage siteContent={props.siteContent} />; if (route === "/about") return <AboutPage siteContent={props.siteContent} />;
if (route === "/library") return <Suspense fallback={<RouteLoader />}><LibraryPage {...props} /></Suspense>; if (route === "/library") return <Suspense fallback={<RouteLoader />}><LibraryPage {...props} /></Suspense>;
if (route === "/design-system" && DesignSystemPage) return <Suspense fallback={<RouteLoader />}><DesignSystemPage /></Suspense>;
if (route === "/toolboxes") return <Suspense fallback={<RouteLoader />}><ToolboxesPage {...props} /></Suspense>; if (route === "/toolboxes") return <Suspense fallback={<RouteLoader />}><ToolboxesPage {...props} /></Suspense>;
if (route.startsWith("/toolbox/")) return <Suspense fallback={<RouteLoader />}><ToolboxPage {...props} toolboxId={route.split("/")[2]} /></Suspense>; if (route.startsWith("/toolbox/")) return <Suspense fallback={<RouteLoader />}><ToolboxPage {...props} toolboxId={route.split("/")[2]} /></Suspense>;
if (route === "/games") return <Suspense fallback={<RouteLoader />}><GamesPage {...props} /></Suspense>; if (route === "/games") return <Suspense fallback={<RouteLoader />}><GamesPage {...props} /></Suspense>;

View file

@ -1,4 +1,6 @@
// Rôle : définit les bases globales HTML, body, typographie et contrôles natifs. // Rôle : définit les bases globales HTML, body, typographie et contrôles natifs.
@use "design-system/controls";
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
@ -36,52 +38,73 @@ a {
text-decoration: none; text-decoration: none;
} }
button, button {
.button, @include controls.text-button;
select,
input,
textarea,
.import-button {
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: rgba(21, 26, 48, 0.92);
color: var(--color-text-primary);
font: inherit;
transition:
transform var(--duration-fast) var(--ease-standard),
border-color var(--duration-fast) var(--ease-standard),
background-color var(--duration-fast) var(--ease-standard),
color var(--duration-fast) var(--ease-standard),
box-shadow var(--duration-fast) var(--ease-standard);
} }
button, select {
.button, @include controls.select-control;
.import-button {
display: inline-flex;
min-height: 42px;
align-items: center;
justify-content: center;
gap: 8px;
padding: 0 18px;
cursor: pointer;
} }
button:hover, select:hover,
.button:hover, select:focus {
.import-button:hover { @include controls.select-control-hover;
border-color: rgba(196, 181, 253, 0.34);
background:
linear-gradient(135deg, rgba(31, 37, 68, 0.96), rgba(22, 26, 51, 0.96)) padding-box,
linear-gradient(135deg, rgba(139, 92, 246, 0.32), rgba(196, 181, 253, 0.2)) border-box;
box-shadow:
0 0 14px rgba(139, 92, 246, 0.12),
inset 0 1px 0 rgba(255, 255, 255, 0.05);
} }
button:active, select:disabled {
.button:active, @include controls.control-disabled;
.import-button:active { }
select option {
@include controls.select-option;
}
:where(input[type="text"], input[type="search"], input[type="email"], input[type="url"], input[type="password"], textarea) {
@include controls.field-control;
}
input[type="time"] {
@include controls.time-control;
}
:where(textarea) {
@include controls.field-textarea;
@include controls.scrollbar-control;
@include controls.scrollbar-resizer;
}
:where(input, textarea)::placeholder {
@include controls.field-placeholder;
}
:where(input[type="text"], input[type="search"], input[type="email"], input[type="url"], input[type="password"], textarea):hover,
input[type="time"]:hover {
@include controls.field-hover;
}
:where(input[type="text"], input[type="search"], input[type="email"], input[type="url"], input[type="password"], textarea):focus,
input[type="time"]:focus {
@include controls.field-focus;
outline: none;
}
:where(input[type="text"], input[type="search"], input[type="email"], input[type="url"], input[type="password"], textarea):focus-visible,
input[type="time"]:focus-visible {
outline: 2px solid var(--color-accent-gold);
outline-offset: 3px;
}
button:disabled,
:where(input[type="text"], input[type="search"], input[type="email"], input[type="url"], input[type="password"], textarea):disabled,
input[type="time"]:disabled {
@include controls.control-disabled;
}
button:hover:not(:disabled, .active, .is-active, [aria-pressed="true"], .nds-button, .nds-icon-button) {
@include controls.text-button-hover;
}
button:active {
transform: translateY(0); transform: translateY(0);
} }
@ -91,66 +114,31 @@ button:active,
} }
input[type="checkbox"] { input[type="checkbox"] {
appearance: none; @include controls.checkbox-control;
-webkit-appearance: none;
display: inline-grid;
width: 20px;
min-width: 20px;
max-width: 20px;
inline-size: 20px;
min-inline-size: 20px;
max-inline-size: 20px;
aspect-ratio: 1;
height: 20px;
min-height: 20px;
max-height: 20px;
block-size: 20px;
min-block-size: 20px;
max-block-size: 20px;
place-content: center;
margin: 0;
padding: 0;
border: 1px solid transparent;
border-radius: 3px;
line-height: 0;
background:
linear-gradient(135deg, rgba(27, 34, 66, 0.92), rgba(16, 20, 44, 0.94)) padding-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.5), rgba(139, 92, 246, 0.44)) border-box;
box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.06),
0 0 0 1px rgba(5, 7, 17, 0.42);
cursor: pointer;
} }
input[type="checkbox"]::before { input[type="checkbox"]::before {
content: ""; @include controls.checkbox-check;
width: 11px;
height: 11px;
background: #fff;
transform: scale(0);
transition: transform var(--duration-fast) var(--ease-standard);
clip-path: polygon(14% 52%, 0 66%, 38% 100%, 100% 18%, 84% 5%, 34% 68%);
} }
input[type="checkbox"]:hover { input[type="checkbox"]:hover {
border-color: rgba(246, 196, 83, 0.6); @include controls.checkbox-hover;
box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.04),
0 0 14px rgba(246, 196, 83, 0.14);
} }
input[type="checkbox"]:checked { input[type="checkbox"]:checked {
border-color: rgba(246, 196, 83, 0.88); @include controls.checkbox-checked;
background:
linear-gradient(135deg, rgba(246, 196, 83, 0.96), rgba(139, 92, 246, 0.86)) padding-box,
linear-gradient(135deg, var(--color-accent-gold), rgba(196, 181, 253, 0.88)) border-box;
box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.12),
0 0 16px rgba(246, 196, 83, 0.2);
} }
input[type="checkbox"]:checked::before { input[type="checkbox"]:checked::before {
transform: scale(1); @include controls.checkbox-checked-check;
}
input[type="checkbox"]:disabled {
@include controls.checkbox-disabled;
}
input[type="checkbox"]:disabled::before {
opacity: 0.54;
} }
input[type="checkbox"]:focus-visible { input[type="checkbox"]:focus-visible {
@ -160,38 +148,24 @@ input[type="checkbox"]:focus-visible {
.themed-scrollbar, .themed-scrollbar,
.legacy-scrollbar { .legacy-scrollbar {
scrollbar-color: rgb(79, 26, 114) transparent; @include controls.scrollbar-control;
scrollbar-width: thin;
} }
.filter-options::-webkit-scrollbar, :is(
.drawer-panel::-webkit-scrollbar, .annotation-marker-list,
.damage-table-wrap::-webkit-scrollbar, .damage-table-wrap,
.themed-scrollbar::-webkit-scrollbar, .drawer-panel,
.legacy-scrollbar::-webkit-scrollbar { .filter-options,
background-color: transparent; .goal-tree-editor-panel,
width: 6px; .goal-tree-prerequisite-list,
height: 6px; .goal-tree-view-wrap,
border-radius: 0; .module-content,
} .notepad-pages-list,
.notepad-surface,
.filter-options::-webkit-scrollbar-track, .table-grid-wrap,
.drawer-panel::-webkit-scrollbar-track, .tool-split-tree
.damage-table-wrap::-webkit-scrollbar-track, ) {
.themed-scrollbar::-webkit-scrollbar-track, @include controls.scrollbar-control;
.legacy-scrollbar::-webkit-scrollbar-track {
background-color: transparent;
border-radius: 0;
box-shadow: none;
}
.filter-options::-webkit-scrollbar-thumb,
.drawer-panel::-webkit-scrollbar-thumb,
.damage-table-wrap::-webkit-scrollbar-thumb,
.themed-scrollbar::-webkit-scrollbar-thumb,
.legacy-scrollbar::-webkit-scrollbar-thumb {
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0.5, rgb(79, 26, 114)), color-stop(1, rgb(44, 1, 135)));
border-radius: 10px;
} }
body.is-modal-open { body.is-modal-open {
@ -203,238 +177,3 @@ body.is-modal-open {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.primary {
border-color: rgba(246, 196, 83, 0.34);
background:
linear-gradient(135deg, rgba(72, 43, 160, 0.96), rgba(45, 52, 146, 0.94)) padding-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.66), rgba(139, 92, 246, 0.36)) border-box;
box-shadow:
0 8px 22px rgba(5, 7, 17, 0.22),
inset 0 1px 0 rgba(255, 255, 255, 0.08);
color: #fff;
font-weight: 700;
}
.primary:hover {
border-color: rgba(246, 196, 83, 0.72);
background:
linear-gradient(135deg, rgba(109, 40, 217, 0.98), rgba(67, 56, 202, 0.96)) padding-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.96), rgba(196, 181, 253, 0.68)) border-box;
box-shadow:
0 0 0 1px rgba(246, 196, 83, 0.16),
0 0 18px rgba(139, 92, 246, 0.3),
0 0 30px rgba(246, 196, 83, 0.16),
inset 0 1px 0 rgba(255, 255, 255, 0.18);
}
.ghost {
border-color: transparent;
background: transparent;
color: var(--color-text-secondary);
}
.ghost:hover {
border-color: rgba(196, 181, 253, 0.18);
background: rgba(139, 92, 246, 0.08);
color: var(--color-text-primary);
box-shadow: none;
}
.danger {
color: var(--color-danger);
}
.danger:hover {
border-color: rgba(251, 113, 133, 0.48);
background:
linear-gradient(135deg, rgba(80, 25, 42, 0.72), rgba(48, 18, 34, 0.82)) padding-box,
linear-gradient(135deg, rgba(251, 113, 133, 0.56), rgba(196, 181, 253, 0.16)) border-box;
color: #fff;
box-shadow:
0 0 14px rgba(251, 113, 133, 0.14),
inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
:is(
.sidebar-action-button,
.import-icon-button,
.toolbox-icon-button,
.card-icon-button,
.drawer-action-button,
.drawer-close-button,
.filter-reset-button,
.image-viewer-button,
.module-edit-button,
.module-exchange-button,
.module-delete-button,
.image-annotate-button,
.image-delete-button,
.checklist-delete-button,
.tool-quick-add-button
) {
display: inline-flex;
width: 40px;
min-width: 40px;
min-height: 40px;
align-items: center;
justify-content: center;
padding: 0;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: rgba(21, 26, 48, 0.88);
color: var(--color-text-primary);
font: inherit;
cursor: pointer;
transition:
transform var(--duration-fast) var(--ease-standard),
border-color var(--duration-fast) var(--ease-standard),
background-color var(--duration-fast) var(--ease-standard),
color var(--duration-fast) var(--ease-standard),
box-shadow var(--duration-fast) var(--ease-standard);
}
:is(
.sidebar-action-button,
.import-icon-button,
.toolbox-icon-button,
.card-icon-button,
.drawer-action-button,
.drawer-close-button,
.filter-reset-button,
.image-viewer-button,
.module-edit-button,
.module-exchange-button,
.module-delete-button,
.image-annotate-button,
.image-delete-button,
.checklist-delete-button,
.tool-quick-add-button
) .ui-icon {
transition:
background-color var(--duration-fast) var(--ease-standard),
filter var(--duration-fast) var(--ease-standard);
}
:is(
.sidebar-action-button,
.import-icon-button,
.toolbox-icon-button,
.card-icon-button,
.drawer-action-button,
.drawer-close-button,
.filter-reset-button,
.image-viewer-button,
.module-edit-button,
.module-exchange-button,
.module-delete-button,
.image-annotate-button,
.image-delete-button,
.checklist-delete-button,
.tool-quick-add-button
):hover {
border-color: rgba(196, 181, 253, 0.32);
background:
linear-gradient(135deg, rgba(31, 37, 68, 0.94), rgba(18, 22, 44, 0.96)) padding-box,
linear-gradient(135deg, rgba(139, 92, 246, 0.3), rgba(196, 181, 253, 0.2)) border-box;
color: #ddd6fe;
box-shadow:
0 0 12px rgba(139, 92, 246, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
:is(
.sidebar-action-button,
.import-icon-button,
.toolbox-icon-button,
.card-icon-button,
.drawer-action-button,
.drawer-close-button,
.filter-reset-button,
.image-viewer-button,
.module-edit-button,
.module-exchange-button,
.module-delete-button,
.image-annotate-button,
.image-delete-button,
.tool-quick-add-button
).primary {
border-color: rgba(246, 196, 83, 0.34);
background:
linear-gradient(135deg, rgba(72, 43, 160, 0.96), rgba(45, 52, 146, 0.94)) padding-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.66), rgba(139, 92, 246, 0.36)) border-box;
color: #fff;
box-shadow:
0 8px 22px rgba(5, 7, 17, 0.22),
inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
:is(
.sidebar-action-button,
.import-icon-button,
.toolbox-icon-button,
.card-icon-button,
.drawer-action-button,
.drawer-close-button,
.filter-reset-button,
.image-viewer-button,
.module-edit-button,
.module-exchange-button,
.module-delete-button,
.image-annotate-button,
.image-delete-button,
.tool-quick-add-button
).primary:hover {
border-color: rgba(246, 196, 83, 0.72);
background:
linear-gradient(135deg, rgba(109, 40, 217, 0.98), rgba(67, 56, 202, 0.96)) padding-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.96), rgba(196, 181, 253, 0.68)) border-box;
color: #fff;
box-shadow:
0 0 0 1px rgba(246, 196, 83, 0.16),
0 0 18px rgba(139, 92, 246, 0.3),
0 0 30px rgba(246, 196, 83, 0.16),
inset 0 1px 0 rgba(255, 255, 255, 0.18);
}
:is(
.sidebar-action-button,
.import-icon-button,
.toolbox-icon-button,
.card-icon-button,
.drawer-action-button,
.drawer-close-button,
.filter-reset-button,
.image-viewer-button,
.module-edit-button,
.module-exchange-button,
.module-delete-button,
.image-annotate-button,
.image-delete-button,
.checklist-delete-button,
.tool-quick-add-button
):hover .ui-icon {
background-color: #c4b5fd;
filter: drop-shadow(0 0 6px rgba(196, 181, 253, 0.28));
}
:is(.card-icon-button, .module-delete-button, .image-delete-button, .checklist-delete-button).danger {
border-color: rgba(251, 113, 133, 0.2);
color: var(--color-danger);
}
:is(.card-icon-button, .module-delete-button, .image-delete-button, .checklist-delete-button).danger:hover {
border-color: rgba(251, 113, 133, 0.56);
background:
linear-gradient(135deg, rgba(80, 25, 42, 0.72), rgba(48, 18, 34, 0.82)) padding-box,
linear-gradient(135deg, rgba(251, 113, 133, 0.56), rgba(196, 181, 253, 0.16)) border-box;
color: #fff;
box-shadow:
0 0 14px rgba(251, 113, 133, 0.14),
inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
:is(.card-icon-button, .module-delete-button, .image-delete-button, .checklist-delete-button).danger:hover .ui-icon {
background-color: #fff;
filter: drop-shadow(0 0 6px rgba(251, 113, 133, 0.26));
}

View file

@ -452,5 +452,5 @@
} }
.card-icon-button { .card-icon-button {
flex: 0 0 40px; flex: 0 0 auto;
} }

View file

@ -0,0 +1,514 @@
// Rôle : met en page le catalogue de contrôles visible uniquement en développement.
@use "design-system/controls";
@use "design-system/interactive-controls";
.design-system-page {
display: grid;
grid-template-columns: minmax(220px, 280px) minmax(0, 1fr);
gap: var(--space-6);
width: min(1480px, calc(100% - 32px));
margin: 0 auto;
padding: var(--space-6) 0;
}
.design-system-nav {
position: sticky;
top: var(--space-4);
display: grid;
max-height: calc(100vh - var(--space-6));
align-content: start;
gap: var(--space-4);
padding: var(--space-5);
overflow: auto;
border: 1px solid rgba(150, 165, 205, 0.14);
border-radius: var(--radius-lg);
background:
radial-gradient(circle at 100% 0%, rgba(246, 196, 83, 0.08), transparent 34%),
rgba(7, 10, 24, 0.66);
box-shadow: var(--shadow-md);
}
.design-system-nav h1,
.design-system-section h2,
.design-system-matrix h3,
.design-system-nav p {
margin: 0;
}
.design-system-nav h1 {
font-size: clamp(2rem, 4vw, 3.2rem);
line-height: 0.95;
}
.design-system-nav p:not(.eyebrow) {
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
}
.design-system-nav nav,
.design-system-token-stack {
display: grid;
gap: 8px;
}
.design-system-nav nav {
padding-top: var(--space-2);
border-top: 1px solid rgba(150, 165, 205, 0.12);
}
.design-system-nav button {
display: flex;
min-height: 34px;
align-items: center;
padding: 0 10px;
border: 1px solid transparent;
border-radius: var(--radius-md);
background: transparent;
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
font-weight: 800;
text-align: left;
cursor: pointer;
}
.design-system-nav button:hover,
.design-system-nav button:focus-visible {
border-color: rgba(246, 196, 83, 0.28);
background: rgba(246, 196, 83, 0.08);
color: var(--color-text-primary);
}
.design-system-main {
display: grid;
min-width: 0;
gap: var(--space-6);
}
.design-system-section {
display: grid;
min-width: 0;
scroll-margin-top: var(--space-5);
gap: var(--space-4);
}
.design-system-section > header {
display: flex;
min-width: 0;
align-items: end;
justify-content: space-between;
gap: var(--space-4);
padding: 0;
border: 0;
}
.design-system-section h2 {
font-size: clamp(1.6rem, 3vw, 2.35rem);
line-height: 1;
}
.design-system-component-intro {
display: grid;
grid-template-columns: minmax(180px, 0.8fr) minmax(0, 2.4fr);
gap: var(--space-3);
padding: var(--space-4);
border: 1px solid rgba(150, 165, 205, 0.14);
border-radius: var(--radius-lg);
background: rgba(7, 10, 24, 0.34);
}
.design-system-doc-summary,
.design-system-attributes-doc {
display: grid;
align-content: start;
gap: 8px;
min-width: 0;
}
.design-system-component-intro span:first-child {
color: var(--color-accent-gold);
font-size: var(--font-size-xs);
font-weight: 900;
text-transform: uppercase;
}
.design-system-component-intro strong {
color: var(--color-text-primary);
font-size: var(--font-size-lg);
}
.design-system-component-intro code,
.design-system-component-intro li code {
color: var(--color-text-primary);
font-size: var(--font-size-sm);
}
.design-system-attributes-doc dl {
display: grid;
overflow: hidden;
margin: 0;
padding: 0;
border: 1px solid rgba(165, 180, 252, 0.12);
border-radius: var(--radius-md);
background: rgba(5, 7, 17, 0.22);
}
.design-system-attributes-doc dl > div {
display: grid;
grid-template-columns: minmax(118px, 0.42fr) minmax(0, 1fr);
min-width: 0;
border-top: 1px solid rgba(165, 180, 252, 0.1);
}
.design-system-attributes-doc dl > div:first-child {
border-top: 0;
}
.design-system-attributes-doc dt,
.design-system-attributes-doc dd {
min-width: 0;
margin: 0;
padding: 8px 10px;
}
.design-system-attributes-doc dt {
border-right: 1px solid rgba(165, 180, 252, 0.1);
background: rgba(139, 92, 246, 0.06);
}
.design-system-attributes-doc dd {
display: flex;
flex-wrap: wrap;
gap: 6px;
color: var(--color-text-secondary);
font-size: var(--font-size-xs);
font-weight: 800;
}
.design-system-attributes-doc dd code {
padding: 2px 6px;
border: 1px solid rgba(246, 196, 83, 0.16);
border-radius: var(--radius-sm);
background: rgba(246, 196, 83, 0.06);
}
.design-system-matrix {
display: grid;
min-width: 0;
overflow: hidden;
border: 1px solid rgba(150, 165, 205, 0.14);
border-radius: var(--radius-lg);
background: rgba(7, 10, 24, 0.42);
box-shadow: var(--shadow-sm);
}
.design-system-matrix > header {
display: flex;
min-width: 0;
align-items: start;
justify-content: flex-end;
gap: var(--space-3);
padding: var(--space-3);
border-bottom: 1px solid rgba(150, 165, 205, 0.12);
}
.design-system-matrix h3 {
font-size: var(--font-size-lg);
}
.design-system-matrix p {
margin: 4px 0 0;
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
}
.design-system-config-panel {
display: flex;
width: 100%;
min-width: 0;
flex-wrap: wrap;
justify-content: flex-end;
gap: 8px;
}
.design-system-config-field {
display: grid;
min-width: 118px;
gap: 4px;
}
.design-system-config-field span {
color: var(--color-text-muted);
font-size: 0.68rem;
font-weight: 900;
text-transform: uppercase;
}
.design-system-matrix-grid {
@include controls.scrollbar-control;
display: grid;
grid-template-columns: 136px repeat(var(--nds-matrix-columns), minmax(150px, 1fr));
min-width: 0;
overflow-x: auto;
overscroll-behavior-x: contain;
}
.design-system-matrix-grid > * {
min-width: 0;
border-top: 1px dashed rgba(139, 92, 246, 0.42);
border-left: 1px dashed rgba(139, 92, 246, 0.42);
}
.design-system-matrix-corner,
.design-system-matrix-heading,
.design-system-matrix-row-label {
display: flex;
min-height: 48px;
align-items: center;
padding: 10px 12px;
color: var(--color-text-secondary);
font-size: var(--font-size-xs);
font-weight: 900;
text-transform: uppercase;
}
.design-system-matrix-heading {
color: var(--color-accent-gold);
}
.design-system-matrix-row-label {
display: grid;
align-content: center;
gap: 3px;
background: rgba(5, 7, 17, 0.22);
line-height: 1.25;
overflow-wrap: anywhere;
}
.design-system-matrix-row-label span {
color: var(--color-text-muted);
font-size: 0.66rem;
}
.design-system-matrix-row-label strong {
color: var(--color-text-secondary);
font-size: 0.78rem;
}
.design-system-matrix-cell {
display: flex;
min-height: 104px;
align-items: center;
gap: 10px;
padding: 12px;
}
.design-system-matrix-separator {
grid-column: 1 / -1;
display: flex;
min-height: 38px;
align-items: center;
padding: 8px 12px;
border-top: 2px solid rgba(246, 196, 83, 0.34);
border-left: 0;
background: rgba(246, 196, 83, 0.055);
color: var(--color-accent-gold);
font-size: var(--font-size-xs);
font-weight: 900;
text-transform: uppercase;
}
.design-system-control-sample {
display: grid;
width: 100%;
min-width: 0;
min-height: 48px;
align-items: center;
justify-items: center;
}
.design-system-sample-preview {
display: flex;
width: 100%;
min-width: 0;
align-items: center;
justify-content: center;
gap: 10px;
}
.design-system-matrix-cell > input,
.design-system-matrix-cell > select,
.design-system-matrix-cell > textarea,
.design-system-matrix-cell > .nds-field,
.design-system-matrix-cell > .mix-tags-input,
.design-system-control-sample input,
.design-system-control-sample select,
.design-system-control-sample textarea,
.design-system-control-sample .mix-tags-input {
width: 100%;
}
.design-system-foundations {
display: grid;
grid-template-columns: minmax(210px, 1.2fr) repeat(3, minmax(180px, 1fr));
gap: var(--space-3);
}
.design-system-foundations article {
display: grid;
min-height: 180px;
align-content: space-between;
gap: var(--space-3);
padding: var(--space-4);
border: 1px solid rgba(150, 165, 205, 0.14);
border-radius: var(--radius-lg);
background: rgba(7, 10, 24, 0.42);
box-shadow: var(--shadow-sm);
}
.design-system-foundations p {
margin: 0;
color: var(--color-text-primary);
font-size: var(--font-size-lg);
font-weight: 900;
}
.design-system-type-card strong {
display: block;
color: var(--color-text-primary);
font-size: clamp(4rem, 10vw, 7rem);
line-height: 0.9;
}
.design-system-type-card span {
color: var(--color-text-secondary);
}
.design-system-swatches {
display: grid;
grid-template-columns: repeat(6, minmax(24px, 1fr));
overflow: hidden;
border-radius: var(--radius-md);
}
.design-system-swatches span {
min-height: 96px;
background: var(--nds-swatch-color);
}
.design-system-radius-card > div,
.design-system-focus-card {
align-items: end;
}
.design-system-radius-card > div {
display: flex;
gap: 8px;
}
.design-system-radius-card span {
display: grid;
width: 52px;
height: 52px;
place-items: center;
border: 1px solid rgba(246, 196, 83, 0.32);
background: rgba(21, 26, 48, 0.72);
color: var(--color-text-secondary);
font-size: var(--font-size-xs);
font-weight: 900;
}
.design-system-spec-pill,
.design-system-size-spec {
display: inline-flex;
min-width: 0;
align-items: center;
}
.design-system-size-spec {
flex-wrap: wrap;
gap: 6px;
}
.design-system-spec-pill {
gap: 5px;
width: fit-content;
max-width: 100%;
padding: 3px 7px;
border: 1px solid rgba(165, 180, 252, 0.12);
border-radius: var(--radius-pill);
background: rgba(5, 7, 17, 0.28);
color: var(--color-text-muted);
font-size: 0.68rem;
font-weight: 800;
}
.design-system-spec-pill b {
color: var(--color-accent-gold);
}
.design-system-inline-controls {
display: flex;
align-items: center;
gap: 6px;
}
.design-system-page .calendar-month-event-repeat-switch {
width: min(230px, 100%);
}
@media (max-width: 980px) {
.design-system-page {
grid-template-columns: 1fr;
}
.design-system-nav {
position: static;
max-height: none;
}
.design-system-nav nav {
grid-template-columns: repeat(auto-fit, minmax(138px, 1fr));
}
.design-system-foundations {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.design-system-component-intro {
grid-template-columns: 1fr;
}
.design-system-matrix > header {
display: grid;
}
.design-system-config-panel {
max-width: none;
justify-content: start;
}
}
@media (max-width: 640px) {
.design-system-page {
width: min(100% - 20px, 1480px);
padding-block: var(--space-4) calc(var(--space-4) + 88px);
}
.design-system-nav,
.design-system-foundations article,
.design-system-matrix > header {
padding: var(--space-3);
}
.design-system-foundations {
grid-template-columns: 1fr;
}
.design-system-matrix-grid {
grid-template-columns: 116px repeat(var(--nds-matrix-columns), minmax(140px, 1fr));
}
.design-system-matrix-cell {
padding: 12px;
}
}

View file

@ -62,4 +62,19 @@
box-shadow: box-shadow:
inset 0 -2px 0 rgba(246, 196, 83, 0.34), inset 0 -2px 0 rgba(246, 196, 83, 0.34),
inset 0 1px 0 rgba(255, 255, 255, 0.05); inset 0 1px 0 rgba(255, 255, 255, 0.05);
&:hover:not(:disabled) {
border-color: rgba(246, 196, 83, 0.86);
background:
radial-gradient(circle at 100% 100%, rgba(246, 196, 83, 0.18), transparent 48%) padding-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.2), rgba(139, 92, 246, 0.12) 52%, rgba(21, 26, 48, 0.08)) padding-box,
linear-gradient(135deg, rgba(16, 20, 38, 0.94), rgba(21, 26, 48, 0.86)) padding-box,
linear-gradient(120deg, rgba(246, 196, 83, 1), rgba(255, 244, 196, 0.92), rgba(246, 196, 83, 0.84)) border-box;
color: var(--color-text-primary);
box-shadow:
0 0 0 1px rgba(246, 196, 83, 0.18),
0 0 16px rgba(246, 196, 83, 0.18),
inset 0 -2px 0 rgba(246, 196, 83, 0.48),
inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
} }

View file

@ -290,7 +290,6 @@
.sidebar-action-button { .sidebar-action-button {
display: inline-flex; display: inline-flex;
background: rgba(7, 10, 24, 0.36);
} }
.content-shell { .content-shell {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,116 @@
// Rôle : complète le catalogue dev avec les helpers de présentation non publics.
@use "controls";
.design-system-page {
.nds-control-row,
.nds-field {
display: grid;
gap: 6px;
min-width: 0;
}
.nds-field {
width: 100%;
}
.nds-label,
.nds-checkbox-label {
color: var(--color-text-secondary);
font-size: var(--font-size-xs);
font-weight: 900;
text-transform: uppercase;
}
.design-system-spec-list {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin: 0;
}
.design-system-spec-list div {
display: inline-flex;
min-width: 0;
align-items: center;
gap: 4px;
padding: 3px 6px;
border: 1px solid rgba(165, 180, 252, 0.1);
border-radius: var(--radius-sm);
background: rgba(5, 7, 17, 0.28);
color: var(--color-text-muted);
font-size: 0.68rem;
font-weight: 800;
}
.design-system-spec-list dt,
.design-system-spec-list dd {
margin: 0;
}
.design-system-spec-list dt {
color: var(--color-accent-gold);
}
.nds-checkbox-label {
display: inline-flex;
align-items: center;
gap: 8px;
text-transform: none;
}
.nds-date-picker-preview {
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
gap: 3px;
width: min(300px, 100%);
padding: 4px;
border: 1px solid rgba(150, 165, 205, 0.16);
border-radius: var(--radius-sm);
background: rgba(8, 11, 24, 0.98);
}
.nds-date-picker-preview button {
@include controls.date-picker-button;
}
.nds-date-picker-preview button:hover,
.nds-date-picker-preview button.active {
@include controls.date-picker-button-hover;
}
.nds-date-picker-preview button.is-outside {
opacity: 0.32;
}
.nds-date-picker-preview-day {
display: grid;
min-height: 22px;
place-items: center;
color: var(--color-text-muted);
font-size: 0.66rem;
font-weight: 900;
text-transform: uppercase;
}
.nds-scrollbox-content-wide {
display: inline-flex;
width: max-content;
min-width: 1280px;
gap: 10px;
}
.nds-scrollbox-content-wide span {
display: inline-flex;
min-width: 170px;
min-height: 42px;
align-items: center;
justify-content: center;
padding: 0 12px;
border: 1px solid rgba(165, 180, 252, 0.12);
border-radius: var(--radius-md);
background: rgba(21, 26, 48, 0.58);
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
font-weight: 800;
}
}

View file

@ -76,15 +76,6 @@
font-size: var(--font-size-lg); font-size: var(--font-size-lg);
} }
.filter-reset-button {
color: var(--color-text-secondary);
box-shadow: var(--shadow-sm);
}
.filter-reset-button:hover {
background: rgba(21, 26, 48, 0.96);
}
.field.compact { .field.compact {
padding: var(--space-4) 0; padding: var(--space-4) 0;
} }
@ -98,27 +89,14 @@
background: rgba(5, 7, 17, 0.36); background: rgba(5, 7, 17, 0.36);
} }
.filter-logic button { .filter-logic-group {
min-height: 30px; flex: 0 0 auto;
padding: 0 12px;
border-color: rgba(246, 196, 83, 0.24);
background:
linear-gradient(135deg, rgba(72, 43, 160, 0.72), rgba(45, 52, 146, 0.68)) padding-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.46), rgba(139, 92, 246, 0.28)) border-box;
color: var(--color-text-primary);
font-size: var(--font-size-sm);
font-weight: 800;
text-transform: uppercase;
} }
.filter-logic button:hover { .filter-logic-group button {
border-color: rgba(246, 196, 83, 0.52); min-height: 30px;
background: padding: 0 12px;
linear-gradient(135deg, rgba(109, 40, 217, 0.86), rgba(67, 56, 202, 0.82)) padding-box, text-transform: uppercase;
linear-gradient(135deg, rgba(246, 196, 83, 0.72), rgba(196, 181, 253, 0.42)) border-box;
box-shadow:
0 0 14px rgba(139, 92, 246, 0.14),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
} }
.filter-options { .filter-options {
@ -151,22 +129,6 @@
color: var(--color-text-primary); color: var(--color-text-primary);
} }
.filter-chip input {
flex: 0 0 18px;
width: 18px;
min-width: 18px;
max-width: 18px;
inline-size: 18px;
min-inline-size: 18px;
max-inline-size: 18px;
height: 18px;
min-height: 18px;
max-height: 18px;
block-size: 18px;
min-block-size: 18px;
max-block-size: 18px;
}
.filter-chip img { .filter-chip img {
width: 22px; width: 22px;
height: 22px; height: 22px;

View file

@ -20,26 +20,8 @@
} }
.game-list-help-link { .game-list-help-link {
display: inline-flex;
width: fit-content; width: fit-content;
min-height: 36px;
align-items: center;
justify-content: center;
margin-top: var(--space-3); margin-top: var(--space-3);
border-color: rgba(165, 180, 252, 0.18);
background: rgba(5, 7, 17, 0.24);
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
font-weight: 800;
padding: 7px 12px;
}
.game-list-help-link:hover {
border-color: rgba(246, 196, 83, 0.44);
background:
linear-gradient(135deg, rgba(246, 196, 83, 0.1), rgba(139, 92, 246, 0.08)) padding-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.42), rgba(196, 181, 253, 0.22)) border-box;
color: var(--color-text-primary);
} }
.game-list-selector { .game-list-selector {
@ -244,13 +226,6 @@
width: 40px; width: 40px;
min-width: 40px; min-width: 40px;
min-height: 40px; min-height: 40px;
padding: 0;
border-radius: var(--radius-md);
}
.game-list-create-button:disabled {
opacity: 0.45;
cursor: not-allowed;
} }
.game-list-create-button .module-icon { .game-list-create-button .module-icon {

View file

@ -138,17 +138,6 @@ span {
gap: 10px; gap: 10px;
} }
.toolbar-stack {
align-items: flex-start;
flex-direction: column;
}
.toolbar-stack > div {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.hero-library-button { .hero-library-button {
display: inline-flex; display: inline-flex;
width: fit-content; width: fit-content;
@ -198,4 +187,3 @@ span {
.home-section-heading p:not(.eyebrow) { .home-section-heading p:not(.eyebrow) {
max-width: 980px; max-width: 980px;
} }

View file

@ -97,17 +97,6 @@
border-right: 0; border-right: 0;
} }
.library-control-preview-stack {
display: inline-grid;
gap: 6px;
justify-items: start;
}
.library-control-preview-row {
display: inline-flex;
gap: 6px;
}
.library-control-preview .notepad-toolbar-button, .library-control-preview .notepad-toolbar-button,
.library-control-preview .drawing-control-button, .library-control-preview .drawing-control-button,
.library-control-preview .notepad-color-toggle, .library-control-preview .notepad-color-toggle,

View file

@ -37,18 +37,10 @@
} }
.library-reset-button { .library-reset-button {
display: inline-grid;
width: 40px; width: 40px;
min-width: 40px; min-width: 40px;
min-height: 40px; min-height: 40px;
flex: 0 0 40px; flex: 0 0 40px;
place-items: center;
padding: 0;
}
.library-reset-button .ui-icon {
width: 16px;
height: 16px;
} }
.library-tool-example .module-content { .library-tool-example .module-content {

View file

@ -6,10 +6,12 @@
@use "home"; @use "home";
@use "cards"; @use "cards";
@use "components"; @use "components";
@use "design-system";
@use "icons"; @use "icons";
@use "games"; @use "games";
@use "toolboxes"; @use "toolboxes";
@use "overlays"; @use "overlays";
@use "mhwilds"; @use "mhwilds";
@use "diablo4"; @use "diablo4";
@use "design-system/attributes";
@use "responsive"; @use "responsive";

View file

@ -1,6 +1,4 @@
// Rôle : gère les résultats et entrées sauvegardées du calculateur. // Rôle : gère les résultats et entrées sauvegardées du calculateur.
@use "../mixins";
.calculator-card { .calculator-card {
gap: 10px; gap: 10px;
align-self: start; align-self: start;
@ -28,11 +26,6 @@
} }
.calculator-drag-handle { .calculator-drag-handle {
width: 30px;
min-width: 30px;
min-height: 36px;
padding: 0;
color: var(--color-text-muted);
cursor: grab; cursor: grab;
touch-action: none; touch-action: none;
user-select: none; user-select: none;

View file

@ -1,6 +1,4 @@
// Rôle : gère la structure commune du calculateur et ses panneaux de saisie. // Rôle : gère la structure commune du calculateur et ses panneaux de saisie.
@use "../mixins";
.calculator-module, .calculator-module,
.timer-module { .timer-module {
display: grid; display: grid;

View file

@ -1,5 +1,5 @@
// Rôle : gère les checklists et les espacements de listes adjacentes. // Rôle : gère les checklists et les espacements de listes adjacentes.
@use "../mixins"; @use "../design-system/controls";
.checklist { .checklist {
display: grid; display: grid;
@ -15,6 +15,12 @@
padding: var(--space-4) var(--space-4) 0; padding: var(--space-4) var(--space-4) 0;
} }
.checklist-complete-mode-group {
@include controls.radio-button-group(xs);
width: fit-content;
align-items: center;
}
.checklist-section { .checklist-section {
display: grid; display: grid;
gap: 8px; gap: 8px;
@ -56,21 +62,13 @@
} }
.checklist-section-title-input { .checklist-section-title-input {
width: 100%; @include controls.inline-field;
min-height: 30px;
padding: 5px 8px;
border: 1px solid transparent;
border-radius: var(--radius-md);
background: transparent;
color: var(--color-accent-gold); color: var(--color-accent-gold);
font: inherit;
outline: none;
} }
.checklist-section-title-input:hover, .checklist-section-title-input:hover,
.checklist-section-title-input:focus { .checklist-section-title-input:focus {
border-color: rgba(246, 196, 83, 0.34); @include controls.inline-field-hover;
background: rgba(21, 26, 48, 0.34);
} }
.checklist-section.is-complete .checklist-section-header h3 { .checklist-section.is-complete .checklist-section-header h3 {
@ -95,7 +93,7 @@
} }
.timer-alert-mode-switch { .timer-alert-mode-switch {
display: inline-flex; @include controls.radio-button-group(xs);
width: fit-content; width: fit-content;
align-items: center; align-items: center;
opacity: 0.78; opacity: 0.78;
@ -103,76 +101,22 @@
.checklist-complete-mode-button, .checklist-complete-mode-button,
.timer-alert-mode-button { .timer-alert-mode-button {
@include controls.radio-button-group-option(xs);
display: inline-grid; display: inline-grid;
width: 36px; width: 36px;
min-width: 36px; min-width: 36px;
min-height: 34px;
place-items: center; place-items: center;
padding: 0;
border: 1px solid rgba(165, 180, 252, 0.12);
border-radius: 0;
background: rgba(5, 7, 17, 0.24);
color: var(--color-text-secondary);
font-size: var(--font-size-xs); font-size: var(--font-size-xs);
font-weight: 800;
}
.checklist-complete-mode-button:first-child,
.timer-alert-mode-button:first-child {
border-radius: var(--radius-pill) 0 0 var(--radius-pill);
}
.checklist-complete-mode-button:last-child,
.timer-alert-mode-button:last-child {
border-radius: 0 var(--radius-pill) var(--radius-pill) 0;
}
.checklist-complete-mode-button + .checklist-complete-mode-button,
.timer-alert-mode-button + .timer-alert-mode-button {
margin-left: -1px;
} }
.checklist-complete-mode-button .ui-icon, .checklist-complete-mode-button .ui-icon,
.timer-alert-mode-button .ui-icon { .timer-alert-mode-button .ui-icon {
width: 16px; @include controls.radio-button-group-option-icon(xs);
height: 16px;
} }
.timer-alert-mode-button { .timer-alert-mode-button {
width: 24px; width: 24px;
min-width: 24px; min-width: 24px;
min-height: 26px;
border-color: rgba(165, 180, 252, 0.08);
background: rgba(5, 7, 17, 0.26);
color: var(--color-text-muted);
}
.timer-alert-mode-button .ui-icon {
width: 13px;
height: 13px;
}
.checklist-section-collapse-button {
display: inline-grid;
width: 30px;
min-width: 30px;
min-height: 30px;
place-items: center;
padding: 0;
border: 1px solid rgba(246, 196, 83, 0.28);
border-radius: var(--radius-md);
background:
linear-gradient(135deg, rgba(246, 196, 83, 0.12), rgba(139, 92, 246, 0.08)) padding-box,
linear-gradient(135deg, rgba(246, 196, 83, 0.3), rgba(196, 181, 253, 0.18)) border-box;
color: #fff;
}
.checklist-section-collapse-button .ui-icon {
width: 16px;
height: 16px;
background-color: #fff;
opacity: 1;
filter: drop-shadow(0 0 7px rgba(255, 255, 255, 0.22));
} }
.checklist-section-items { .checklist-section-items {
@ -198,7 +142,7 @@
.checklist-item { .checklist-item {
display: grid; display: grid;
grid-template-columns: 1fr 42px 42px; grid-template-columns: 1fr 34px 34px;
gap: 8px; gap: 8px;
align-items: center; align-items: center;
} }
@ -225,26 +169,11 @@
} }
.checklist-drag-handle { .checklist-drag-handle {
display: inline-grid;
width: 30px;
min-width: 30px;
min-height: 30px;
place-items: center;
padding: 0;
border: 1px solid rgba(165, 180, 252, 0.1);
border-radius: var(--radius-md);
background: rgba(21, 26, 48, 0.76);
color: var(--color-text-secondary);
cursor: grab; cursor: grab;
touch-action: none; touch-action: none;
user-select: none; user-select: none;
} }
.checklist-drag-handle .ui-icon {
width: 15px;
height: 15px;
}
.checklist-drag-handle:active { .checklist-drag-handle:active {
cursor: grabbing; cursor: grabbing;
} }
@ -259,14 +188,7 @@
.checklist-label-input, .checklist-label-input,
.checklist-category-input { .checklist-category-input {
min-width: 0; @include controls.inline-field;
width: 100%;
min-height: 30px;
padding: 5px 8px;
border: 1px solid transparent;
border-radius: var(--radius-md);
background: transparent;
color: var(--color-text-primary);
} }
.checklist-category-input { .checklist-category-input {
@ -278,8 +200,7 @@
.checklist-label-input:focus, .checklist-label-input:focus,
.checklist-category-input:hover, .checklist-category-input:hover,
.checklist-category-input:focus { .checklist-category-input:focus {
border-color: rgba(246, 196, 83, 0.34); @include controls.inline-field-hover;
background: rgba(21, 26, 48, 0.42);
} }
.checklist-item-summary { .checklist-item-summary {
@ -312,20 +233,7 @@
.checklist-edit-button { .checklist-edit-button {
display: inline-grid; display: inline-grid;
width: 40px;
min-width: 40px;
min-height: 40px;
place-items: center; place-items: center;
padding: 0;
border: 1px solid rgba(165, 180, 252, 0.1);
border-radius: var(--radius-md);
background: rgba(21, 26, 48, 0.76);
color: var(--color-text-secondary);
}
.checklist-edit-button .ui-icon {
width: 17px;
height: 17px;
} }
.checklist input[type="checkbox"] { .checklist input[type="checkbox"] {
@ -343,53 +251,29 @@
} }
.checklist-qty-controls { .checklist-qty-controls {
display: inline-flex; @include controls.quantity-stepper;
flex: 0 0 auto;
align-items: center;
gap: 6px;
} }
.checklist-qty-controls button { .checklist-qty-controls button {
width: 28px; @include controls.quantity-stepper-button;
min-width: 28px; }
min-height: 28px;
padding: 0; .checklist-qty-controls button:hover {
border: 1px solid rgba(165, 180, 252, 0.12); @include controls.quantity-stepper-button-hover;
border-radius: var(--radius-md);
background: rgba(21, 26, 48, 0.88);
color: var(--color-text-secondary);
line-height: 1;
} }
.checklist-qty-current { .checklist-qty-current {
display: inline-flex; @include controls.quantity-stepper-value;
align-items: center;
justify-content: center;
gap: 3px;
color: var(--color-text-secondary);
} }
.checklist-qty-current input { .checklist-qty-current input {
width: max(18px, calc((var(--qty-current-digits, 1) * 0.72rem) + 8px)); @include controls.quantity-stepper-input;
min-height: 28px; @include controls.number-spinner-reset;
padding: 0 4px;
border: 1px solid transparent;
background: transparent;
color: var(--color-text-primary);
text-align: right;
-moz-appearance: textfield;
}
.checklist-qty-current input::-webkit-outer-spin-button,
.checklist-qty-current input::-webkit-inner-spin-button {
margin: 0;
-webkit-appearance: none;
} }
.checklist-qty-current input:hover, .checklist-qty-current input:hover,
.checklist-qty-current input:focus { .checklist-qty-current input:focus {
border-color: rgba(246, 196, 83, 0.34); @include controls.quantity-stepper-input-hover;
background: rgba(21, 26, 48, 0.42);
} }
.checklist-qty-current span { .checklist-qty-current span {

View file

@ -1,6 +1,4 @@
// Rôle : gère l’outil compteurs et ses actions compactes. // Rôle : gère l’outil compteurs et ses actions compactes.
@use "../mixins";
.counters-add-form { .counters-add-form {
grid-template-columns: minmax(0, 1fr) auto; grid-template-columns: minmax(0, 1fr) auto;
} }
@ -31,13 +29,6 @@
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 10px; right: 10px;
display: inline-grid;
width: 34px;
min-width: 34px;
min-height: 34px;
place-items: center;
padding: 0;
color: var(--color-text-muted);
cursor: grab; cursor: grab;
touch-action: none; touch-action: none;
user-select: none; user-select: none;
@ -71,11 +62,3 @@
justify-content: start; justify-content: start;
gap: 8px; gap: 8px;
} }
.counter-actions button {
width: 34px;
min-width: 34px;
min-height: 34px;
padding: 0;
font-weight: 900;
}

View file

@ -1,6 +1,4 @@
// Rôle : gère le calque de dessin, son panneau et ses contrôles superposés. // Rôle : gère le calque de dessin, son panneau et ses contrôles superposés.
@use "../mixins";
.drawing-overlay { .drawing-overlay {
position: absolute; position: absolute;
top: 0; top: 0;

View file

@ -1,5 +1,5 @@
// Rôle : gère les imports image, annotations, marqueurs et galeries. // Rôle : gère les imports image, annotations, marqueurs et galeries.
@use "../mixins"; @use "../design-system/controls";
.dropzone { .dropzone {
display: grid; display: grid;
@ -186,10 +186,7 @@
} }
.annotation-marker-list input { .annotation-marker-list input {
width: 100%; @include controls.field-control(xs);
min-width: 0;
min-height: 34px;
padding: 0 10px;
} }
.annotation-empty { .annotation-empty {
@ -220,27 +217,20 @@
} }
.image-drag-handle { .image-drag-handle {
@include controls.icon-button(sm);
position: absolute; position: absolute;
z-index: 4; z-index: 4;
top: 12px; top: 12px;
left: 12px; left: 12px;
display: grid; display: grid;
width: 32px;
height: 32px;
place-items: center; place-items: center;
padding: 0;
border: 1px solid rgba(138, 150, 197, 0.22);
border-radius: var(--radius-md);
background: rgba(9, 13, 29, 0.76);
color: rgba(229, 235, 255, 0.78);
cursor: grab; cursor: grab;
opacity: 0; opacity: 0;
transition: border-color var(--transition-fast), background var(--transition-fast), color var(--transition-fast), opacity var(--transition-fast), box-shadow var(--transition-fast); transition: border-color var(--transition-fast), background var(--transition-fast), color var(--transition-fast), opacity var(--transition-fast), box-shadow var(--transition-fast);
} }
.image-drag-handle .ui-icon { .image-drag-handle .ui-icon {
width: 16px; @include controls.icon-button-icon(sm);
height: 16px;
} }
.images figure:hover .image-drag-handle, .images figure:hover .image-drag-handle,
@ -249,10 +239,7 @@
} }
.image-drag-handle:hover { .image-drag-handle:hover {
border-color: rgba(246, 196, 83, 0.56); @include controls.icon-button-hover;
background: rgba(20, 28, 55, 0.86);
color: var(--color-accent-gold);
box-shadow: 0 0 14px rgba(246, 196, 83, 0.12);
} }
.image-drag-handle:active { .image-drag-handle:active {
@ -289,23 +276,15 @@
} }
.image-label-input { .image-label-input {
width: 100%; @include controls.field-control(sm);
min-height: 36px;
padding: 0 10px;
border: 1px solid rgba(138, 150, 197, 0.2);
border-radius: var(--radius-md);
background: rgba(8, 13, 30, 0.62);
color: var(--color-text);
} }
.image-label-input::placeholder { .image-label-input::placeholder {
color: rgba(185, 194, 215, 0.48); @include controls.field-placeholder;
} }
.image-label-input:focus { .image-label-input:focus {
border-color: rgba(246, 196, 83, 0.72); @include controls.field-focus;
outline: none;
box-shadow: 0 0 0 3px rgba(246, 196, 83, 0.14);
} }
.images img { .images img {

View file

@ -1,6 +1,4 @@
// Rôle : gère l’outil liens et ses actions de liste. // Rôle : gère l’outil liens et ses actions de liste.
@use "../mixins";
.link-item { .link-item {
display: grid; display: grid;
grid-template-columns: 34px minmax(0, 1fr) auto; grid-template-columns: 34px minmax(0, 1fr) auto;
@ -13,11 +11,6 @@
} }
.link-drag-handle { .link-drag-handle {
width: 34px;
min-width: 34px;
min-height: 34px;
padding: 0;
color: var(--color-text-muted);
cursor: grab; cursor: grab;
touch-action: none; touch-action: none;
user-select: none; user-select: none;
@ -50,15 +43,3 @@
display: flex; display: flex;
gap: 8px; gap: 8px;
} }
.link-item button {
width: 40px;
min-width: 40px;
min-height: 40px;
}
.link-item .link-drag-handle {
width: 34px;
min-width: 34px;
min-height: 34px;
}

View file

@ -19,12 +19,9 @@
} }
.memos-ticker-toggle { .memos-ticker-toggle {
width: 40px; flex: 0 0 auto;
min-width: 40px;
padding: 0;
} }
.memos-ticker-toggle.active,
.memo-card.is-drop-target { .memo-card.is-drop-target {
border-color: rgba(246, 196, 83, 0.48); border-color: rgba(246, 196, 83, 0.48);
background: rgba(246, 196, 83, 0.1); background: rgba(246, 196, 83, 0.1);
@ -92,16 +89,11 @@
} }
.memo-card textarea { .memo-card textarea {
width: 100%;
min-height: 76px; min-height: 76px;
overflow: hidden; overflow: hidden;
resize: none; resize: none;
border: 0;
background: transparent;
color: var(--color-text-primary);
font: inherit;
line-height: 1.45; line-height: 1.45;
outline: none; scrollbar-gutter: auto;
} }
.memo-card.is-dragging { .memo-card.is-dragging {
@ -112,13 +104,6 @@
position: absolute; position: absolute;
top: 8px; top: 8px;
right: 8px; right: 8px;
display: inline-grid;
width: 34px;
min-width: 34px;
min-height: 34px;
place-items: center;
padding: 0;
color: var(--color-text-muted);
cursor: grab; cursor: grab;
opacity: 0.72; opacity: 0.72;
touch-action: none; touch-action: none;
@ -166,21 +151,12 @@
border-bottom: 0; border-bottom: 0;
} }
.memo-delete-button {
width: 34px;
min-width: 34px;
min-height: 34px;
padding: 0;
}
.memos-empty { .memos-empty {
display: inline-flex; display: inline-flex;
min-height: 118px; min-height: 118px;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 8px; gap: 8px;
border-style: dashed;
color: var(--color-text-secondary);
} }
@keyframes memos-ticker-scroll { @keyframes memos-ticker-scroll {

View file

@ -1,4 +1,5 @@
// Rôle : gère le shell des modules toolbox, titres, icônes, onglets et boutons de module. // Rôle : gère le shell des modules toolbox, titres, icônes, onglets et boutons de module.
@use "../design-system/controls";
@use "../mixins"; @use "../mixins";
.module header { .module header {
@ -41,58 +42,21 @@
} }
.layout-switch { .layout-switch {
display: inline-grid;
grid-template-columns: repeat(2, 38px); grid-template-columns: repeat(2, 38px);
gap: 4px;
padding: 4px;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: rgba(5, 7, 17, 0.48);
} }
.layout-switch button { .layout-switch button {
min-height: 34px;
align-items: center;
justify-content: center;
padding: 0; padding: 0;
border-color: transparent;
background: transparent;
color: var(--color-text-muted);
font-weight: 900;
}
.layout-switch button:hover {
border-color: rgba(196, 181, 253, 0.22);
background: rgba(21, 26, 48, 0.5);
color: var(--color-text-primary);
box-shadow: none;
}
.layout-switch button.active {
@include mixins.temporary-toggle-active;
} }
.module-drag-handle { .module-drag-handle {
display: inline-grid; flex: 0 0 40px;
width: 34px;
height: 34px;
flex: 0 0 34px;
place-items: center;
padding: 0;
border-color: transparent;
background: transparent;
color: var(--color-text-muted);
cursor: grab; cursor: grab;
opacity: 0.62; opacity: 0.62;
touch-action: none; touch-action: none;
user-select: none; user-select: none;
} }
.module-drag-handle .ui-icon {
width: 18px;
height: 18px;
}
.module:hover .module-drag-handle, .module:hover .module-drag-handle,
.module:focus-within .module-drag-handle { .module:focus-within .module-drag-handle {
opacity: 1; opacity: 1;
@ -128,49 +92,20 @@
} }
.module-scroll-button { .module-scroll-button {
display: inline-grid; @include controls.switch-control(lg);
grid-template-columns: 16px 30px;
width: fit-content;
min-height: 40px;
flex: 0 0 auto; flex: 0 0 auto;
align-items: center;
gap: 7px;
padding: 0 7px 0 10px;
border: 1px solid rgba(165, 180, 252, 0.12);
border-radius: var(--radius-pill);
background: rgba(5, 7, 17, 0.24);
color: var(--color-text-secondary);
} }
.module-scroll-button .ui-icon { .module-scroll-button .ui-icon {
width: 16px; @include controls.switch-icon;
height: 16px;
} }
.module-scroll-button i { .module-scroll-button i {
position: relative; @include controls.switch-track;
display: block;
width: 30px;
height: 16px;
border: 1px solid rgba(165, 180, 252, 0.18);
border-radius: var(--radius-pill);
background: rgba(7, 10, 24, 0.72);
} }
.module-scroll-button i::before { .module-scroll-button i::before {
content: ""; @include controls.switch-thumb;
position: absolute;
top: 50%;
left: 3px;
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--color-text-muted);
transform: translateY(-50%);
transition:
left var(--duration-fast) var(--ease-standard),
background-color var(--duration-fast) var(--ease-standard),
box-shadow var(--duration-fast) var(--ease-standard);
} }
.module-edit-button.active, .module-edit-button.active,
@ -318,39 +253,22 @@
} }
.tabs { .tabs {
@include controls.radio-button-group-surface;
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
gap: 6px; gap: 6px;
padding: 5px;
border: 1px solid rgba(165, 180, 252, 0.12);
border-radius: var(--radius-md);
background: rgba(5, 7, 17, 0.34);
} }
.tabs button { .tabs button {
display: inline-flex; @include controls.radio-button-group-option;
min-width: 0;
min-height: 36px;
align-items: center;
justify-content: center;
gap: 8px;
padding: 0 10px;
border: 1px solid transparent;
border-radius: calc(var(--radius-md) - 2px);
background: transparent;
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
font-weight: 900;
} }
.tabs button:hover { .tabs button:hover {
border-color: rgba(246, 196, 83, 0.28); @include controls.radio-button-group-option-hover;
background: rgba(246, 196, 83, 0.08);
color: var(--color-text-primary);
} }
.tabs button.active { .tabs button.active {
@include mixins.temporary-toggle-active; @include controls.radio-button-group-option-active;
} }
.tabs .module-icon-svg { .tabs .module-icon-svg {

View file

@ -1,5 +1,5 @@
// Rôle : gère la surface de Bloc Note riche, ses pages et sa barre de formatage. // Rôle : gère la surface de Bloc Note riche, ses pages et sa barre de formatage.
@use "../mixins"; @use "../design-system/controls";
.notepad-module { .notepad-module {
position: relative; position: relative;
@ -34,39 +34,13 @@
top: 50%; top: 50%;
right: 0; right: 0;
z-index: 66; z-index: 66;
display: inline-grid;
width: 40px;
min-width: 40px;
min-height: 40px;
place-items: center;
padding: 0;
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
background: rgba(39, 18, 69, 0.86);
color: var(--color-text-primary);
transform: translateY(-50%); transform: translateY(-50%);
transition:
border-color var(--duration-fast) var(--ease-standard),
background-color var(--duration-fast) var(--ease-standard),
color var(--duration-fast) var(--ease-standard),
box-shadow var(--duration-fast) var(--ease-standard);
}
.notepad-pages-toggle .ui-icon {
width: 20px;
height: 20px;
} }
.notepad-pages-toggle:active { .notepad-pages-toggle:active {
transform: translateY(-50%); transform: translateY(-50%);
} }
.notepad-pages-toggle:hover,
.notepad-pages-toggle.active {
border-color: rgba(246, 196, 83, 0.44);
background: rgba(56, 28, 92, 0.94);
color: var(--color-accent-gold);
}
.notepad-pages-menu { .notepad-pages-menu {
position: absolute; position: absolute;
top: 0; top: 0;
@ -103,49 +77,25 @@
} }
.notepad-page-main { .notepad-page-main {
@include controls.list-row-button;
min-width: 0; min-width: 0;
overflow: hidden;
border: 1px solid rgba(150, 165, 205, 0.12);
border-radius: var(--radius-sm);
background: rgba(5, 7, 17, 0.36);
} }
.notepad-page-row:hover .notepad-page-main, .notepad-page-row:hover .notepad-page-main,
.notepad-page-row:focus-within .notepad-page-main { .notepad-page-row:focus-within .notepad-page-main {
border-color: rgba(150, 165, 205, 0.24); @include controls.list-row-button-hover;
background: rgba(5, 7, 17, 0.5);
} }
.notepad-page-row.active .notepad-page-main { .notepad-page-row.active .notepad-page-main {
border-color: rgba(246, 196, 83, 0.42); @include controls.list-row-button-active;
background: rgba(246, 196, 83, 0.08);
} }
.notepad-page-select { .notepad-page-select {
width: 100%; @include controls.list-row-button-content;
min-height: 32px;
justify-content: flex-start;
overflow: hidden;
padding: 0 8px;
border-color: transparent;
background: transparent;
color: var(--color-text-primary);
font-size: var(--font-size-sm);
font-weight: 900;
text-overflow: ellipsis;
white-space: nowrap;
} }
.notepad-page-title-input { .notepad-page-title-input {
width: 100%; @include controls.field-control(xs);
min-width: 0;
min-height: 32px;
padding: 0 8px;
border: 1px solid rgba(150, 165, 205, 0.14);
border-radius: var(--radius-sm);
background: rgba(7, 10, 24, 0.72);
color: var(--color-text-primary);
font: inherit;
} }
.notepad-page-actions { .notepad-page-actions {
@ -155,16 +105,21 @@
.notepad-page-edit, .notepad-page-edit,
.notepad-page-delete { .notepad-page-delete {
@include controls.icon-button(xxs);
width: 28px; width: 28px;
min-width: 28px; min-width: 28px;
height: 28px;
min-height: 28px; min-height: 28px;
padding: 0;
} }
.notepad-page-edit .ui-icon, .notepad-page-edit .ui-icon,
.notepad-page-delete .ui-icon { .notepad-page-delete .ui-icon {
width: 14px; @include controls.icon-button-icon(xxs);
height: 14px; }
.notepad-page-edit:hover:not(:disabled),
.notepad-page-delete:hover:not(:disabled) {
@include controls.icon-button-hover;
} }
@keyframes notepad-pages-slide-in { @keyframes notepad-pages-slide-in {
@ -220,45 +175,48 @@
} }
.notepad-toolbar-button, .notepad-toolbar-button,
.drawing-control-button, .drawing-control-button {
@include controls.toolbar-button;
}
.notepad-swatch, .notepad-swatch,
.notepad-color-toggle, .notepad-color-toggle,
.drawing-width-toggle, .drawing-width-toggle,
.drawing-width-button { .drawing-width-button {
display: inline-grid; @include controls.toolbar-button;
place-items: center; }
width: 34px;
min-width: 34px; .notepad-swatch,
min-height: 34px; .notepad-color-toggle {
padding: 0; @include controls.color-swatch;
border-color: rgba(150, 165, 205, 0.18); }
border-radius: var(--radius-sm);
background: rgba(16, 20, 38, 0.74); .notepad-color-toggle.is-rainbow {
color: var(--color-text-secondary); @include controls.color-swatch-rainbow;
font-size: var(--font-size-sm);
font-weight: 900;
box-shadow: none;
} }
.notepad-toolbar-button .ui-icon, .notepad-toolbar-button .ui-icon,
.drawing-control-button .ui-icon { .drawing-control-button .ui-icon {
width: 17px; @include controls.toolbar-button-icon;
height: 17px;
} }
.notepad-toolbar-button:hover, .notepad-toolbar-button:hover:not(.active, .is-active),
.drawing-control-button:hover, .drawing-control-button:hover:not(.active, .is-active),
.notepad-swatch:hover, .notepad-swatch:hover,
.notepad-color-toggle:hover, .notepad-color-toggle:hover,
.drawing-width-toggle:hover, .drawing-width-toggle:hover,
.drawing-width-button:hover, .drawing-width-button:hover {
@include controls.toolbar-button-hover;
}
.notepad-toolbar-button.active, .notepad-toolbar-button.active,
.drawing-control-button.active, .drawing-control-button.active,
.notepad-swatch.active,
.drawing-width-button.active { .drawing-width-button.active {
border-color: rgba(246, 196, 83, 0.48); @include controls.toolbar-button-active;
background: rgba(246, 196, 83, 0.1); }
color: var(--color-accent-gold);
.notepad-swatch.active {
@include controls.color-swatch-active;
} }
.notepad-color-dropdown { .notepad-color-dropdown {
@ -298,48 +256,12 @@
top: 6px; top: 6px;
} }
.notepad-color-toggle::before,
.notepad-swatch::before {
content: "";
width: 18px;
height: 18px;
border: 1px solid rgba(245, 247, 255, 0.22);
border-radius: 50%;
background: var(--swatch-color);
box-shadow: inset 0 0 0 2px rgba(5, 7, 17, 0.36);
}
.notepad-color-toggle.is-rainbow::before {
width: 20px;
height: 20px;
border-color: rgba(245, 247, 255, 0.28);
background: conic-gradient(
from 20deg,
#f6c453,
#22d3ee,
#8b5cf6,
#d946ef,
#f5f7ff,
#f6c453
);
box-shadow:
inset 0 0 0 3px rgba(5, 7, 17, 0.34),
0 0 10px rgba(139, 92, 246, 0.16);
}
.notepad-color-menu { .notepad-color-menu {
position: absolute; position: absolute;
top: calc(100% + 8px); top: calc(100% + 8px);
left: 50%; left: 50%;
z-index: 50; z-index: 50;
display: grid; @include controls.floating-control-menu(3);
grid-template-columns: repeat(3, 34px);
gap: 6px;
padding: 8px;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: rgba(10, 13, 28, 0.99);
box-shadow: var(--shadow-lg);
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
transform: translate(-50%, -4px); transform: translate(-50%, -4px);
@ -353,14 +275,7 @@
top: calc(100% + 8px); top: calc(100% + 8px);
left: 50%; left: 50%;
z-index: 50; z-index: 50;
display: grid; @include controls.floating-control-menu(2);
grid-template-columns: repeat(2, 34px);
gap: 6px;
padding: 8px;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: rgba(10, 13, 28, 0.99);
box-shadow: var(--shadow-lg);
transform: translateX(-50%); transform: translateX(-50%);
} }
@ -384,24 +299,17 @@
} }
.notepad-list-option { .notepad-list-option {
justify-content: center; @include controls.compact-menu-button(sm);
width: 34px;
min-width: 34px;
min-height: 34px;
padding: 0;
border-color: rgba(150, 165, 205, 0.18);
background: rgba(16, 20, 38, 0.74);
color: var(--color-text-secondary);
font-size: var(--font-size-sm); font-size: var(--font-size-sm);
font-weight: 800; font-weight: 800;
box-shadow: none;
} }
.notepad-list-option:hover, .notepad-list-option:hover {
@include controls.compact-menu-button-hover;
}
.notepad-list-option.active { .notepad-list-option.active {
border-color: rgba(246, 196, 83, 0.48); @include controls.icon-button-active;
background: rgba(246, 196, 83, 0.1);
color: var(--color-accent-gold);
} }
.notepad-color-menu.is-sectioned { .notepad-color-menu.is-sectioned {

View file

@ -1,5 +1,5 @@
// Rôle : gère l’outil table, sa grille, ses cellules et états de sélection. // Rôle : gère l’outil table, sa grille, ses cellules et états de sélection.
@use "../mixins"; @use "../design-system/controls";
.table-module { .table-module {
display: grid; display: grid;
@ -88,17 +88,11 @@
} }
.table-header-input { .table-header-input {
width: 100%; @include controls.table-header-field;
padding: 0 8px;
border-top: 0;
border-left: 0;
border-radius: 0;
text-align: center;
} }
.table-header-input::placeholder { .table-header-input::placeholder {
color: var(--color-text-muted); @include controls.field-placeholder;
opacity: 1;
} }
.table-header-input:hover { .table-header-input:hover {
@ -106,12 +100,7 @@
} }
.table-header-input:focus { .table-header-input:focus {
z-index: 5; @include controls.table-header-field-focus;
border-color: rgba(246, 196, 83, 0.38);
background: rgba(21, 26, 48, 0.8);
color: var(--color-text-primary);
box-shadow: inset 0 0 0 1px rgba(246, 196, 83, 0.55), 0 0 0 2px rgba(139, 92, 246, 0.18);
outline: 0;
} }
.table-header-cell { .table-header-cell {
@ -134,19 +123,7 @@
} }
.table-cell-input { .table-cell-input {
width: 100%; @include controls.table-cell-field;
min-width: 0;
min-height: 34px;
padding: 6px 8px;
border: 0;
border-right: 1px solid rgba(165, 180, 252, 0.08);
border-bottom: 1px solid rgba(165, 180, 252, 0.08);
border-radius: 0;
background: rgba(16, 20, 38, 0.34);
color: var(--color-text-primary);
caret-color: transparent;
font-size: var(--font-size-sm);
font-variant-numeric: tabular-nums;
} }
.table-cell-input:hover { .table-cell-input:hover {
@ -154,21 +131,11 @@
} }
.table-cell-input:focus { .table-cell-input:focus {
position: relative; @include controls.table-cell-field-focus;
z-index: 4;
border-color: rgba(34, 211, 238, 0.42);
background:
linear-gradient(135deg, rgba(34, 211, 238, 0.08), rgba(139, 92, 246, 0.06)),
rgba(21, 26, 48, 0.58);
box-shadow: inset 0 0 0 1px rgba(34, 211, 238, 0.58);
outline: 0;
} }
.table-cell-input.is-editing:focus { .table-cell-input.is-editing:focus {
border-color: rgba(246, 196, 83, 0.38); @include controls.table-cell-field-editing-focus;
background: rgba(21, 26, 48, 0.72);
caret-color: auto;
box-shadow: inset 0 0 0 1px rgba(246, 196, 83, 0.55), 0 0 0 2px rgba(139, 92, 246, 0.18);
} }
.table-cell-input.is-formula { .table-cell-input.is-formula {

View file

@ -1,5 +1,5 @@
// Rôle : gère les formulaires communs et les panneaux d’import/export texte. // Rôle : gère les formulaires communs et les panneaux d’import/export texte.
@use "../mixins"; @use "../design-system/controls";
.notepad-footer { .notepad-footer {
display: flex; display: flex;
@ -133,27 +133,23 @@
text-align: center; text-align: center;
} }
input, .text-import-modal-form input,
select, .text-import-modal-form select,
textarea { .text-import-modal-form textarea {
min-height: 44px; min-height: 44px;
padding: 0 14px;
outline: none;
} }
textarea { .text-import-modal-form textarea {
padding-block: 14px; @include controls.field-textarea;
} }
input::placeholder, .text-import-modal-form input::placeholder,
textarea::placeholder { .text-import-modal-form textarea::placeholder {
color: var(--color-text-muted); @include controls.field-placeholder;
opacity: 0.58;
} }
input:focus, .text-import-modal-form input:focus,
select:focus, .text-import-modal-form select:focus,
textarea:focus { .text-import-modal-form textarea:focus {
border-color: var(--color-primary); @include controls.field-focus;
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.16);
} }

View file

@ -1,5 +1,5 @@
// Rôle : gère l’outil timer, ses formulaires, listes et états d’alerte. // Rôle : gère l’outil timer, ses formulaires, listes et états d’alerte.
@use "../mixins"; @use "../design-system/controls";
.timer-tabs { .timer-tabs {
width: fit-content; width: fit-content;
@ -7,6 +7,7 @@
} }
.timer-tabs button { .timer-tabs button {
@include controls.radio-button-group-option;
width: 40px; width: 40px;
min-width: 40px; min-width: 40px;
min-height: 36px; min-height: 36px;
@ -48,31 +49,15 @@
} }
.time-parts-input { .time-parts-input {
display: grid; @include controls.time-parts-control;
grid-template-columns: minmax(34px, 1fr) 4px minmax(34px, 1fr) 4px minmax(34px, 1fr);
align-items: center;
min-height: 44px;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: rgba(15, 23, 42, 0.64);
overflow: hidden;
} }
.time-parts-input:focus-within { .time-parts-input:focus-within {
border-color: var(--color-primary); @include controls.time-parts-focus;
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.16);
} }
.time-parts-input input { .time-parts-input input {
width: auto; @include controls.number-spinner-reset;
min-width: 0;
min-height: 42px;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
text-align: center;
box-shadow: none;
} }
.time-parts-input input:focus { .time-parts-input input:focus {
@ -104,8 +89,7 @@
} }
.timer-interval-fields .time-parts-input { .timer-interval-fields .time-parts-input {
grid-template-columns: minmax(22px, 1fr) 4px minmax(22px, 1fr) 4px minmax(22px, 1fr); @include controls.time-parts-control(sm);
min-height: 38px;
} }
.timer-interval-fields .time-parts-input input { .timer-interval-fields .time-parts-input input {
@ -207,8 +191,7 @@
gap: var(--space-2); gap: var(--space-2);
} }
.timer-action-button, .timer-action-button {
.timer-actions .danger {
display: inline-flex; display: inline-flex;
width: 40px; width: 40px;
min-width: 40px; min-width: 40px;
@ -282,72 +265,39 @@
} }
.timer-drag-handle { .timer-drag-handle {
width: 30px; @include controls.icon-button(xs);
min-width: 30px;
min-height: 36px;
padding: 0;
color: var(--color-text-muted);
cursor: grab; cursor: grab;
touch-action: none; touch-action: none;
user-select: none; user-select: none;
} }
.timer-drag-handle .ui-icon {
@include controls.icon-button-icon(xs);
}
.timer-inline-reset { .timer-inline-reset {
width: 26px; @include controls.icon-button(xxs);
min-width: 26px; @include controls.icon-button-ghost;
min-height: 26px;
padding: 0;
border-color: rgba(165, 180, 252, 0.08);
border-radius: var(--radius-sm);
background: rgba(5, 7, 17, 0.26);
color: var(--color-text-muted);
} }
.timer-inline-refresh-toggle { .timer-inline-refresh-toggle {
display: inline-grid; @include controls.switch-control(xs);
grid-template-columns: 13px 18px;
width: 42px;
min-width: 42px;
min-height: 26px;
align-items: center;
gap: 3px;
padding: 0 4px;
border-color: rgba(165, 180, 252, 0.08);
border-radius: var(--radius-sm);
background: rgba(5, 7, 17, 0.26);
color: var(--color-text-muted);
} }
.timer-inline-refresh-toggle i { .timer-inline-refresh-toggle i {
position: relative; @include controls.switch-track;
display: block;
width: 18px;
height: 12px;
border: 1px solid rgba(165, 180, 252, 0.18);
border-radius: var(--radius-pill);
background: rgba(7, 10, 24, 0.72);
} }
.timer-inline-refresh-toggle i::before { .timer-inline-refresh-toggle i::before {
content: ""; @include controls.switch-thumb;
position: absolute; }
top: 50%;
left: 2px; .timer-inline-reset .ui-icon {
width: 8px; @include controls.icon-button-icon(xxs);
height: 8px;
border-radius: 50%;
background: var(--color-text-muted);
transform: translateY(-50%);
transition:
left var(--duration-fast) var(--ease-standard),
background-color var(--duration-fast) var(--ease-standard),
box-shadow var(--duration-fast) var(--ease-standard);
} }
.timer-inline-reset .ui-icon,
.timer-inline-refresh-toggle .ui-icon { .timer-inline-refresh-toggle .ui-icon {
width: 14px; @include controls.switch-icon;
height: 14px;
} }
.timer-inline-controls { .timer-inline-controls {

Some files were not shown because too many files have changed in this diff Show more