62317f2ad7
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>
197 lines
6.1 KiB
JavaScript
197 lines
6.1 KiB
JavaScript
#!/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)`);
|