Files
KArtSell.Aegis/docs/Design/kbx-foundation-v60-status-canonical-contract-hardening/scripts/generate-design-tokens.mjs
T
kjh2064 3f293d8aa8
deploy / deploy (push) Successful in 1m52s
deploy / notify (push) Successful in 1s
V13-FE-006: consolidate approved UI and contract hardening
2026-08-13 02:41:00 +09:00

126 lines
5.8 KiB
JavaScript

import fs from 'node:fs'
import path from 'node:path'
const root = process.cwd()
const sourcePath = path.join(root, 'packages/kbx-ui/src/tokens/source/kbx.tokens.json')
const cssPath = path.join(root, 'packages/kbx-ui/src/tokens/kbx.css')
const manifestPath = path.join(root, 'generated/token-manifest.json')
const figmaPath = path.join(root, 'design/figma/variables.contract.json')
const source = JSON.parse(fs.readFileSync(sourcePath, 'utf8'))
const byPath = new Map(source.tokens.map(t => [t.path, t]))
function cssValue(value) {
const ref = /^\{(.+)\}$/.exec(value)
if (!ref) return value
const token = byPath.get(ref[1])
if (!token) throw new Error(`Unknown token reference: ${value}`)
return `var(${token.css})`
}
const roles = ['primitive', 'semantic', 'component']
const lines = [':root {']
for (const role of roles) {
lines.push(` /* ${role[0].toUpperCase()}${role.slice(1)} tokens */`)
for (const token of source.tokens.filter(t => t.role === role)) {
lines.push(` ${token.css}: ${cssValue(token.value)};`)
}
lines.push('')
}
lines.push('}')
lines.push('')
for (const [theme, definition] of Object.entries(source.themes ?? {})) {
const overrides = definition.overrides ?? {}
if (!Object.keys(overrides).length) continue
lines.push(`[data-kbx-theme=\"${theme}\"] {`)
if (definition.colorScheme) lines.push(` color-scheme: ${definition.colorScheme};`)
for (const [tokenPath, value] of Object.entries(overrides)) {
const token = byPath.get(tokenPath)
if (!token) throw new Error(`Unknown theme token: ${tokenPath}`)
lines.push(` ${token.css}: ${cssValue(value)};`)
}
lines.push('}')
lines.push('')
}
for (const [module, modes] of Object.entries(source.moduleThemes ?? {})) {
for (const [theme, overrides] of Object.entries(modes ?? {})) {
if (!Object.keys(overrides ?? {}).length) continue
const selector = theme === 'light'
? `[data-kbx-module=\"${module}\"]`
: `[data-kbx-theme=\"${theme}\"] [data-kbx-module=\"${module}\"]`
lines.push(`${selector} {`)
for (const [tokenPath, value] of Object.entries(overrides)) {
const token = byPath.get(tokenPath)
if (!token) throw new Error(`Unknown module theme token: ${tokenPath}`)
lines.push(` ${token.css}: ${cssValue(value)};`)
}
lines.push('}')
lines.push('')
}
}
lines.push('html, body, button, input, select, textarea {')
lines.push(' font-family: var(--kbx-font-family);')
lines.push('}')
lines.push('')
for (const [mode, overrides] of Object.entries(source.modes ?? {})) {
if (mode === 'compact') continue
lines.push(`[data-kbx-density="${mode}"] {`)
for (const [tokenPath, value] of Object.entries(overrides)) {
const token = byPath.get(tokenPath)
if (!token) throw new Error(`Unknown mode token: ${tokenPath}`)
lines.push(` ${token.css}: ${value};`)
}
lines.push('}')
lines.push('')
}
lines.push(':focus-visible {')
lines.push(' outline: 2px solid var(--kbx-color-focus);')
lines.push(' outline-offset: 2px;')
lines.push('}')
lines.push('')
lines.push('@media (forced-colors: active) {')
lines.push(' :root, [data-kbx-theme="light"], [data-kbx-theme="dark"] {')
lines.push(' --kbx-color-surface: Canvas;')
lines.push(' --kbx-color-surface-muted: Canvas;')
lines.push(' --kbx-color-surface-subtle: Canvas;')
lines.push(' --kbx-color-surface-hover: Highlight;')
lines.push(' --kbx-color-text: CanvasText;')
lines.push(' --kbx-color-text-muted: CanvasText;')
lines.push(' --kbx-color-border: ButtonBorder;')
lines.push(' --kbx-color-border-strong: CanvasText;')
lines.push(' --kbx-color-primary: LinkText;')
lines.push(' --kbx-color-success: CanvasText;')
lines.push(' --kbx-color-warning: CanvasText;')
lines.push(' --kbx-color-danger: CanvasText;')
lines.push(' --kbx-color-focus: Highlight;')
lines.push(' --kbx-color-info-surface: Canvas;')
lines.push(' --kbx-color-success-surface: Canvas;')
lines.push(' --kbx-color-warning-surface: Canvas;')
lines.push(' --kbx-color-danger-surface: Canvas;')
lines.push(' --kbx-color-ai-surface: Canvas;')
lines.push(' --kbx-color-module-accent: Highlight;')
lines.push(' --kbx-color-module-accent-surface: Canvas;')
lines.push(' --kbx-color-module-accent-border: Highlight;')
lines.push(' --kbx-color-workspace: Canvas;')
lines.push(' --kbx-color-shell-chrome: Canvas;')
lines.push(' --kbx-color-navigation-surface: Canvas;')
lines.push(' --kbx-color-section-heading: Canvas;')
lines.push(' --kbx-shadow-shell: none;')
lines.push(' --kbx-shadow-overlay: none;')
lines.push(' }')
lines.push(' :focus-visible { outline-color: Highlight; }')
lines.push('}')
lines.push('')
fs.mkdirSync(path.dirname(manifestPath), { recursive: true })
fs.mkdirSync(path.dirname(figmaPath), { recursive: true })
fs.writeFileSync(cssPath, lines.join('\n'))
fs.writeFileSync(manifestPath, JSON.stringify({version: source.version, tokens: source.tokens, modes: source.modes, themes: source.themes, moduleThemes: source.moduleThemes}, null, 2) + '\n')
const collections = {
'KBX Primitive': source.tokens.filter(t => t.role === 'primitive').map(t => ({name:t.path, type:t.type, value:t.value, description:t.description ?? ''})),
'KBX Semantic': source.tokens.filter(t => t.role === 'semantic').map(t => ({name:t.path, type:t.type, value:t.value, description:t.description ?? ''})),
'KBX Component': source.tokens.filter(t => t.role === 'component').map(t => ({name:t.path, type:t.type, value:t.value, description:t.description ?? '', modes:Object.fromEntries(Object.entries(source.modes ?? {}).filter(([,o]) => t.path in o).map(([mode,o]) => [mode,o[t.path]]))}))
}
fs.writeFileSync(figmaPath, JSON.stringify({schemaVersion:'1.0', tokenVersion:source.version, collections, themes:source.themes ?? {}, moduleThemes:source.moduleThemes ?? {}}, null, 2) + '\n')
console.log(`Generated ${source.tokens.length} design tokens -> CSS / token manifest / Figma variable contract`)