// 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";
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"],
surface: ["default", "interactive", "tool", "filter", "modal"],
badge: ["primary", "neutral", "gold", "success", "warning", "danger"],
feedback: ["info", "success", "warning", "danger"],
position: ["left", "right"],
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 }) =>
},
{
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 }) => (
)
},
{
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 }) => (
)
},
{
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 }) =>
},
{
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 }) =>
},
{
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 }) =>
},
{
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 }) =>
},
{
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 ;
}
},
{
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"
?
AB
:
},
{
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 }) =>
},
{
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 }) =>
},
{
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"
?
:
},
{
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 }) =>
},
{
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 }) =>
},
{
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 }) =>
},
{
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 }) =>
},
{
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 }) =>
},
{
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 }) => (
)
},
{
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"
? Alpha sectorBeta sectorGamma sectorDelta sectorEpsilon sectorZeta sectorEta sector
: Zone scrollable
Track transparent
Thumb commun
Contenu supplémentaire
Encore une ligne
}
];
const PATTERN_COMPONENTS = [
{
id: "nds-panel",
name: "Panel",
className: "nds-panel",
sizes: SIZE_VALUES.none,
variants: ["default", "interactive"],
attributes: { "data-variant": ["default", "interactive"] },
render: ({ attributes, variant }) => (
{variant}
Surface de lecture
Fond sombre, bordure Nebula et accent dore contenu.
)
},
{
id: "nds-card",
name: "Card",
className: "nds-card",
sizes: SIZE_VALUES.none,
variants: ["default", "interactive"],
attributes: { "data-variant": ["default", "interactive"] },
render: ({ attributes }) => (
Card
Element repete
)
},
{
id: "nds-tool-panel",
name: "Tool Panel",
className: "nds-tool-panel",
sizes: SIZE_VALUES.none,
variants: ["tool", "interactive"],
attributes: { "data-variant": ["tool", "interactive"] },
render: ({ attributes }) => (
Surface de travail compacte pour les toolboxes.
)
},
{
id: "nds-toolbar",
name: "Toolbar",
className: "nds-toolbar",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
attributes: {},
render: () => (
)
},
{
id: "nds-section-heading",
name: "Section Heading",
className: "nds-section-heading",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
attributes: {},
render: () => (
)
},
{
id: "nds-page-hero",
name: "Page Hero",
className: "nds-page-hero",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
attributes: {},
render: () => (
Hero
Page conciseIntroduction courte et lisible.
)
},
{
id: "nds-badge",
name: "Badge",
className: "nds-badge",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.badge,
attributes: { "data-variant": VARIANT_VALUES.badge },
render: ({ attributes, variant }) => {variant}
},
{
id: "nds-chip",
name: "Chip",
className: "nds-chip",
sizes: ["sm", "default"],
variants: VARIANT_VALUES.none,
attributes: { "data-size": ["sm", "default"], "data-state": STATE_VALUES },
render: ({ attributes, state }) =>
},
{
id: "nds-empty-state",
name: "Empty State",
className: "nds-empty-state",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
attributes: {},
render: () => Aucun contenuMessage court et action optionnelle.
},
{
id: "nds-inline-notice",
name: "Inline Notice",
className: "nds-inline-notice",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.feedback,
attributes: { "data-variant": VARIANT_VALUES.feedback },
render: ({ attributes, variant }) => {variant === "info" ? "Information compacte" : `${variant} compact`}
},
{
id: "nds-progress",
name: "Progress",
className: "nds-progress",
sizes: SIZE_VALUES.none,
variants: ["primary", "success", "warning", "danger"],
attributes: { "data-variant": ["primary", "success", "warning", "danger"] },
render: ({ attributes, variant }) =>
},
{
id: "nds-filter-panel",
name: "Filter Panel",
className: "nds-filter-panel",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
attributes: {},
render: () => (
)
},
{
id: "nds-reorder-indicator",
name: "Reorder Indicator",
className: "nds-reorder-indicator",
sizes: SIZE_VALUES.none,
variants: VARIANT_VALUES.none,
configurable: { "data-axis": ["vertical", "horizontal"], "data-placement": ["before", "after"] },
attributes: { "data-axis": ["vertical", "horizontal"], "data-placement": ["before", "after"] },
render: ({ attributes }) => Drop target
},
{
id: "nds-overlays",
name: "Overlay Patterns",
className: "nds-modal / nds-drawer / nds-toast / nds-popover",
sizes: SIZE_VALUES.none,
variants: ["modal", "drawer", "toast", "popover"],
attributes: { "data-variant": ["modal", "drawer", "toast", "popover"] },
render: ({ variant }) => {
if (variant === "drawer") return Drawer panel
;
if (variant === "toast") return Alerte globale
;
if (variant === "popover") return Menu flottant
;
return Modale
;
}
}
];
const NAV_ITEMS = [
{ id: "foundations", label: "Foundations" },
...COMPONENTS.map(({ id, name }) => ({ id, label: name })),
...PATTERN_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 (
);
}
function SpecPill({ label, value }) {
return {label}{value};
}
function ComponentSection({ id, title, meta, children }) {
return (
);
}
function AttributeSummary({ component }) {
return (
Element
{component.name}
Classe
{component.className}
Attributes
{Object.entries(component.attributes).map(([name, values]) => (
{name}
- {values.map((value) =>
{value})}
))}
);
}
function VariantMatrix({ columns, rows, controls, children }) {
return (
{controls && }
{columns.map((column) =>
{column}
)}
{children || rows.map((row) =>
)}
);
}
function StatePreview({ label, cells }) {
const [attribute, value] = label.includes("=") ? label.split("=") : [null, label];
return (
<>
{attribute ? (
<>
{attribute}
{value}
>
) : {value}}
{cells.map((cell, index) => {cell}
)}
>
);
}
function ConfigSelect({ name, values, value, onChange }) {
return (
);
}
function ControlSample({ children }) {
return (
);
}
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 {component.render({ attributes, config, state, variant, label, size })};
};
return (
size === "default" ? "default" : `data-size=${size}`)}
controls={hasConfigurableAttributes && (
{Object.entries(component.configurable).map(([name, values]) => )}
)}
>
{variants.map((variant) => (
renderCell({ size, variant }))}
/>
))}
{stateRows.length > 0 && data-state
}
{stateRows.map((state) => (
renderCell({ size, variant: defaultStateVariant, state }))}
/>
))}
);
}
function ToggleButton({ children, className = "nds-button", attributes, ariaLabel }) {
const [pressed, setPressed] = useState(false);
return (
);
}
function InteractiveRadioButtonGroup({ attributes, options, iconOnly = false }) {
const [value, setValue] = useState(options[0]?.value);
return (
{options.map((option) => {
const active = option.value === value;
return (
);
})}
);
}
function InteractiveStepper({ attributes, input = false }) {
const [value, setValue] = useState(2);
const increment = (step) => setValue((current) => Math.min(20, Math.max(0, current + step)));
return (
{input ? setValue(Math.min(20, Math.max(0, Number.parseInt(event.target.value, 10) || 0)))} aria-label="Quantité actuelle" /> : value}
/ 20
);
}
function InteractiveSwitch({ attributes, initialActive = false }) {
const [active, setActive] = useState(initialActive);
return (
);
}
function NativeSwitchPreview({ disabled = false, initialChecked = false }) {
const [checked, setChecked] = useState(initialChecked);
return (
);
}
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 (
{["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"].map((day) => {day})}
{days.map((day) => (
))}
);
}
function FoundationsSection() {
return (
Typography
AaInter, system-ui
Colors
{COLOR_TOKENS.map(([label, color]) => )}
Radius
smmdlg
Focus
);
}
function DatePickerButtonsSection() {
return (
] }]} />
);
}
function SpecializedComponentsSection() {
const [mixValue, setMixValue] = useState("base + bonus");
const [smallMixValue, setSmallMixValue] = useState("base * 2");
return (
,
]
}]}
/>
);
}
function AuditSection() {
return (
className="nds-button",
data-size data-variant data-edge data-border data-axis,
disabled aria-pressed .active .is-active
]
},
{
label: "global",
cells: [
Home / About / Games CTAs,
Shell / drawer / modales / filters,
Image viewer / library actions
]
},
{
label: "sample",
cells: [
,
,
Actif
]
}
]}
/>
);
}
export function DesignSystemPage() {
return (
{COMPONENTS.map((component) => )}
{PATTERN_COMPONENTS.map((component) => )}
);
}