feat(design-tokens): add @bytelyst/design-tokens package

- 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
This commit is contained in:
saravanakumardb1 2026-02-12 11:22:52 -08:00
parent cf8781cc11
commit b80d249c78
5 changed files with 403 additions and 0 deletions

View File

@ -0,0 +1,24 @@
{
"name": "@bytelyst/design-tokens",
"version": "0.1.0",
"type": "module",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./tokens.json": "./tokens/bytelyst.tokens.json",
"./css": "./generated/tokens.css"
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": ["dist", "tokens", "generated", "scripts"],
"scripts": {
"build": "tsc",
"generate": "tsx scripts/generate.ts",
"test": "vitest run"
},
"devDependencies": {
"tsx": "^4.0.0"
}
}

View File

@ -0,0 +1,156 @@
/**
* 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/");

View File

@ -0,0 +1,49 @@
/**
* Design tokens programmatic access to the canonical token JSON.
* For generated platform files, see the `generated/` directory.
* For the canonical JSON source, see `tokens/bytelyst.tokens.json`.
*/
import { readFileSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
export interface DesignTokens {
meta: { name: string; version: string; updatedAt: string; scale: string };
color: {
palette: Record<string, Record<string, string>>;
semantic: {
dark: Record<string, string>;
light: Record<string, string>;
};
brain: Record<string, { from: string; to: string }>;
};
typography: {
fontFamily: Record<string, string>;
fontWeight: Record<string, number>;
fontSize: Record<string, number>;
lineHeight: Record<string, number>;
letterSpacing: Record<string, number>;
};
spacing: Record<string, number>;
radius: Record<string, number>;
elevation: Record<string, string>;
motion: {
duration: Record<string, number>;
easing: Record<string, string>;
};
breakpoints: Record<string, number>;
layout: Record<string, number>;
}
let _cached: DesignTokens | null = null;
export function loadTokens(): DesignTokens {
if (_cached) return _cached;
const tokenPath = resolve(__dirname, "../tokens/bytelyst.tokens.json");
const raw = readFileSync(tokenPath, "utf-8");
_cached = JSON.parse(raw) as DesignTokens;
return _cached;
}

View File

@ -0,0 +1,165 @@
{
"meta": {
"name": "ByteLyst Design Tokens",
"version": "1.0.0",
"updatedAt": "2026-02-12",
"scale": "8pt"
},
"color": {
"palette": {
"neutral": {
"0": "#FFFFFF",
"50": "#F6F8FC",
"100": "#EEF2FA",
"200": "#DCE4F2",
"300": "#BFCBDE",
"400": "#92A1BA",
"500": "#6C7C98",
"600": "#55637A",
"700": "#3B455A",
"800": "#1A2335",
"900": "#0E1320",
"950": "#06070A"
},
"brand": {
"blue": "#5A8CFF",
"cyan": "#2EE6D6",
"coral": "#FF6E6E",
"gold": "#FFD166",
"mint": "#34D399",
"warning": "#F59E0B"
}
},
"semantic": {
"dark": {
"bgCanvas": "#06070A",
"bgElevated": "#0E1118",
"surfaceCard": "#121725",
"surfaceMuted": "#1A2335",
"borderDefault": "rgba(255,255,255,0.12)",
"borderStrong": "rgba(255,255,255,0.22)",
"textPrimary": "#EFF4FF",
"textSecondary": "#A5B1C7",
"textTertiary": "#6C7C98",
"accentPrimary": "#5A8CFF",
"accentSecondary": "#2EE6D6",
"success": "#34D399",
"warning": "#F59E0B",
"danger": "#FF6E6E",
"focusRing": "rgba(90,140,255,0.45)",
"overlayScrim": "rgba(5,8,18,0.72)"
},
"light": {
"bgCanvas": "#F6F8FC",
"bgElevated": "#EEF2FA",
"surfaceCard": "#FFFFFF",
"surfaceMuted": "#F3F5FA",
"borderDefault": "rgba(14,19,32,0.12)",
"borderStrong": "rgba(14,19,32,0.24)",
"textPrimary": "#0E1320",
"textSecondary": "#55637A",
"textTertiary": "#6C7C98",
"accentPrimary": "#5A8CFF",
"accentSecondary": "#2EE6D6",
"success": "#13956A",
"warning": "#B87504",
"danger": "#D24242",
"focusRing": "rgba(90,140,255,0.35)",
"overlayScrim": "rgba(10,13,23,0.5)"
}
},
"brain": {
"work": { "from": "#5A8CFF", "to": "#2EE6D6" },
"home": { "from": "#FF6E6E", "to": "#FFD166" },
"money": { "from": "#34D399", "to": "#2EE6D6" },
"health": { "from": "#2EE6D6", "to": "#9FE870" },
"global": { "from": "#7D8FB4", "to": "#A5B1C7" }
}
},
"typography": {
"fontFamily": {
"display": "'Space Grotesk', 'SF Pro Display', sans-serif",
"body": "'DM Sans', 'SF Pro Text', sans-serif",
"mono": "'IBM Plex Mono', 'SF Mono', monospace"
},
"fontWeight": {
"regular": 400,
"medium": 500,
"semibold": 600,
"bold": 700
},
"fontSize": {
"xs": 12,
"sm": 14,
"md": 16,
"lg": 18,
"xl": 22,
"2xl": 28,
"3xl": 36
},
"lineHeight": {
"tight": 1.2,
"normal": 1.45,
"relaxed": 1.65
},
"letterSpacing": {
"tight": -0.02,
"normal": 0,
"wide": 0.02
}
},
"spacing": {
"0": 0,
"1": 4,
"2": 8,
"3": 12,
"4": 16,
"5": 20,
"6": 24,
"7": 28,
"8": 32,
"10": 40,
"12": 48,
"16": 64
},
"radius": {
"xs": 8,
"sm": 12,
"md": 16,
"lg": 20,
"xl": 24,
"pill": 999
},
"elevation": {
"none": "0 0 0 rgba(0,0,0,0)",
"sm": "0 4px 12px rgba(0,0,0,0.12)",
"md": "0 12px 28px rgba(0,0,0,0.18)",
"lg": "0 20px 48px rgba(0,0,0,0.24)"
},
"motion": {
"duration": {
"instant": 70,
"fast": 140,
"base": 220,
"slow": 320
},
"easing": {
"standard": "cubic-bezier(0.2, 0.0, 0.2, 1)",
"decelerate": "cubic-bezier(0.0, 0.0, 0.2, 1)",
"accelerate": "cubic-bezier(0.4, 0.0, 1, 1)"
}
},
"breakpoints": {
"mobile": 0,
"tablet": 768,
"desktop": 1200,
"wide": 1440
},
"layout": {
"maxContentWidth": 1280,
"mobileGutter": 16,
"tabletGutter": 24,
"desktopGutter": 32,
"touchTargetMin": 44
}
}

View File

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["src/**/*.test.ts"]
}