/** * React Native token generator for Expo/NomGap * Reads bytelyst.tokens.json, outputs RN StyleSheet-compatible tokens * * Usage: tsx scripts/generate-react-native.ts */ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { dirname, resolve } 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/react-native'); mkdirSync(outDir, { recursive: true }); const tokens = JSON.parse(readFileSync(tokensPath, 'utf-8')); // ── Helpers ─────────────────────────────────────────────────────────── function generateReactNative(): string { const lines: string[] = [ '/**', ' * React Native Design Tokens — Auto-generated from bytelyst.tokens.json', ' * Do not edit manually. Run: tsx scripts/generate-react-native.ts', ' */', '', 'export const tokens = {', '', ' // ── Semantic Colors (Dark Theme) ──────────────────────────────────', ' colors: {', ]; // Semantic dark colors for (const [key, value] of Object.entries(tokens.color.semantic.dark)) { if (typeof value === 'string' && value.startsWith('#')) { lines.push(` ${key}: '${value}',`); } else if (typeof value === 'string' && value.startsWith('rgba')) { // Convert rgba to hex8 for React Native const match = value.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/); if (match) { const [, r, g, b, a] = match; const alpha = Math.round(parseFloat(a) * 255) .toString(16) .padStart(2, '0'); const hex = `#${alpha}${parseInt(r).toString(16).padStart(2, '0')}${parseInt(g).toString(16).padStart(2, '0')}${parseInt(b).toString(16).padStart(2, '0')}`.toUpperCase(); lines.push(` ${key}: '${hex}',`); } } } lines.push(' },', ''); // NomGap-specific colors lines.push(' // ── NomGap Product Colors ──────────────────────────────────────────'); lines.push(' nomgap: {'); for (const [key, value] of Object.entries(tokens.color.nomgap)) { lines.push(` ${key}: '${value}',`); } lines.push(' },', ''); // Spacing lines.push(' // ── Spacing (8pt grid) ──────────────────────────────────────────────'); lines.push(' spacing: {'); for (const [key, value] of Object.entries(tokens.spacing)) { lines.push(` ${key}: ${value},`); } lines.push(' },', ''); // Radius lines.push(' // ── Border Radius ───────────────────────────────────────────────────'); lines.push(' radius: {'); for (const [key, value] of Object.entries(tokens.radius)) { lines.push(` ${key}: ${value},`); } lines.push(' },', ''); // Typography lines.push(' // ── Typography ─────────────────────────────────────────────────────'); lines.push(' typography: {'); lines.push(' fontFamily: {'); for (const key of Object.keys(tokens.typography.fontFamily)) { // Use system fonts for React Native const systemFont = key === 'display' ? 'System' : key === 'mono' ? 'Courier' : 'System'; lines.push(` ${key}: '${systemFont}',`); } lines.push(' },'); lines.push(' fontSize: {'); for (const [key, value] of Object.entries(tokens.typography.fontSize)) { lines.push(` ${key}: ${value},`); } lines.push(' },'); lines.push(' fontWeight: {'); for (const [key, value] of Object.entries(tokens.typography.fontWeight)) { lines.push(` ${key}: '${value}',`); } lines.push(' },'); lines.push(' },', ''); // Icon sizes lines.push(' // ── Icon Sizes ──────────────────────────────────────────────────────'); lines.push(' icon: {'); for (const [key, value] of Object.entries(tokens.icon)) { lines.push(` ${key}: ${value},`); } lines.push(' },', ''); // Z-index (for RN zIndex style prop) lines.push(' // ── Z-Index Layers ───────────────────────────────────────────────────'); lines.push(' zIndex: {'); for (const [key, value] of Object.entries(tokens.zIndex)) { lines.push(` ${key}: ${value},`); } lines.push(' },', ''); // Opacity lines.push(' // ── Opacity ─────────────────────────────────────────────────────────'); lines.push(' opacity: {'); for (const [key, value] of Object.entries(tokens.opacity)) { lines.push(` ${key}: ${value},`); } lines.push(' },', ''); // Motion (duration in ms) lines.push(' // ── Motion ────────────────────────────────────────────────────────────'); lines.push(' motion: {'); for (const [key, value] of Object.entries(tokens.motion.duration)) { lines.push(` ${key}: ${value},`); } lines.push(' },', ''); lines.push('} as const;', ''); lines.push(''); lines.push('export type Tokens = typeof tokens;', ''); return lines.join('\n'); } // ── Write ────────────────────────────────────────────────────────── writeFileSync(resolve(outDir, 'tokens.ts'), generateReactNative()); // eslint-disable-next-line no-console console.log('Generated React Native tokens in generated/react-native/');