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,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)`);
|
||||
Reference in New Issue
Block a user