chore: initial DSMMG v0.2 — refonte architecturale complète
Mise en place du Design System ManageMate Group v0.2 — refonte du
système de tokens (préfixe --mmg-color-*), 9 presets accent
user-themable validés WCAG AA, overlays Radix UI + Floating UI,
Storybook 8 + Vitest + axe-core en CI, doc Astro Starlight,
DESIGN.md (format google-labs-code) et exports tokens DTCG/CSS/
TS/Figma/Tailwind v3 et v4.
- 4 packages monorepo pnpm : @managemate/{tokens,css,react,icons}
- 62 composants React headless-first (Sheet, HoverCard, ContextMenu,
Slider, ToggleGroup, AvatarGroup, UserCard, ProfileHeader,
MetricCard, PricingCard, FeatureCard, Text/Display/Eyebrow/Lead…)
- Lint contraste WCAG : 37/37 paires AA, branché CI
- Toast pile Sonner-style avec ResizeObserver
- Theming user (9 presets) sans casser sémantique fixe
- Identité Synapse (rose #D12B6A) préservée
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
# @managemate/tokens
|
||||
|
||||
Source unique des tokens DSMMG au format **W3C Design Tokens Community Group (DTCG)**.
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
pnpm --filter @managemate/tokens build
|
||||
```
|
||||
|
||||
Génère dans `dist/` :
|
||||
|
||||
| Fichier | Format | Pour qui |
|
||||
|---|---|---|
|
||||
| `tokens.css` | CSS custom properties `--mmg-*` | Le navigateur, importé par `@managemate/css` |
|
||||
| `tokens.js` | ESM | Code Node/Bundler — JS/TS apps |
|
||||
| `tokens.cjs` | CommonJS | Code Node legacy |
|
||||
| `tokens.d.ts` | TypeScript declarations | Auto-complete dans les IDEs |
|
||||
| `figma-tokens.json` | [Tokens Studio](https://tokens.studio/) | Plugin Figma → sync designer↔code |
|
||||
|
||||
## Workflow Figma
|
||||
|
||||
1. Le designer modifie les variables dans Figma via [Tokens Studio](https://tokens.studio/).
|
||||
2. Export → push vers `packages/tokens/src/tokens.json` (PR avec changeset minor).
|
||||
3. CI valide les contrastes et regen les sorties.
|
||||
4. Ou inversement : modifier `tokens.json` → CI regen Figma JSON → designer pull dans Figma.
|
||||
|
||||
## DTCG schema
|
||||
|
||||
Chaque token a une forme :
|
||||
|
||||
```json
|
||||
{
|
||||
"$value": "#D12B6A",
|
||||
"$type": "color",
|
||||
"$description": "(optionnel)"
|
||||
}
|
||||
```
|
||||
|
||||
Types supportés : `color`, `dimension`, `fontWeight`, `duration`.
|
||||
|
||||
## Conventions de naming
|
||||
|
||||
- `color.<palette>.<shade>` : `color.synapse.500`
|
||||
- `spacing.<index>` : `spacing.4` (= 16px)
|
||||
- `radius.<scale>` : `radius.md`
|
||||
- `fontSize.<scale>` : `fontSize.base`
|
||||
- `fontWeight.<scale>` : `fontWeight.semi`
|
||||
- `duration.<scale>` : `duration.fast`
|
||||
|
||||
Le préfixe CSS `--mmg-` est ajouté automatiquement par Style Dictionary.
|
||||
|
||||
## Présets accent (cross-pkg)
|
||||
|
||||
Les tokens primitifs sont publiés ici. Les presets accent (`[data-mmg-accent="…"]`) restent définis dans `@managemate/css` (`tokens/accent.css`) car ils dépendent du contexte (light/dark, sélection user).
|
||||
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* @managemate/tokens — build pipeline
|
||||
*
|
||||
* Source de vérité unique : src/tokens.json (format W3C DTCG).
|
||||
* Génère :
|
||||
* - dist/tokens.css : CSS custom properties --mmg-color-*
|
||||
* - dist/tokens.js / .d.ts: objet JS/TS pour consommation programmatique
|
||||
* - dist/tokens.cjs : CommonJS
|
||||
* - dist/figma-tokens.json: format Tokens Studio pour sync Figma
|
||||
*/
|
||||
import StyleDictionary from "style-dictionary";
|
||||
import { mkdir, writeFile, readFile } from "node:fs/promises";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { existsSync } from "node:fs";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = __dirname;
|
||||
const DIST = join(ROOT, "dist");
|
||||
const SOURCE = join(ROOT, "src", "tokens.json");
|
||||
|
||||
if (!existsSync(DIST)) await mkdir(DIST, { recursive: true });
|
||||
|
||||
// — Style Dictionary config ————————————————————————————————————
|
||||
const sd = new StyleDictionary({
|
||||
source: [SOURCE],
|
||||
log: { warnings: "warn", verbosity: "default" },
|
||||
platforms: {
|
||||
css: {
|
||||
transformGroup: "css",
|
||||
buildPath: "dist/",
|
||||
files: [
|
||||
{
|
||||
destination: "tokens.css",
|
||||
format: "css/variables",
|
||||
options: {
|
||||
outputReferences: false,
|
||||
selector: ":root",
|
||||
},
|
||||
},
|
||||
],
|
||||
prefix: "mmg",
|
||||
},
|
||||
js: {
|
||||
transformGroup: "js",
|
||||
buildPath: "dist/",
|
||||
files: [
|
||||
{
|
||||
destination: "tokens.js",
|
||||
format: "javascript/esm",
|
||||
},
|
||||
{
|
||||
destination: "tokens.cjs",
|
||||
format: "javascript/module",
|
||||
},
|
||||
{
|
||||
destination: "tokens.d.ts",
|
||||
format: "typescript/es6-declarations",
|
||||
},
|
||||
],
|
||||
prefix: "mmg",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await sd.cleanAllPlatforms();
|
||||
await sd.buildAllPlatforms();
|
||||
|
||||
// — Figma Tokens Studio export ————————————————————————————————————
|
||||
// Plugin Tokens Studio attend un format proche de DTCG mais avec une
|
||||
// structure aplatie : { "color/neutral/50": { "value": "...", "type": "color" } }
|
||||
const dtcg = JSON.parse(await readFile(SOURCE, "utf8"));
|
||||
|
||||
function flatten(obj, prefix = "") {
|
||||
const out = {};
|
||||
for (const [k, v] of Object.entries(obj)) {
|
||||
if (k.startsWith("$")) continue;
|
||||
const path = prefix ? `${prefix}/${k}` : k;
|
||||
if (v && typeof v === "object" && "$value" in v) {
|
||||
out[path] = { value: v.$value, type: v.$type };
|
||||
} else if (v && typeof v === "object") {
|
||||
Object.assign(out, flatten(v, path));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const figmaTokens = {
|
||||
global: flatten(dtcg),
|
||||
$themes: [],
|
||||
$metadata: {
|
||||
tokenSetOrder: ["global"],
|
||||
generatedAt: new Date().toISOString(),
|
||||
generatedBy: "@managemate/tokens build.mjs",
|
||||
},
|
||||
};
|
||||
|
||||
await writeFile(join(DIST, "figma-tokens.json"), JSON.stringify(figmaTokens, null, 2));
|
||||
|
||||
// — Tailwind v3 export (theme.extend JSON) ————————————————————————
|
||||
function buildTailwindV3() {
|
||||
const colors = {};
|
||||
for (const [name, ramp] of Object.entries(dtcg.color ?? {})) {
|
||||
if (!ramp || typeof ramp !== "object") continue;
|
||||
const shades = {};
|
||||
for (const [shade, val] of Object.entries(ramp)) {
|
||||
if (val && typeof val === "object" && "$value" in val) {
|
||||
shades[shade] = val.$value;
|
||||
}
|
||||
}
|
||||
if (Object.keys(shades).length > 0) colors[name] = shades;
|
||||
}
|
||||
const spacing = {};
|
||||
for (const [k, v] of Object.entries(dtcg.spacing ?? {})) {
|
||||
if (v && typeof v === "object" && "$value" in v) {
|
||||
spacing[`mmg-${k}`] = v.$value;
|
||||
}
|
||||
}
|
||||
const radius = {};
|
||||
for (const [k, v] of Object.entries(dtcg.radius ?? {})) {
|
||||
if (v && typeof v === "object" && "$value" in v) {
|
||||
radius[`mmg-${k}`] = v.$value;
|
||||
}
|
||||
}
|
||||
const fontSize = {};
|
||||
for (const [k, v] of Object.entries(dtcg.fontSize ?? {})) {
|
||||
if (v && typeof v === "object" && "$value" in v) {
|
||||
fontSize[`mmg-${k}`] = v.$value;
|
||||
}
|
||||
}
|
||||
return {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: { mmg: colors },
|
||||
spacing,
|
||||
borderRadius: radius,
|
||||
fontSize,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
await writeFile(
|
||||
join(DIST, "tailwind.config.json"),
|
||||
JSON.stringify(buildTailwindV3(), null, 2),
|
||||
);
|
||||
|
||||
// — Tailwind v4 export (@theme CSS block) ————————————————————————
|
||||
function buildTailwindV4() {
|
||||
const lines = ["/* DSMMG tokens — Tailwind v4 @theme block. */", "@theme {"];
|
||||
// Colors
|
||||
for (const [name, ramp] of Object.entries(dtcg.color ?? {})) {
|
||||
if (!ramp || typeof ramp !== "object") continue;
|
||||
for (const [shade, val] of Object.entries(ramp)) {
|
||||
if (val && typeof val === "object" && "$value" in val) {
|
||||
lines.push(` --color-mmg-${name}-${shade}: ${val.$value};`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Spacing
|
||||
for (const [k, v] of Object.entries(dtcg.spacing ?? {})) {
|
||||
if (v && typeof v === "object" && "$value" in v) {
|
||||
lines.push(` --spacing-mmg-${k}: ${v.$value};`);
|
||||
}
|
||||
}
|
||||
// Radius
|
||||
for (const [k, v] of Object.entries(dtcg.radius ?? {})) {
|
||||
if (v && typeof v === "object" && "$value" in v) {
|
||||
lines.push(` --radius-mmg-${k}: ${v.$value};`);
|
||||
}
|
||||
}
|
||||
// Font sizes
|
||||
for (const [k, v] of Object.entries(dtcg.fontSize ?? {})) {
|
||||
if (v && typeof v === "object" && "$value" in v) {
|
||||
lines.push(` --text-mmg-${k}: ${v.$value};`);
|
||||
}
|
||||
}
|
||||
// Durations
|
||||
for (const [k, v] of Object.entries(dtcg.duration ?? {})) {
|
||||
if (v && typeof v === "object" && "$value" in v) {
|
||||
lines.push(` --animate-duration-mmg-${k}: ${v.$value};`);
|
||||
}
|
||||
}
|
||||
lines.push("}");
|
||||
return lines.join("\n") + "\n";
|
||||
}
|
||||
|
||||
await writeFile(join(DIST, "tailwind.css"), buildTailwindV4());
|
||||
|
||||
console.log(`✓ Tokens built → ${DIST}`);
|
||||
console.log(` - tokens.css (CSS custom properties)`);
|
||||
console.log(` - tokens.{js,cjs,d.ts} (ESM / CJS / TypeScript)`);
|
||||
console.log(` - figma-tokens.json (Tokens Studio for Figma sync)`);
|
||||
console.log(` - tailwind.config.json (Tailwind v3 theme.extend)`);
|
||||
console.log(` - tailwind.css (Tailwind v4 @theme block)`);
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@managemate/tokens",
|
||||
"version": "0.1.0",
|
||||
"description": "DSMMG design tokens — DTCG source + Style Dictionary build (CSS, TS, Figma)",
|
||||
"type": "module",
|
||||
"license": "UNLICENSED",
|
||||
"private": true,
|
||||
"sideEffects": false,
|
||||
"main": "./dist/tokens.cjs",
|
||||
"module": "./dist/tokens.js",
|
||||
"types": "./dist/tokens.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/tokens.d.ts",
|
||||
"import": "./dist/tokens.js",
|
||||
"require": "./dist/tokens.cjs"
|
||||
},
|
||||
"./css": "./dist/tokens.css",
|
||||
"./figma": "./dist/figma-tokens.json",
|
||||
"./tailwind": "./dist/tailwind.config.json",
|
||||
"./tailwind.css": "./dist/tailwind.css",
|
||||
"./source": "./src/tokens.json",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "node build.mjs",
|
||||
"watch": "node build.mjs --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"style-dictionary": "^4.3.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
{
|
||||
"$schema": "https://schemas.tr.designtokens.org/format/",
|
||||
"color": {
|
||||
"neutral": {
|
||||
"0": { "$value": "#FFFFFF", "$type": "color" },
|
||||
"50": { "$value": "#F7F6FB", "$type": "color" },
|
||||
"100": { "$value": "#F0EFF9", "$type": "color" },
|
||||
"150": { "$value": "#EDEDFA", "$type": "color" },
|
||||
"200": { "$value": "#E4E3F4", "$type": "color" },
|
||||
"300": { "$value": "#C9C7E0", "$type": "color" },
|
||||
"400": { "$value": "#AAA8C9", "$type": "color" },
|
||||
"500": { "$value": "#7875A1", "$type": "color" },
|
||||
"600": { "$value": "#56557A", "$type": "color" },
|
||||
"700": { "$value": "#3B3A56", "$type": "color" },
|
||||
"800": { "$value": "#1F1E32", "$type": "color" },
|
||||
"900": { "$value": "#111120", "$type": "color" }
|
||||
},
|
||||
"synapse": {
|
||||
"50": { "$value": "#FEF0F4", "$type": "color" },
|
||||
"100": { "$value": "#FCE0EA", "$type": "color" },
|
||||
"200": { "$value": "#FAD0DF", "$type": "color" },
|
||||
"300": { "$value": "#F4A0BD", "$type": "color" },
|
||||
"400": { "$value": "#ED608E", "$type": "color" },
|
||||
"500": { "$value": "#D12B6A", "$type": "color" },
|
||||
"600": { "$value": "#BA245F", "$type": "color" },
|
||||
"700": { "$value": "#A82257", "$type": "color" },
|
||||
"800": { "$value": "#831B45", "$type": "color" },
|
||||
"900": { "$value": "#5A132F", "$type": "color" }
|
||||
},
|
||||
"rose": {
|
||||
"50": { "$value": "#FFF1F2", "$type": "color" },
|
||||
"100": { "$value": "#FFE4E6", "$type": "color" },
|
||||
"200": { "$value": "#FECDD3", "$type": "color" },
|
||||
"300": { "$value": "#FDA4AF", "$type": "color" },
|
||||
"400": { "$value": "#FB7185", "$type": "color" },
|
||||
"500": { "$value": "#E11D48", "$type": "color" },
|
||||
"600": { "$value": "#BE123C", "$type": "color" },
|
||||
"700": { "$value": "#9F1239", "$type": "color" },
|
||||
"800": { "$value": "#881337", "$type": "color" },
|
||||
"900": { "$value": "#4C0519", "$type": "color" }
|
||||
},
|
||||
"blue": {
|
||||
"50": { "$value": "#EFF6FF", "$type": "color" },
|
||||
"100": { "$value": "#DBEAFE", "$type": "color" },
|
||||
"200": { "$value": "#BFDBFE", "$type": "color" },
|
||||
"300": { "$value": "#93C5FD", "$type": "color" },
|
||||
"400": { "$value": "#60A5FA", "$type": "color" },
|
||||
"500": { "$value": "#2563EB", "$type": "color" },
|
||||
"600": { "$value": "#1D4ED8", "$type": "color" },
|
||||
"700": { "$value": "#1E40AF", "$type": "color" },
|
||||
"800": { "$value": "#1E3A8A", "$type": "color" },
|
||||
"900": { "$value": "#172554", "$type": "color" }
|
||||
},
|
||||
"violet": {
|
||||
"50": { "$value": "#F5F3FF", "$type": "color" },
|
||||
"100": { "$value": "#EDE9FE", "$type": "color" },
|
||||
"200": { "$value": "#DDD6FE", "$type": "color" },
|
||||
"300": { "$value": "#C4B5FD", "$type": "color" },
|
||||
"400": { "$value": "#A78BFA", "$type": "color" },
|
||||
"500": { "$value": "#7C3AED", "$type": "color" },
|
||||
"600": { "$value": "#6D28D9", "$type": "color" },
|
||||
"700": { "$value": "#5B21B6", "$type": "color" },
|
||||
"800": { "$value": "#4C1D95", "$type": "color" },
|
||||
"900": { "$value": "#2E1065", "$type": "color" }
|
||||
},
|
||||
"green": {
|
||||
"50": { "$value": "#ECFDF5", "$type": "color" },
|
||||
"100": { "$value": "#D1FAE5", "$type": "color" },
|
||||
"200": { "$value": "#BAEFD3", "$type": "color" },
|
||||
"300": { "$value": "#6EE7B7", "$type": "color" },
|
||||
"400": { "$value": "#34D399", "$type": "color" },
|
||||
"500": { "$value": "#0E9F6E", "$type": "color" },
|
||||
"600": { "$value": "#0B8861", "$type": "color" },
|
||||
"700": { "$value": "#086B4D", "$type": "color" },
|
||||
"800": { "$value": "#064E3B", "$type": "color" },
|
||||
"900": { "$value": "#022C22", "$type": "color" }
|
||||
},
|
||||
"amber": {
|
||||
"50": { "$value": "#FFFBEB", "$type": "color" },
|
||||
"100": { "$value": "#FEF3C7", "$type": "color" },
|
||||
"200": { "$value": "#FDE68A", "$type": "color" },
|
||||
"300": { "$value": "#FCD34D", "$type": "color" },
|
||||
"400": { "$value": "#FBBF24", "$type": "color" },
|
||||
"500": { "$value": "#D97706", "$type": "color" },
|
||||
"600": { "$value": "#B45309", "$type": "color" },
|
||||
"700": { "$value": "#92400E", "$type": "color" },
|
||||
"800": { "$value": "#78350F", "$type": "color" },
|
||||
"900": { "$value": "#451A03", "$type": "color" }
|
||||
},
|
||||
"red": {
|
||||
"50": { "$value": "#FEF2F2", "$type": "color" },
|
||||
"100": { "$value": "#FEE2E2", "$type": "color" },
|
||||
"200": { "$value": "#FECACA", "$type": "color" },
|
||||
"300": { "$value": "#FCA5A5", "$type": "color" },
|
||||
"400": { "$value": "#F87171", "$type": "color" },
|
||||
"500": { "$value": "#DC2626", "$type": "color" },
|
||||
"600": { "$value": "#B91C1C", "$type": "color" },
|
||||
"700": { "$value": "#991B1B", "$type": "color" },
|
||||
"800": { "$value": "#7F1D1D", "$type": "color" },
|
||||
"900": { "$value": "#450A0A", "$type": "color" }
|
||||
},
|
||||
"cyan": {
|
||||
"50": { "$value": "#ECFEFF", "$type": "color" },
|
||||
"100": { "$value": "#CFFAFE", "$type": "color" },
|
||||
"200": { "$value": "#A5F3FC", "$type": "color" },
|
||||
"300": { "$value": "#67E8F9", "$type": "color" },
|
||||
"400": { "$value": "#22D3EE", "$type": "color" },
|
||||
"500": { "$value": "#0891B2", "$type": "color" },
|
||||
"600": { "$value": "#0E7490", "$type": "color" },
|
||||
"700": { "$value": "#155E75", "$type": "color" },
|
||||
"800": { "$value": "#164E63", "$type": "color" },
|
||||
"900": { "$value": "#083344", "$type": "color" }
|
||||
},
|
||||
"slate": {
|
||||
"50": { "$value": "#F8FAFC", "$type": "color" },
|
||||
"100": { "$value": "#F1F5F9", "$type": "color" },
|
||||
"200": { "$value": "#E2E8F0", "$type": "color" },
|
||||
"300": { "$value": "#CBD5E1", "$type": "color" },
|
||||
"400": { "$value": "#94A3B8", "$type": "color" },
|
||||
"500": { "$value": "#475569", "$type": "color" },
|
||||
"600": { "$value": "#334155", "$type": "color" },
|
||||
"700": { "$value": "#1E293B", "$type": "color" },
|
||||
"800": { "$value": "#0F172A", "$type": "color" },
|
||||
"900": { "$value": "#020617", "$type": "color" }
|
||||
}
|
||||
},
|
||||
"spacing": {
|
||||
"0": { "$value": "0px", "$type": "dimension" },
|
||||
"1": { "$value": "4px", "$type": "dimension" },
|
||||
"2": { "$value": "8px", "$type": "dimension" },
|
||||
"3": { "$value": "12px", "$type": "dimension" },
|
||||
"4": { "$value": "16px", "$type": "dimension" },
|
||||
"5": { "$value": "20px", "$type": "dimension" },
|
||||
"6": { "$value": "24px", "$type": "dimension" },
|
||||
"7": { "$value": "32px", "$type": "dimension" },
|
||||
"8": { "$value": "40px", "$type": "dimension" },
|
||||
"9": { "$value": "48px", "$type": "dimension" },
|
||||
"10": { "$value": "64px", "$type": "dimension" },
|
||||
"11": { "$value": "80px", "$type": "dimension" },
|
||||
"12": { "$value": "120px", "$type": "dimension" }
|
||||
},
|
||||
"radius": {
|
||||
"sm": { "$value": "8px", "$type": "dimension" },
|
||||
"md": { "$value": "12px", "$type": "dimension" },
|
||||
"card": { "$value": "20px", "$type": "dimension" },
|
||||
"panel": { "$value": "24px", "$type": "dimension" },
|
||||
"icon": { "$value": "12px", "$type": "dimension" },
|
||||
"pill": { "$value": "9999px", "$type": "dimension" }
|
||||
},
|
||||
"fontSize": {
|
||||
"xs": { "$value": "11px", "$type": "dimension" },
|
||||
"sm": { "$value": "13px", "$type": "dimension" },
|
||||
"base": { "$value": "15px", "$type": "dimension" },
|
||||
"lg": { "$value": "17px", "$type": "dimension" },
|
||||
"xl": { "$value": "20px", "$type": "dimension" },
|
||||
"2xl": { "$value": "24px", "$type": "dimension" },
|
||||
"3xl": { "$value": "30px", "$type": "dimension" },
|
||||
"4xl": { "$value": "36px", "$type": "dimension" },
|
||||
"5xl": { "$value": "48px", "$type": "dimension" }
|
||||
},
|
||||
"fontWeight": {
|
||||
"regular": { "$value": 400, "$type": "fontWeight" },
|
||||
"medium": { "$value": 500, "$type": "fontWeight" },
|
||||
"semi": { "$value": 600, "$type": "fontWeight" },
|
||||
"bold": { "$value": 700, "$type": "fontWeight" },
|
||||
"extra": { "$value": 800, "$type": "fontWeight" }
|
||||
},
|
||||
"duration": {
|
||||
"fast": { "$value": "120ms", "$type": "duration" },
|
||||
"base": { "$value": "200ms", "$type": "duration" },
|
||||
"slow": { "$value": "320ms", "$type": "duration" }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user