tokka 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ var __dirname = path.dirname(__filename);
15
15
  async function initCommand(options) {
16
16
  console.log(kleur.bold().cyan("\n\u2728 Welcome to tokka\n"));
17
17
  const cwd = process.cwd();
18
- const systemsDir = path.join(__dirname, "../../systems");
18
+ const systemsDir = path.join(__dirname, "../systems");
19
19
  const systems = await loadSystems(systemsDir);
20
20
  let systemId = options.system;
21
21
  if (!systemId) {
@@ -196,7 +196,7 @@ async function addCommand(components) {
196
196
  spinner.start(`Adding ${component}...`);
197
197
  const sourceFile = path2.join(
198
198
  __dirname2,
199
- "../../ui/src",
199
+ "../components",
200
200
  COMPONENT_REGISTRY[component]
201
201
  );
202
202
  const targetFile = path2.join(componentsDir, COMPONENT_REGISTRY[component]);
@@ -378,7 +378,7 @@ async function listCommand(type, options) {
378
378
  }
379
379
  }
380
380
  async function listSystems(json) {
381
- const systemsDir = path6.join(__dirname6, "../../systems");
381
+ const systemsDir = path6.join(__dirname6, "../systems");
382
382
  const systems = await loadSystemsMetadata(systemsDir);
383
383
  if (json) {
384
384
  console.log(JSON.stringify(systems, null, 2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokka",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "type": "module",
5
5
  "description": "A shadcn-compatible UI foundation with a real token system and optional Figma exports",
6
6
  "bin": "./dist/index.js",
@@ -17,7 +17,8 @@
17
17
  "compiler"
18
18
  ],
19
19
  "scripts": {
20
- "build": "tsup src/index.ts --format esm --dts --clean",
20
+ "build": "pnpm build:compiler && tsup src/index.ts --format esm --dts --clean",
21
+ "build:compiler": "cd ../compiler && tsup src/index.ts --format esm --outDir ../cli/compiler --dts --clean",
21
22
  "dev": "tsup src/index.ts --format esm --dts --watch",
22
23
  "test": "vitest run",
23
24
  "typecheck": "tsc --noEmit"
@@ -1,146 +0,0 @@
1
- import { type ResolvedToken, type System } from "../types.js"
2
-
3
- export interface CSSGeneratorOptions {
4
- modeSelector?: {
5
- strategy: "class" | "data-attribute"
6
- selectors?: {
7
- light: string
8
- dark: string
9
- [key: string]: string
10
- }
11
- }
12
- }
13
-
14
- /**
15
- * Convert token ID to CSS variable name
16
- * surface.brand → --surface-brand
17
- * button.primary.bg → --button-primary-bg
18
- */
19
- export function tokenIdToCSSVar(id: string): string {
20
- return `--${id.replace(/\./g, "-")}`
21
- }
22
-
23
- /**
24
- * Parse color value to HSL triplet format
25
- * Supports: hsl(...), oklch(...), hex, rgb(...)
26
- */
27
- export function parseColorToHSLTriplet(value: string): string {
28
- // If already a triplet (e.g., "220 90% 56%"), return as-is
29
- if (/^\d+\s+\d+%\s+\d+%$/.test(value.trim())) {
30
- return value.trim()
31
- }
32
-
33
- // If it's hsl(...), extract the triplet
34
- const hslMatch = value.match(/hsl\(([^)]+)\)/)
35
- if (hslMatch) {
36
- return hslMatch[1].trim()
37
- }
38
-
39
- // If it's oklch(...), we'll need conversion (simplified for v1)
40
- const oklchMatch = value.match(/oklch\(([^)]+)\)/)
41
- if (oklchMatch) {
42
- // For v1, just pass through - users should provide HSL
43
- // In production, would convert OKLCH to HSL
44
- return value
45
- }
46
-
47
- // For other formats, pass through as-is
48
- return value
49
- }
50
-
51
- /**
52
- * Generate CSS variables for a mode
53
- */
54
- export function generateCSSForMode(
55
- tokens: ResolvedToken[],
56
- mode: string | "default",
57
- options: CSSGeneratorOptions = {}
58
- ): string {
59
- const lines: string[] = []
60
-
61
- const selector =
62
- mode === "default"
63
- ? ":root"
64
- : options.modeSelector?.strategy === "data-attribute"
65
- ? options.modeSelector.selectors?.[mode] || `[data-theme="${mode}"]`
66
- : options.modeSelector?.selectors?.[mode] || `.${mode}`
67
-
68
- lines.push(`${selector} {`)
69
-
70
- for (const token of tokens) {
71
- const cssVar = tokenIdToCSSVar(token.id)
72
- let value: string | undefined
73
-
74
- // Get value for this mode
75
- if (mode === "default") {
76
- value = token.resolvedValue || token.value
77
- } else if (token.resolvedModes?.[mode]) {
78
- value = token.resolvedModes[mode]
79
- } else if (token.modes?.[mode]) {
80
- value = token.modes[mode]
81
- }
82
-
83
- if (value === undefined) continue
84
-
85
- // Convert colors to HSL triplet format
86
- if (token.type === "color") {
87
- const triplet = parseColorToHSLTriplet(String(value))
88
- lines.push(` ${cssVar}: ${triplet};`)
89
- } else {
90
- lines.push(` ${cssVar}: ${value};`)
91
- }
92
- }
93
-
94
- lines.push("}")
95
-
96
- return lines.join("\n")
97
- }
98
-
99
- /**
100
- * Generate complete CSS output
101
- */
102
- export function generateCSS(
103
- tokens: ResolvedToken[],
104
- system: System,
105
- options: CSSGeneratorOptions = {}
106
- ): string {
107
- const output: string[] = []
108
-
109
- // Header comment
110
- output.push("/**")
111
- output.push(` * Design tokens for ${system.name}`)
112
- output.push(" * Generated by figma-base - do not edit directly")
113
- output.push(" */")
114
- output.push("")
115
-
116
- // Generate default (light) mode
117
- const lightMode = system.modes.includes("light") ? "light" : system.modes[0]
118
- output.push(generateCSSForMode(tokens, "default", options))
119
-
120
- // Generate other modes
121
- for (const mode of system.modes) {
122
- if (mode !== lightMode) {
123
- output.push("")
124
- output.push(generateCSSForMode(tokens, mode, options))
125
- }
126
- }
127
-
128
- return output.join("\n")
129
- }
130
-
131
- /**
132
- * Generate CSS output files
133
- */
134
- export interface CSSOutput {
135
- "tokens.css": string
136
- }
137
-
138
- export function generateCSSOutput(
139
- tokens: ResolvedToken[],
140
- system: System,
141
- options: CSSGeneratorOptions = {}
142
- ): CSSOutput {
143
- return {
144
- "tokens.css": generateCSS(tokens, system, options),
145
- }
146
- }
@@ -1,147 +0,0 @@
1
- import { type ResolvedToken, type System } from "../types.js"
2
-
3
- /**
4
- * Format a color value for Tokens Studio compatibility
5
- * Converts Tailwind-style HSL (e.g., "220 90% 56%") to proper hsl() format
6
- */
7
- function formatColorValue(value: string): string {
8
- // Check if it's an HSL value without the hsl() wrapper (Tailwind format)
9
- if (/^\d+\s+\d+%\s+\d+%$/.test(value.trim())) {
10
- return `hsl(${value})`
11
- }
12
- // Return as-is for hex, rgb(), hsl(), or other formats
13
- return value
14
- }
15
-
16
- /**
17
- * Format a token value based on its type
18
- */
19
- function formatTokenValue(token: ResolvedToken, value: string): string {
20
- // Don't format references
21
- if (value.startsWith("{") && value.endsWith("}")) {
22
- return value
23
- }
24
-
25
- // Format colors
26
- if (token.type === "color") {
27
- return formatColorValue(value)
28
- }
29
-
30
- return value
31
- }
32
-
33
- /**
34
- * Generate Tokens Studio compatible format
35
- */
36
- export function generateFigmaTokens(
37
- tokens: ResolvedToken[],
38
- _system: System
39
- ): Record<string, any> {
40
- const output: Record<string, any> = {}
41
-
42
- // Group tokens by source
43
- const primitiveTokens = tokens.filter((t) => t.source === "primitive")
44
- const semanticTokens = tokens.filter((t) => t.source === "semantic")
45
- const componentTokens = tokens.filter((t) => t.source === "component")
46
-
47
- // Build nested structure for primitives
48
- const primitiveTree: Record<string, any> = {}
49
- for (const token of primitiveTokens) {
50
- const parts = token.id.split(".")
51
- let current = primitiveTree
52
- for (let i = 0; i < parts.length - 1; i++) {
53
- if (!current[parts[i]]) {
54
- current[parts[i]] = {}
55
- }
56
- current = current[parts[i]]
57
- }
58
- const lastPart = parts[parts.length - 1]
59
- const rawValue = token.resolvedValue || token.value
60
- const formattedValue = formatTokenValue(token, rawValue)
61
-
62
- current[lastPart] = {
63
- value: formattedValue,
64
- type: token.type,
65
- description: token.description,
66
- }
67
- }
68
- output.global = primitiveTree
69
-
70
- // Build nested structure for semantics
71
- const semanticTree: Record<string, any> = {}
72
- for (const token of semanticTokens) {
73
- const parts = token.id.split(".")
74
- let current = semanticTree
75
- for (let i = 0; i < parts.length - 1; i++) {
76
- if (!current[parts[i]]) {
77
- current[parts[i]] = {}
78
- }
79
- current = current[parts[i]]
80
- }
81
- const lastPart = parts[parts.length - 1]
82
-
83
- // Use reference format if token has references
84
- const rawValue =
85
- token.references && token.references.length > 0
86
- ? `{${token.references[0]}}`
87
- : token.resolvedValue || token.value
88
-
89
- const formattedValue = formatTokenValue(token, rawValue)
90
-
91
- current[lastPart] = {
92
- value: formattedValue,
93
- type: token.type,
94
- description: token.description,
95
- }
96
- }
97
- output.semantic = semanticTree
98
-
99
- // Build component tokens if any
100
- if (componentTokens.length > 0) {
101
- const componentTree: Record<string, any> = {}
102
- for (const token of componentTokens) {
103
- const parts = token.id.split(".")
104
- let current = componentTree
105
- for (let i = 0; i < parts.length - 1; i++) {
106
- if (!current[parts[i]]) {
107
- current[parts[i]] = {}
108
- }
109
- current = current[parts[i]]
110
- }
111
- const lastPart = parts[parts.length - 1]
112
-
113
- const rawValue =
114
- token.references && token.references.length > 0
115
- ? `{${token.references[0]}}`
116
- : token.resolvedValue || token.value
117
-
118
- const formattedValue = formatTokenValue(token, rawValue)
119
-
120
- current[lastPart] = {
121
- value: formattedValue,
122
- type: token.type,
123
- description: token.description,
124
- }
125
- }
126
- output.component = componentTree
127
- }
128
-
129
- return output
130
- }
131
-
132
- /**
133
- * Generate Figma token export file
134
- */
135
- export interface FigmaTokenOutput {
136
- "tokka.tokens.json": string
137
- }
138
-
139
- export function generateFigmaTokenOutput(
140
- tokens: ResolvedToken[],
141
- system: System
142
- ): FigmaTokenOutput {
143
- const tokensObject = generateFigmaTokens(tokens, system)
144
- return {
145
- "tokka.tokens.json": JSON.stringify(tokensObject, null, 2),
146
- }
147
- }
@@ -1,106 +0,0 @@
1
- import { type ResolvedToken, type System } from "../types.js"
2
- import { tokenIdToCSSVar } from "./css.js"
3
-
4
- /**
5
- * Semantic token mapping to Tailwind color names
6
- * Maps semantic tokens to Tailwind's expected color scheme
7
- */
8
- const SEMANTIC_TO_TAILWIND_MAPPING: Record<string, string> = {
9
- "surface.brand": "primary",
10
- "text.on-brand": "primary-foreground",
11
- "surface.secondary": "secondary",
12
- "text.on-secondary": "secondary-foreground",
13
- "surface.destructive": "destructive",
14
- "text.on-destructive": "destructive-foreground",
15
- "surface.default": "background",
16
- "text.default": "foreground",
17
- "surface.card": "card",
18
- "text.on-card": "card-foreground",
19
- "surface.popover": "popover",
20
- "text.on-popover": "popover-foreground",
21
- "surface.muted": "muted",
22
- "text.muted": "muted-foreground",
23
- "surface.accent": "accent",
24
- "text.on-accent": "accent-foreground",
25
- "border.default": "border",
26
- "border.input": "input",
27
- "focus.ring": "ring",
28
- }
29
-
30
- /**
31
- * Generate Tailwind config mapping semantic tokens to CSS vars
32
- */
33
- export function generateTailwindConfig(
34
- tokens: ResolvedToken[],
35
- _system: System
36
- ): Record<string, any> {
37
- const colors: Record<string, string> = {}
38
- const spacing: Record<string, string> = {}
39
- const borderRadius: Record<string, string> = {}
40
- const boxShadow: Record<string, string> = {}
41
-
42
- for (const token of tokens) {
43
- // Only map semantic tokens to Tailwind (not primitives)
44
- if (token.source !== "semantic") continue
45
-
46
- const cssVar = tokenIdToCSSVar(token.id)
47
-
48
- // Map semantic tokens to Tailwind color names
49
- if (token.type === "color") {
50
- const tailwindName = SEMANTIC_TO_TAILWIND_MAPPING[token.id]
51
- if (tailwindName) {
52
- colors[tailwindName] = `hsl(var(${cssVar}))`
53
- } else {
54
- // Also include the semantic token by its own name
55
- const name = token.id.replace(/\./g, "-")
56
- colors[name] = `hsl(var(${cssVar}))`
57
- }
58
- }
59
-
60
- // Map spacing tokens
61
- if (token.type === "dimension" && token.id.startsWith("space.")) {
62
- const name = token.id.replace("space.", "")
63
- spacing[name] = `var(${cssVar})`
64
- }
65
-
66
- // Map radius tokens
67
- if (token.type === "radius" && token.id.startsWith("radius.")) {
68
- const name = token.id.replace("radius.", "")
69
- borderRadius[name] = `var(${cssVar})`
70
- }
71
-
72
- // Map shadow tokens
73
- if (token.type === "shadow" && token.id.startsWith("shadow.")) {
74
- const name = token.id.replace("shadow.", "")
75
- boxShadow[name] = `var(${cssVar})`
76
- }
77
- }
78
-
79
- return {
80
- theme: {
81
- extend: {
82
- colors,
83
- spacing,
84
- borderRadius,
85
- boxShadow,
86
- },
87
- },
88
- }
89
- }
90
-
91
- /**
92
- * Generate Tailwind config file content
93
- */
94
- export function generateTailwindOutput(
95
- tokens: ResolvedToken[],
96
- system: System
97
- ): string {
98
- const config = generateTailwindConfig(tokens, system)
99
-
100
- return `/**
101
- * Tailwind config for ${system.name}
102
- * Generated by figma-base - do not edit directly
103
- */
104
- export default ${JSON.stringify(config, null, 2)}
105
- `
106
- }
@@ -1,113 +0,0 @@
1
- import { type ResolvedToken, type System } from "../types.js"
2
-
3
- /**
4
- * Generate TypeScript token map with full typing
5
- */
6
- export function generateTypeScript(
7
- tokens: ResolvedToken[],
8
- system: System
9
- ): string {
10
- const lines: string[] = []
11
-
12
- // Header
13
- lines.push("/**")
14
- lines.push(` * Token type definitions for ${system.name}`)
15
- lines.push(" * Generated by figma-base - do not edit directly")
16
- lines.push(" */")
17
- lines.push("")
18
-
19
- // Token type enum
20
- lines.push("export type TokenType =")
21
- lines.push(' | "color"')
22
- lines.push(' | "number"')
23
- lines.push(' | "dimension"')
24
- lines.push(' | "radius"')
25
- lines.push(' | "shadow"')
26
- lines.push(' | "typography"')
27
- lines.push(' | "motion"')
28
- lines.push(' | "opacity"')
29
- lines.push(' | "zIndex"')
30
- lines.push("")
31
-
32
- // Token source enum
33
- lines.push("export type TokenSource = \"primitive\" | \"semantic\" | \"component\"")
34
- lines.push("")
35
-
36
- // Token interface
37
- lines.push("export interface Token {")
38
- lines.push(" id: string")
39
- lines.push(" type: TokenType")
40
- lines.push(" source: TokenSource")
41
- lines.push(" description: string")
42
- lines.push(" value?: string")
43
- lines.push(" modes?: Record<string, string>")
44
- lines.push("}")
45
- lines.push("")
46
-
47
- // All token IDs union type
48
- lines.push("export type TokenId =")
49
- tokens.forEach((token, index) => {
50
- const isLast = index === tokens.length - 1
51
- lines.push(` | "${token.id}"${isLast ? "" : ""}`)
52
- })
53
- lines.push("")
54
-
55
- // Token map object
56
- lines.push("export const tokens: Record<TokenId, Token> = {")
57
- for (const token of tokens) {
58
- lines.push(` "${token.id}": {`)
59
- lines.push(` id: "${token.id}",`)
60
- lines.push(` type: "${token.type}",`)
61
- lines.push(` source: "${token.source}",`)
62
- lines.push(` description: ${JSON.stringify(token.description)},`)
63
-
64
- if (token.resolvedValue) {
65
- lines.push(` value: ${JSON.stringify(token.resolvedValue)},`)
66
- }
67
-
68
- if (token.resolvedModes && Object.keys(token.resolvedModes).length > 0) {
69
- lines.push(` modes: {`)
70
- for (const [mode, value] of Object.entries(token.resolvedModes)) {
71
- lines.push(` "${mode}": ${JSON.stringify(value)},`)
72
- }
73
- lines.push(` },`)
74
- }
75
-
76
- lines.push(` },`)
77
- }
78
- lines.push("} as const")
79
- lines.push("")
80
-
81
- // Helper functions
82
- lines.push("export function getToken(id: TokenId): Token | undefined {")
83
- lines.push(" return tokens[id]")
84
- lines.push("}")
85
- lines.push("")
86
-
87
- lines.push("export function getTokensByType(type: TokenType): Token[] {")
88
- lines.push(" return Object.values(tokens).filter(t => t.type === type)")
89
- lines.push("}")
90
- lines.push("")
91
-
92
- lines.push("export function getTokensBySource(source: TokenSource): Token[] {")
93
- lines.push(" return Object.values(tokens).filter(t => t.source === source)")
94
- lines.push("}")
95
-
96
- return lines.join("\n")
97
- }
98
-
99
- /**
100
- * Generate TypeScript output
101
- */
102
- export interface TypeScriptOutput {
103
- "tokens.ts": string
104
- }
105
-
106
- export function generateTypeScriptOutput(
107
- tokens: ResolvedToken[],
108
- system: System
109
- ): TypeScriptOutput {
110
- return {
111
- "tokens.ts": generateTypeScript(tokens, system),
112
- }
113
- }
package/compiler/index.ts DELETED
@@ -1,45 +0,0 @@
1
- export * from "./types.js"
2
- export * from "./loader.js"
3
- export * from "./validator.js"
4
- export * from "./resolver.js"
5
- export * from "./generators/css.js"
6
- export * from "./generators/tailwind.js"
7
- export * from "./generators/typescript.js"
8
- export * from "./generators/figma.js"
9
-
10
- import { loadTokens, loadSystem, type LoadOptions } from "./loader.js"
11
- import { validateTokens } from "./validator.js"
12
- import { buildDependencyGraph, resolveTokens } from "./resolver.js"
13
- import { type CompilationResult } from "./types.js"
14
-
15
- /**
16
- * Main compilation function
17
- */
18
- export async function compile(options: LoadOptions): Promise<CompilationResult> {
19
- // Load tokens and system
20
- const loadedTokens = await loadTokens(options)
21
- const system = await loadSystem(options)
22
-
23
- // Validate tokens
24
- const validationErrors = validateTokens(loadedTokens.all, system.modes, {
25
- strict: false, // Tier 0 default
26
- })
27
-
28
- if (validationErrors.length > 0) {
29
- return {
30
- tokens: [],
31
- errors: validationErrors,
32
- warnings: [],
33
- }
34
- }
35
-
36
- // Build dependency graph and resolve
37
- const graph = buildDependencyGraph(loadedTokens.all)
38
- const { resolved, errors } = resolveTokens(loadedTokens.all, graph)
39
-
40
- return {
41
- tokens: resolved,
42
- errors,
43
- warnings: [],
44
- }
45
- }
@@ -1,92 +0,0 @@
1
- import fs from "fs/promises"
2
- import path from "path"
3
- import { tokenFileSchema, systemSchema, type Token, type System } from "./types.js"
4
-
5
- export interface LoadOptions {
6
- cwd: string
7
- tokensDir?: string
8
- }
9
-
10
- export interface LoadedTokens {
11
- primitives: Token[]
12
- semantics: Token[]
13
- components: Token[]
14
- all: Token[]
15
- }
16
-
17
- /**
18
- * Load token files from a project directory
19
- */
20
- export async function loadTokens(options: LoadOptions): Promise<LoadedTokens> {
21
- const tokensDir = options.tokensDir || path.join(options.cwd, "tokens")
22
-
23
- // Load primitives (required)
24
- const primitivesPath = path.join(tokensDir, "primitives.json")
25
- const primitivesContent = await fs.readFile(primitivesPath, "utf-8")
26
- const primitivesData = tokenFileSchema.parse(JSON.parse(primitivesContent))
27
- const primitives = primitivesData.tokens
28
-
29
- // Load semantics (required)
30
- const semanticsPath = path.join(tokensDir, "semantics.json")
31
- const semanticsContent = await fs.readFile(semanticsPath, "utf-8")
32
- const semanticsData = tokenFileSchema.parse(JSON.parse(semanticsContent))
33
- const semantics = semanticsData.tokens
34
-
35
- // Load component tokens (optional - Tier 2)
36
- const components: Token[] = []
37
- const componentsDir = path.join(tokensDir, "components")
38
- try {
39
- const componentFiles = await fs.readdir(componentsDir)
40
- for (const file of componentFiles) {
41
- if (file.endsWith(".json")) {
42
- const componentPath = path.join(componentsDir, file)
43
- const componentContent = await fs.readFile(componentPath, "utf-8")
44
- const componentData = tokenFileSchema.parse(JSON.parse(componentContent))
45
- components.push(...componentData.tokens)
46
- }
47
- }
48
- } catch (error) {
49
- // Components directory is optional
50
- }
51
-
52
- return {
53
- primitives,
54
- semantics,
55
- components,
56
- all: [...primitives, ...semantics, ...components],
57
- }
58
- }
59
-
60
- /**
61
- * Load system metadata
62
- */
63
- export async function loadSystem(options: LoadOptions): Promise<System> {
64
- const systemPath = path.join(options.cwd, "system.json")
65
- const systemContent = await fs.readFile(systemPath, "utf-8")
66
- const system = systemSchema.parse(JSON.parse(systemContent))
67
- return system
68
- }
69
-
70
- /**
71
- * Check if tokens directory exists
72
- */
73
- export async function hasTokens(cwd: string): Promise<boolean> {
74
- try {
75
- await fs.access(path.join(cwd, "tokens"))
76
- return true
77
- } catch {
78
- return false
79
- }
80
- }
81
-
82
- /**
83
- * Check if system.json exists
84
- */
85
- export async function hasSystem(cwd: string): Promise<boolean> {
86
- try {
87
- await fs.access(path.join(cwd, "system.json"))
88
- return true
89
- } catch {
90
- return false
91
- }
92
- }