diff --git a/DESIGN_SYSTEM.md b/DESIGN_SYSTEM.md index 9908551..9677de2 100644 --- a/DESIGN_SYSTEM.md +++ b/DESIGN_SYSTEM.md @@ -63,21 +63,22 @@ Animations : - `--duration-normal: 180ms` - `--ease-standard: cubic-bezier(0.2, 0.8, 0.2, 1)` -## 3. API Des Contrôles NDS +## 3. API Publique NDS -Le Nebula Design System expose ses contrôles publics avec une classe de famille et des attributs `data-*`. +Le Nebula Design System expose ses primitives publiques 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 patterns transverses utilisent aussi une classe de famille : `nds-panel`, `nds-card`, `nds-card-cover`, `nds-card-body`, `nds-card-actions`, `nds-tool-panel`, `nds-toolbar`, `nds-section-heading`, `nds-page-hero`, `nds-badge`, `nds-chip`, `nds-empty-state`, `nds-inline-notice`, `nds-progress`, `nds-filter-panel`, `nds-reorder-indicator`, `nds-modal`, `nds-modal-backdrop`, `nds-modal-footer`, `nds-drawer-panel`, `nds-drawer-resize-handle`, `nds-popover`, `nds-toast-stack`, `nds-toast` ; - 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-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, `icon-picker` pour les triggers de sélection compacts à icône, `interactive|tool|filter|modal` pour les surfaces, et `neutral|gold|success|warning|danger` pour les badges, notices et progress ; - `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` ; +- `data-axis="horizontal|vertical"` reste réservé aux composants dont l'axe est métier, comme `nds-scrollbox` et `nds-reorder-indicator` ; - les boutons icônes de réorganisation utilisent `nds-icon-button` avec `data-variant="secondary"` et une classe métier contenant `drag-handle` ; le curseur `grab/grabbing` est alors géré par le NDS ; - les états fonctionnels restent portés par le HTML et l'application : `disabled`, `aria-pressed="true"`, `.active`, `.is-active`. @@ -89,10 +90,29 @@ Exemples : +
+
+Rare +

Erreur compacte

``` 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`. +### Surfaces Et Patterns + +Les surfaces NDS couvrent les patterns transverses, pas les composants metier complets. + +- `nds-panel` : panneau de lecture non repete, sans hover par defaut. +- `nds-card` : element repete ou ouvrable ; utiliser `nds-card-cover`, `nds-card-body` et `nds-card-actions` quand la structure cover/contenu/actions est necessaire. +- `nds-tool-panel` : surface de travail d'outil, plus discrete qu'une card, avec accent dore bas/droite. +- `nds-filter-panel` : panneau sticky ou lateral de filtres ; les options utilisent `nds-chip`. +- `nds-empty-state` : etat vide court, avec action optionnelle. +- `nds-inline-notice` : feedback compact inline, avec `data-variant="info|success|warning|danger"`. +- `nds-progress` : rail de progression ; le remplissage est le premier `span` enfant ou `.nds-progress-bar`. +- `nds-modal`, `nds-drawer-panel`, `nds-popover`, `nds-toast` : surfaces d'overlay. Le comportement React reste gere par les composants existants. + +Les classes metier existantes peuvent coexister avec ces classes pendant la migration. Quand une classe metier reste necessaire, elle doit porter le layout, la grille ou le positionnement, pas recreer les fonds, bordures, hovers et focus partages. + ## 4. Fond Nebula Le fond global est defini dans `website/src/styles/_nebula.scss`. diff --git a/tests/static-styles.test.mjs b/tests/static-styles.test.mjs index 6a6dbe1..7517029 100644 --- a/tests/static-styles.test.mjs +++ b/tests/static-styles.test.mjs @@ -20,9 +20,28 @@ test("style entrypoint and theme layers are wired", async () => { const styleShell = await readFile("website/src/styles/_shell.scss", "utf8"); const styleCards = await readFile("website/src/styles/_cards.scss", "utf8"); const styleSurfaces = await readFile("website/src/styles/design-system/_surfaces.scss", "utf8"); + const styleControls = await readFile("website/src/styles/design-system/_controls.scss", "utf8"); + const styleAttributes = await readFile("website/src/styles/design-system/_attributes.scss", "utf8"); + const styleControlsPatterns = await readFile("website/src/styles/design-system/controls/_patterns.scss", "utf8"); + const styleAttributesPatterns = await readFile("website/src/styles/design-system/attributes/_patterns.scss", "utf8"); const styleControlsButtons = await readFile("website/src/styles/design-system/controls/_buttons.scss", "utf8"); const styleAttributesButtons = await readFile("website/src/styles/design-system/attributes/_buttons.scss", "utf8"); const goalTreeBranch = await readFile("website/src/features/toolboxes/modules/GoalTreeBranch.jsx", "utf8"); + const ndsPatternConsumers = await readJoinedStyles([ + "website/src/pages/HomePage.jsx", + "website/src/pages/LibraryPage.jsx", + "website/src/pages/AboutPage.jsx", + "website/src/features/games/GamesPage.jsx", + "website/src/features/games/GameFiltersPanel.jsx", + "website/src/features/games/diablo4/Diablo4AffixCard.jsx", + "website/src/features/toolboxes/ToolboxCard.jsx", + "website/src/features/toolboxes/ToolboxPages.jsx", + "website/src/components/AppOverlays.jsx", + "website/src/components/StorageQuota.jsx", + "website/src/components/ToolboxModals.jsx", + "website/src/features/toolboxes/modules/AddToolControls.jsx", + "website/src/features/toolboxes/modules/TextImportModal.jsx" + ]); const styleHome = await readJoinedStyles([ "website/src/styles/_home.scss", "website/src/styles/home/_shared.scss", @@ -152,7 +171,7 @@ test("style entrypoint and theme layers are wired", async () => { assert.match(styleHome, /\.dialogue-line i/); assert.match(styleHome, /font-style: italic/); assert.match(styleHome, /\.about-page/); - assert.match(styleHome, /\.nebula-panel/); + assert.doesNotMatch(styleHome, /\.nebula-panel\s*\{/); assert.match(styleHome, /\.home-onboarding-layout/); assert.match(styleHome, /\.home-onboarding-slider figcaption/); assert.match(styleHome, /\.home-onboarding-dots/); @@ -223,8 +242,38 @@ test("style entrypoint and theme layers are wired", async () => { assert.match(styleSurfaces, /@mixin card-surface/); assert.match(styleSurfaces, /@mixin card-surface-hover/); assert.match(styleSurfaces, /@mixin card-cover-divider/); - assert.match(styleCards, /@use "design-system\/surfaces"/); - assert.match(styleCards, /surfaces\.card-surface/); + assert.match(styleControls, /@forward "controls\/patterns"/); + assert.match(styleAttributes, /@use "attributes\/patterns"/); + assert.match(styleControlsPatterns, /@mixin panel-surface/); + assert.match(styleControlsPatterns, /@mixin toast-pattern/); + assert.match(styleAttributesPatterns, /\.nds-panel/); + assert.match(styleAttributesPatterns, /\.nds-card/); + assert.match(styleAttributesPatterns, /\.nds-tool-panel/); + assert.match(styleAttributesPatterns, /\.nds-badge/); + assert.match(styleAttributesPatterns, /\.nds-chip/); + assert.match(styleAttributesPatterns, /\.nds-empty-state/); + assert.match(styleAttributesPatterns, /\.nds-inline-notice/); + assert.match(styleAttributesPatterns, /\.nds-progress/); + assert.match(styleAttributesPatterns, /\.nds-filter-panel/); + assert.match(styleAttributesPatterns, /\.nds-modal/); + assert.match(styleAttributesPatterns, /\.nds-drawer-panel/); + assert.match(styleAttributesPatterns, /\.nds-toast/); + assert.match(ndsPatternConsumers, /nds-page-hero/); + assert.match(ndsPatternConsumers, /nds-panel/); + assert.match(ndsPatternConsumers, /nds-card/); + assert.match(ndsPatternConsumers, /nds-card-cover/); + assert.match(ndsPatternConsumers, /nds-tool-panel/); + assert.match(ndsPatternConsumers, /nds-filter-panel/); + assert.match(ndsPatternConsumers, /nds-chip/); + assert.match(ndsPatternConsumers, /nds-badge/); + assert.match(ndsPatternConsumers, /nds-empty-state/); + assert.match(ndsPatternConsumers, /nds-inline-notice/); + assert.match(ndsPatternConsumers, /nds-progress/); + assert.match(ndsPatternConsumers, /nds-modal/); + assert.match(ndsPatternConsumers, /nds-drawer-panel/); + assert.match(ndsPatternConsumers, /nds-toast/); + assert.doesNotMatch(styleCards, /@use "design-system\/surfaces"/); + assert.doesNotMatch(styleCards, /controls\.card-pattern/); assert.match(styleGames, /@use "\.\.\/design-system\/surfaces"/); assert.match(styleGames, /surfaces\.card-surface/); assert.match(styleGames, /surfaces\.card-cover-divider/); @@ -234,7 +283,7 @@ test("style entrypoint and theme layers are wired", async () => { assert.match(styleMhwilds, /surfaces\.card-surface/); assert.match(styleMhwilds, /surfaces\.card-cover-divider/); assert.doesNotMatch(styleMhwilds, /\.game-(home-card|layout|grid)/); - assert.match(styleCards, /compact-data-card/); + assert.doesNotMatch(styleCards, /\.compact-data-card\s*\{/); assert.doesNotMatch( [ goalTreeBranch, diff --git a/website/src/components/AppOverlays.jsx b/website/src/components/AppOverlays.jsx index 3ac7d6a..280b406 100644 --- a/website/src/components/AppOverlays.jsx +++ b/website/src/components/AppOverlays.jsx @@ -13,7 +13,7 @@ function scrollToTopAfterNavigation() { export function InlineNotice({ className = "", iconName = "link", message, tone = "warning" }) { if (!message) return null; - const classes = ["inline-notice", `is-${tone}`, className].filter(Boolean).join(" "); + const classes = ["inline-notice", "nds-inline-notice", `is-${tone}`, className].filter(Boolean).join(" "); return ( @@ -72,7 +72,7 @@ export function AppOverlays({ {storageError && setStorageError("")} />} {image && setImage(null)} createMarkerId={() => uid("marker")} />} {notifications.length > 0 && ( -
+
{notifications.map((notification) => ( item.id === gameId); const linkedToolbox = game ? toolboxes.find((item) => item.id === links[game.id]) : null; @@ -45,7 +45,15 @@ export function Shell({ route, content, games, toolboxes, links, actions, childr
{topbarLabel}
{game && (
-
diff --git a/website/src/components/StorageQuota.jsx b/website/src/components/StorageQuota.jsx index b5c37ca..7926ebd 100644 --- a/website/src/components/StorageQuota.jsx +++ b/website/src/components/StorageQuota.jsx @@ -19,7 +19,7 @@ export function StorageQuota({ usage }) { return (
Stockage local recommandé{formatBytes(safeUsage.used)} / {formatBytes(APP_STORAGE_SOFT_LIMIT_BYTES)}
-
+
); } diff --git a/website/src/components/ToolboxModals.jsx b/website/src/components/ToolboxModals.jsx index 6a00d6d..6598c19 100644 --- a/website/src/components/ToolboxModals.jsx +++ b/website/src/components/ToolboxModals.jsx @@ -11,11 +11,11 @@ export function ConfirmModal({ title, message, confirmLabel = "Confirmer", cance useModalScrollLock(); return (
-
onClose(false)} /> -
+
onClose(false)} /> +

{title}

{message}

-
+
@@ -31,7 +31,7 @@ export function NotificationToast({ id, message, durationMs = 120000, onClose }) }, [durationMs, id, onClose]); return ( -
+
{message}
+
@@ -65,15 +65,15 @@ export function LinkToolboxModal({ toolboxes, selectedId, onClose }) { const [toolboxId, setToolboxId] = useState(selectedId); return (
-
onClose(null)} /> -
+
onClose(null)} /> +
{ event.preventDefault(); onClose(toolboxId); }}> -
+
diff --git a/website/src/features/games/GameFiltersPanel.jsx b/website/src/features/games/GameFiltersPanel.jsx index fe9a589..7a1f1c0 100644 --- a/website/src/features/games/GameFiltersPanel.jsx +++ b/website/src/features/games/GameFiltersPanel.jsx @@ -22,7 +22,7 @@ export function GameFiltersPanel({ } return ( -
+

Filtres

@@ -63,7 +63,7 @@ export function GameFiltersPanel({ {options.map((option) => { const optionId = getOptionId(option); return ( -