- Canonical bytelyst.tokens.json with colors, typography, spacing, radius, elevation, motion, breakpoints - loadTokens() for programmatic access - generate.ts script outputs 4 platform formats: - tokens.css (CSS custom properties with --ml-* prefix) - tokens.ts (TypeScript constants) - MindLystTokens.kt (Kotlin object for KMP shared module) - MindLystTheme.swift (Swift structs for SwiftUI) - Shared across LysnrAI dashboards and MindLyst native apps
157 lines
5.8 KiB
TypeScript
157 lines
5.8 KiB
TypeScript
/**
|
|
* Token generator — reads bytelyst.tokens.json, outputs 4 platform formats:
|
|
* 1. CSS custom properties (tokens.css)
|
|
* 2. TypeScript constants (tokens.ts)
|
|
* 3. Kotlin object (MindLystTokens.kt) — for KMP shared module
|
|
* 4. Swift structs (MindLystTheme.swift) — for iOS SwiftUI
|
|
*
|
|
* Usage: tsx scripts/generate.ts
|
|
*/
|
|
|
|
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
import { resolve, dirname } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const tokensPath = resolve(__dirname, "../tokens/bytelyst.tokens.json");
|
|
const outDir = resolve(__dirname, "../generated");
|
|
|
|
mkdirSync(outDir, { recursive: true });
|
|
|
|
const tokens = JSON.parse(readFileSync(tokensPath, "utf-8"));
|
|
|
|
// ── 1. CSS ───────────────────────────────────────────────────────────
|
|
function generateCSS(): string {
|
|
const lines: string[] = [
|
|
"/* Auto-generated from bytelyst.tokens.json — do not edit manually */",
|
|
"",
|
|
":root {",
|
|
];
|
|
|
|
// Semantic colors (dark theme as default)
|
|
for (const [key, value] of Object.entries(tokens.color.semantic.dark)) {
|
|
lines.push(` --ml-${camelToKebab(key)}: ${value};`);
|
|
}
|
|
|
|
// Typography
|
|
for (const [key, value] of Object.entries(tokens.typography.fontFamily)) {
|
|
lines.push(` --ml-font-${key}: ${value};`);
|
|
}
|
|
for (const [key, value] of Object.entries(tokens.typography.fontSize)) {
|
|
lines.push(` --ml-text-${key}: ${value}px;`);
|
|
}
|
|
|
|
// Spacing
|
|
for (const [key, value] of Object.entries(tokens.spacing)) {
|
|
lines.push(` --ml-space-${key}: ${value}px;`);
|
|
}
|
|
|
|
// Radius
|
|
for (const [key, value] of Object.entries(tokens.radius)) {
|
|
lines.push(` --ml-radius-${key}: ${value}px;`);
|
|
}
|
|
|
|
// Elevation
|
|
for (const [key, value] of Object.entries(tokens.elevation)) {
|
|
lines.push(` --ml-shadow-${key}: ${value};`);
|
|
}
|
|
|
|
lines.push("}", "");
|
|
return lines.join("\n");
|
|
}
|
|
|
|
// ── 2. TypeScript ────────────────────────────────────────────────────
|
|
function generateTS(): string {
|
|
return [
|
|
"// Auto-generated from bytelyst.tokens.json — do not edit manually",
|
|
"",
|
|
`export const tokens = ${JSON.stringify(tokens, null, 2)} as const;`,
|
|
"",
|
|
"export type Tokens = typeof tokens;",
|
|
"",
|
|
].join("\n");
|
|
}
|
|
|
|
// ── 3. Kotlin ────────────────────────────────────────────────────────
|
|
function generateKotlin(): string {
|
|
const lines: string[] = [
|
|
"// Auto-generated from bytelyst.tokens.json — do not edit manually",
|
|
"package com.mindlyst.shared.theme",
|
|
"",
|
|
"object MindLystTokens {",
|
|
"",
|
|
" // Semantic colors (dark)",
|
|
" object Colors {",
|
|
];
|
|
|
|
for (const [key, value] of Object.entries(tokens.color.semantic.dark)) {
|
|
if (typeof value === "string" && value.startsWith("#")) {
|
|
const hex = value.replace("#", "");
|
|
lines.push(` const val ${key} = 0xFF${hex.toUpperCase()}`);
|
|
}
|
|
}
|
|
|
|
lines.push(" }", "", " // Brain gradients", " object Brains {");
|
|
for (const [name, grad] of Object.entries(tokens.color.brain) as [string, { from: string; to: string }][]) {
|
|
const fromHex = grad.from.replace("#", "");
|
|
const toHex = grad.to.replace("#", "");
|
|
lines.push(` val ${name}From = 0xFF${fromHex.toUpperCase()}`);
|
|
lines.push(` val ${name}To = 0xFF${toHex.toUpperCase()}`);
|
|
}
|
|
|
|
lines.push(" }", "", " // Spacing (8pt grid)", " object Spacing {");
|
|
for (const [key, value] of Object.entries(tokens.spacing)) {
|
|
lines.push(` const val x${key} = ${value}`);
|
|
}
|
|
|
|
lines.push(" }", "", " // Radius", " object Radius {");
|
|
for (const [key, value] of Object.entries(tokens.radius)) {
|
|
lines.push(` const val ${key} = ${value}`);
|
|
}
|
|
|
|
lines.push(" }", "}");
|
|
return lines.join("\n");
|
|
}
|
|
|
|
// ── 4. Swift ─────────────────────────────────────────────────────────
|
|
function generateSwift(): string {
|
|
const lines: string[] = [
|
|
"// Auto-generated from bytelyst.tokens.json — do not edit manually",
|
|
"import SwiftUI",
|
|
"",
|
|
"struct MindLystColors {",
|
|
];
|
|
|
|
for (const [key, value] of Object.entries(tokens.color.semantic.dark)) {
|
|
if (typeof value === "string" && value.startsWith("#")) {
|
|
lines.push(` static let ${key} = Color(hex: "${value}")`);
|
|
}
|
|
}
|
|
|
|
lines.push("}", "", "struct MindLystSpacing {");
|
|
for (const [key, value] of Object.entries(tokens.spacing)) {
|
|
lines.push(` static let x${key}: CGFloat = ${value}`);
|
|
}
|
|
|
|
lines.push("}", "", "struct MindLystRadius {");
|
|
for (const [key, value] of Object.entries(tokens.radius)) {
|
|
lines.push(` static let ${key}: CGFloat = ${value}`);
|
|
}
|
|
|
|
lines.push("}");
|
|
return lines.join("\n");
|
|
}
|
|
|
|
// ── Helpers ──────────────────────────────────────────────────────────
|
|
function camelToKebab(str: string): string {
|
|
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
}
|
|
|
|
// ── Write all ────────────────────────────────────────────────────────
|
|
writeFileSync(resolve(outDir, "tokens.css"), generateCSS());
|
|
writeFileSync(resolve(outDir, "tokens.ts"), generateTS());
|
|
writeFileSync(resolve(outDir, "MindLystTokens.kt"), generateKotlin());
|
|
writeFileSync(resolve(outDir, "MindLystTheme.swift"), generateSwift());
|
|
|
|
console.log("Generated 4 token files in generated/");
|