tokka 0.4.1 → 0.4.2

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.
@@ -433,6 +433,10 @@ interface CSSOutput {
433
433
  }
434
434
  declare function generateCSSOutput(tokens: ResolvedToken[], system: System, options?: CSSGeneratorOptions): CSSOutput;
435
435
 
436
+ /**
437
+ * Generate Tailwind v4 CSS theme configuration
438
+ */
439
+ declare function generateTailwindV4CSS(tokens: ResolvedToken[], system: System): string;
436
440
  /**
437
441
  * Generate Tailwind config mapping semantic tokens to CSS vars
438
442
  */
@@ -474,4 +478,4 @@ declare function generateFigmaTokenOutput(tokens: ResolvedToken[], system: Syste
474
478
  */
475
479
  declare function compile(options: LoadOptions): Promise<CompilationResult>;
476
480
 
477
- export { type CSSGeneratorOptions, type CSSOutput, type CompilationContext, type CompilationError, type CompilationResult, type CompilationWarning, type FigmaTokenOutput, type LoadOptions, type LoadedTokens, type ResolvedToken, type System, type Token, type TokenFile, type TokenSource, type TokenType, type TypeScriptOutput, buildDependencyGraph, compile, detectCycles, generateCSS, generateCSSForMode, generateCSSOutput, generateFigmaTokenOutput, generateFigmaTokens, generateTailwindConfig, generateTailwindOutput, generateTypeScript, generateTypeScriptOutput, hasSystem, hasTokens, loadSystem, loadTokens, parseColorToHSLTriplet, resolveTokens, systemSchema, tokenFileSchema, tokenIdToCSSVar, tokenSchema, tokenSourceSchema, tokenTypeSchema, topologicalSort, validateModeCoverage, validateTokenLayering, validateTokenNaming, validateTokenReferences, validateTokenUniqueness, validateTokens };
481
+ export { type CSSGeneratorOptions, type CSSOutput, type CompilationContext, type CompilationError, type CompilationResult, type CompilationWarning, type FigmaTokenOutput, type LoadOptions, type LoadedTokens, type ResolvedToken, type System, type Token, type TokenFile, type TokenSource, type TokenType, type TypeScriptOutput, buildDependencyGraph, compile, detectCycles, generateCSS, generateCSSForMode, generateCSSOutput, generateFigmaTokenOutput, generateFigmaTokens, generateTailwindConfig, generateTailwindOutput, generateTailwindV4CSS, generateTypeScript, generateTypeScriptOutput, hasSystem, hasTokens, loadSystem, loadTokens, parseColorToHSLTriplet, resolveTokens, systemSchema, tokenFileSchema, tokenIdToCSSVar, tokenSchema, tokenSourceSchema, tokenTypeSchema, topologicalSort, validateModeCoverage, validateTokenLayering, validateTokenNaming, validateTokenReferences, validateTokenUniqueness, validateTokens };
package/compiler/index.js CHANGED
@@ -469,6 +469,40 @@ var SEMANTIC_TO_TAILWIND_MAPPING = {
469
469
  "border.input": "input",
470
470
  "focus.ring": "ring"
471
471
  };
472
+ function generateTailwindV4CSS(tokens, system) {
473
+ const themeVars = [];
474
+ for (const token of tokens) {
475
+ if (token.source !== "semantic") continue;
476
+ const cssVar = tokenIdToCSSVar(token.id);
477
+ if (token.type === "color") {
478
+ const tailwindName = SEMANTIC_TO_TAILWIND_MAPPING[token.id];
479
+ if (tailwindName) {
480
+ themeVars.push(` --color-${tailwindName}: hsl(var(${cssVar}));`);
481
+ }
482
+ }
483
+ if (token.type === "dimension" && token.id.startsWith("space.")) {
484
+ const name = token.id.replace("space.", "");
485
+ themeVars.push(` --spacing-${name}: var(${cssVar});`);
486
+ }
487
+ if (token.type === "radius" && token.id.startsWith("radius.")) {
488
+ const name = token.id.replace("radius.", "");
489
+ themeVars.push(` --radius-${name}: var(${cssVar});`);
490
+ }
491
+ }
492
+ return `/**
493
+ * Tailwind CSS v4 configuration for ${system.name}
494
+ * Generated by tokka - do not edit directly
495
+ */
496
+ @import "tailwindcss";
497
+
498
+ /* Import tokka design tokens */
499
+ @import "../dist/css/tokens.css";
500
+
501
+ @theme {
502
+ ${themeVars.join("\n")}
503
+ }
504
+ `;
505
+ }
472
506
  function generateTailwindConfig(tokens, _system) {
473
507
  const colors = {};
474
508
  const spacing = {};
@@ -514,11 +548,11 @@ function generateTailwindOutput(tokens, system) {
514
548
  const config = generateTailwindConfig(tokens, system);
515
549
  return `/**
516
550
  * Tailwind config for ${system.name}
517
- * Generated by figma-base - do not edit directly
551
+ * Generated by tokka - do not edit directly
518
552
  *
519
553
  * Compatible with Tailwind CSS v3.x
520
554
  */
521
- export default ${JSON.stringify(config, null, 2)}
555
+ module.exports = ${JSON.stringify(config, null, 2)}
522
556
  `;
523
557
  }
524
558
 
@@ -727,6 +761,7 @@ export {
727
761
  generateFigmaTokens,
728
762
  generateTailwindConfig,
729
763
  generateTailwindOutput,
764
+ generateTailwindV4CSS,
730
765
  generateTypeScript,
731
766
  generateTypeScriptOutput,
732
767
  hasSystem,
@@ -13,7 +13,7 @@ const badgeVariants = cva(
13
13
  "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
14
14
  destructive:
15
15
  "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
16
- outline: "text-foreground",
16
+ outline: "text-foreground border-border",
17
17
  },
18
18
  },
19
19
  defaultVariants: {
@@ -10,7 +10,7 @@ const Checkbox = React.forwardRef<
10
10
  <CheckboxPrimitive.Root
11
11
  ref={ref}
12
12
  className={cn(
13
- "peer size-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
13
+ "peer size-4 shrink-0 rounded-sm border border-input ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
14
14
  className
15
15
  )}
16
16
  {...props}
@@ -8,7 +8,7 @@ const Switch = React.forwardRef<
8
8
  >(({ className, ...props }, ref) => (
9
9
  <SwitchPrimitives.Root
10
10
  className={cn(
11
- "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
11
+ "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-muted",
12
12
  className
13
13
  )}
14
14
  {...props}
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
+ import { createRequire } from "module";
5
6
 
6
7
  // src/commands/init.ts
7
8
  import fs from "fs-extra";
@@ -87,10 +88,10 @@ Setting up ${selectedSystem.name}...
87
88
  try {
88
89
  const compilerPath = path.join(__dirname, "../compiler/index.js");
89
90
  const { compile, generateCSSOutput, generateTailwindOutput, generateTypeScriptOutput } = await import(compilerPath);
90
- const result = await compile({ cwd });
91
- if (result.errors.length > 0) {
91
+ const result2 = await compile({ cwd });
92
+ if (result2.errors.length > 0) {
92
93
  spinner.fail("Build failed");
93
- for (const error of result.errors) {
94
+ for (const error of result2.errors) {
94
95
  console.error(kleur.red(` \u2716 ${error.message}`));
95
96
  }
96
97
  process.exit(1);
@@ -98,10 +99,10 @@ Setting up ${selectedSystem.name}...
98
99
  await fs.ensureDir(path.join(cwd, "dist/css"));
99
100
  await fs.ensureDir(path.join(cwd, "dist/tailwind"));
100
101
  await fs.ensureDir(path.join(cwd, "dist/ts"));
101
- const systemData = await fs.readJSON(path.join(cwd, "system.json"));
102
- const cssOutput = generateCSSOutput(result.tokens, systemData);
103
- const tailwindOutput = generateTailwindOutput(result.tokens, systemData);
104
- const tsOutput = generateTypeScriptOutput(result.tokens, systemData);
102
+ const systemData2 = await fs.readJSON(path.join(cwd, "system.json"));
103
+ const cssOutput = generateCSSOutput(result2.tokens, systemData2);
104
+ const tailwindOutput = generateTailwindOutput(result2.tokens, systemData2);
105
+ const tsOutput = generateTypeScriptOutput(result2.tokens, systemData2);
105
106
  await fs.writeFile(path.join(cwd, "dist/css/tokens.css"), cssOutput["tokens.css"]);
106
107
  await fs.writeFile(
107
108
  path.join(cwd, "dist/tailwind/tokens.tailwind.js"),
@@ -114,9 +115,39 @@ Setting up ${selectedSystem.name}...
114
115
  console.error(kleur.red(error.message));
115
116
  process.exit(1);
116
117
  }
117
- spinner.start("Creating Tailwind config...");
118
- try {
119
- const tailwindConfig = `/** @type {import('tailwindcss').Config} */
118
+ spinner.start("Detecting Tailwind CSS version...");
119
+ const tailwindVersion = await detectTailwindVersion(cwd);
120
+ const isV4 = tailwindVersion && tailwindVersion.startsWith("4");
121
+ const configProtected = await shouldProtectConfigs(cwd);
122
+ if (configProtected) {
123
+ spinner.warn("Config files detected - preserving your customizations");
124
+ console.log(kleur.dim(" Run 'tokka build' to regenerate tokens only"));
125
+ } else {
126
+ spinner.text = isV4 ? "Creating Tailwind v4 config..." : "Creating Tailwind v3 config...";
127
+ try {
128
+ if (isV4) {
129
+ const compilerPath = path.join(__dirname, "../compiler/index.js");
130
+ const { generateTailwindV4CSS } = await import(compilerPath);
131
+ const cssConfig = generateTailwindV4CSS(result.tokens, systemData);
132
+ const cssPath = await findMainCSSFile(cwd);
133
+ if (cssPath) {
134
+ await fs.writeFile(cssPath, cssConfig);
135
+ spinner.succeed(`Tailwind v4 config created at ${path.relative(cwd, cssPath)}`);
136
+ } else {
137
+ await fs.ensureDir(path.join(cwd, "src"));
138
+ await fs.writeFile(path.join(cwd, "src/index.css"), cssConfig);
139
+ spinner.succeed("Tailwind v4 config created at src/index.css");
140
+ }
141
+ const postcssConfig = `export default {
142
+ plugins: {
143
+ '@tailwindcss/postcss': {},
144
+ },
145
+ }
146
+ `;
147
+ await fs.writeFile(path.join(cwd, "postcss.config.js"), postcssConfig);
148
+ console.log(kleur.dim(" Using Tailwind CSS v4 (CSS-first configuration)"));
149
+ } else {
150
+ const tailwindConfig = `/** @type {import('tailwindcss').Config} */
120
151
  module.exports = {
121
152
  darkMode: ["class"],
122
153
  content: [
@@ -125,24 +156,27 @@ module.exports = {
125
156
  "./app/**/*.{ts,tsx}",
126
157
  ],
127
158
  theme: {
128
- extend: require("./dist/tailwind/tokens.tailwind.js"),
159
+ extend: require("./dist/tailwind/tokens.tailwind.js").theme.extend,
129
160
  },
130
161
  plugins: [],
131
162
  }
132
163
  `;
133
- await fs.writeFile(path.join(cwd, "tailwind.config.js"), tailwindConfig);
134
- const postcssConfig = `module.exports = {
164
+ await fs.writeFile(path.join(cwd, "tailwind.config.js"), tailwindConfig);
165
+ const postcssConfig = `module.exports = {
135
166
  plugins: {
136
167
  tailwindcss: {},
137
168
  autoprefixer: {},
138
169
  },
139
170
  }
140
171
  `;
141
- await fs.writeFile(path.join(cwd, "postcss.config.js"), postcssConfig);
142
- spinner.succeed("Tailwind config created (v3 format)");
143
- console.log(kleur.dim(" Note: Tailwind v4 support coming soon"));
144
- } catch (error) {
145
- spinner.warn("Failed to create Tailwind config - please create manually");
172
+ await fs.writeFile(path.join(cwd, "postcss.config.js"), postcssConfig);
173
+ spinner.succeed("Tailwind v3 config created");
174
+ console.log(kleur.dim(" Note: Tailwind v4 also supported - upgrade anytime"));
175
+ }
176
+ } catch (error) {
177
+ spinner.warn("Failed to create Tailwind config - please create manually");
178
+ console.log(kleur.dim(" See docs for manual setup instructions"));
179
+ }
146
180
  }
147
181
  const lockFile = {
148
182
  version: "1",
@@ -192,6 +226,62 @@ async function loadSystems(systemsDir) {
192
226
  }
193
227
  return systems;
194
228
  }
229
+ async function detectTailwindVersion(cwd) {
230
+ try {
231
+ const packageJsonPath = path.join(cwd, "package.json");
232
+ if (await fs.pathExists(packageJsonPath)) {
233
+ const packageJson = await fs.readJSON(packageJsonPath);
234
+ const tailwindVersion = packageJson.dependencies?.tailwindcss || packageJson.devDependencies?.tailwindcss;
235
+ if (tailwindVersion) {
236
+ const match = tailwindVersion.match(/\d+/);
237
+ return match ? match[0] : null;
238
+ }
239
+ }
240
+ } catch (error) {
241
+ }
242
+ return null;
243
+ }
244
+ async function shouldProtectConfigs(cwd) {
245
+ const tailwindConfigPath = path.join(cwd, "tailwind.config.js");
246
+ const postcssConfigPath = path.join(cwd, "postcss.config.js");
247
+ const lockPath = path.join(cwd, "tokka.lock");
248
+ if (!await fs.pathExists(lockPath)) {
249
+ return false;
250
+ }
251
+ try {
252
+ const lockFile = await fs.readJSON(lockPath);
253
+ const lockDate = new Date(lockFile.generatedAt);
254
+ if (await fs.pathExists(tailwindConfigPath)) {
255
+ const tailwindStats = await fs.stat(tailwindConfigPath);
256
+ if (tailwindStats.mtime > lockDate) {
257
+ return true;
258
+ }
259
+ }
260
+ if (await fs.pathExists(postcssConfigPath)) {
261
+ const postcssStats = await fs.stat(postcssConfigPath);
262
+ if (postcssStats.mtime > lockDate) {
263
+ return true;
264
+ }
265
+ }
266
+ } catch (error) {
267
+ return true;
268
+ }
269
+ return false;
270
+ }
271
+ async function findMainCSSFile(cwd) {
272
+ const candidates = [
273
+ path.join(cwd, "src/index.css"),
274
+ path.join(cwd, "src/main.css"),
275
+ path.join(cwd, "app/globals.css"),
276
+ path.join(cwd, "src/styles/globals.css")
277
+ ];
278
+ for (const candidate of candidates) {
279
+ if (await fs.pathExists(candidate)) {
280
+ return candidate;
281
+ }
282
+ }
283
+ return null;
284
+ }
195
285
  async function createDemoApp(cwd, systemName) {
196
286
  const demoHtml = `<!DOCTYPE html>
197
287
  <html lang="en" class="light">
@@ -413,10 +503,10 @@ async function buildCommand(options) {
413
503
  try {
414
504
  const compilerPath = path3.join(__dirname3, "../compiler/index.js");
415
505
  const { compile, generateCSSOutput, generateTailwindOutput, generateTypeScriptOutput } = await import(compilerPath);
416
- const result = await compile({ cwd });
417
- if (result.errors.length > 0) {
506
+ const result2 = await compile({ cwd });
507
+ if (result2.errors.length > 0) {
418
508
  spinner.fail("Build failed");
419
- for (const error of result.errors) {
509
+ for (const error of result2.errors) {
420
510
  console.error(kleur3.red(` \u2716 ${error.message}`));
421
511
  }
422
512
  process.exit(1);
@@ -426,16 +516,16 @@ async function buildCommand(options) {
426
516
  await fs3.ensureDir(path3.join(cwd, "dist/ts"));
427
517
  const systemPath = path3.join(cwd, "system.json");
428
518
  const system = await fs3.readJSON(systemPath);
429
- const cssOutput = generateCSSOutput(result.tokens, system);
430
- const tailwindOutput = generateTailwindOutput(result.tokens, system);
431
- const tsOutput = generateTypeScriptOutput(result.tokens, system);
519
+ const cssOutput = generateCSSOutput(result2.tokens, system);
520
+ const tailwindOutput = generateTailwindOutput(result2.tokens, system);
521
+ const tsOutput = generateTypeScriptOutput(result2.tokens, system);
432
522
  await fs3.writeFile(path3.join(cwd, "dist/css/tokens.css"), cssOutput["tokens.css"]);
433
523
  await fs3.writeFile(path3.join(cwd, "dist/tailwind/tokens.tailwind.js"), tailwindOutput);
434
524
  await fs3.writeFile(path3.join(cwd, "dist/ts/tokens.ts"), tsOutput["tokens.ts"]);
435
- spinner.succeed(`Built ${result.tokens.length} tokens`);
436
- if (result.warnings.length > 0) {
525
+ spinner.succeed(`Built ${result2.tokens.length} tokens`);
526
+ if (result2.warnings.length > 0) {
437
527
  console.log(kleur3.yellow("\nWarnings:"));
438
- for (const warning of result.warnings) {
528
+ for (const warning of result2.warnings) {
439
529
  console.log(kleur3.yellow(` \u26A0 ${warning.message}`));
440
530
  }
441
531
  }
@@ -515,16 +605,16 @@ async function exportCommand(target, options) {
515
605
  try {
516
606
  const compilerPath = path5.join(__dirname5, "../compiler/index.js");
517
607
  const { compile, generateFigmaTokenOutput } = await import(compilerPath);
518
- const result = await compile({ cwd });
519
- if (result.errors.length > 0) {
608
+ const result2 = await compile({ cwd });
609
+ if (result2.errors.length > 0) {
520
610
  spinner.fail("Export failed");
521
- for (const error of result.errors) {
611
+ for (const error of result2.errors) {
522
612
  console.error(kleur5.red(` \u2716 ${error.message}`));
523
613
  }
524
614
  process.exit(1);
525
615
  }
526
616
  const system = await fs5.readJSON(path5.join(cwd, "system.json"));
527
- const figmaOutput = generateFigmaTokenOutput(result.tokens, system);
617
+ const figmaOutput = generateFigmaTokenOutput(result2.tokens, system);
528
618
  const outputDir = options.output || path5.join(cwd, "dist/figma");
529
619
  await fs5.ensureDir(outputDir);
530
620
  await fs5.writeFile(
@@ -629,14 +719,15 @@ async function enableCommand(feature) {
629
719
  console.log(kleur7.green("\u2713 Advanced mode enabled\n"));
630
720
  console.log("Advanced features now available:");
631
721
  console.log(kleur7.cyan(" \u2022 ") + "tokka validate - Strict validation");
632
- console.log(kleur7.cyan(" \u2022 ") + "tokka gen types - Generate TypeScript types");
633
722
  console.log(kleur7.cyan(" \u2022 ") + "Component token catalogs (tokens/components/)");
634
723
  console.log("");
635
724
  }
636
725
 
637
726
  // src/index.ts
727
+ var require2 = createRequire(import.meta.url);
728
+ var pkg = require2("../package.json");
638
729
  var program = new Command();
639
- program.name("tokka").description("A shadcn-compatible UI foundation with a real token system").version("0.0.1");
730
+ program.name("tokka").description("A shadcn-compatible UI foundation with a real token system").version(pkg.version);
640
731
  program.command("init").description("Initialize a new tokka project").option("-s, --system <id>", "System to use (soft-saas, corporate, dark-mode, accessible, brutalist)").option("-p, --package-manager <pm>", "Package manager to use", "pnpm").option("--no-demo", "Skip demo app creation").action(initCommand);
641
732
  program.command("add").description("Add components to your project").argument("<components...>", "Component names (button, input, card, etc.)").action(addCommand);
642
733
  program.command("build").description("Build design tokens and outputs").option("-f, --force", "Force rebuild").action(buildCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokka",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
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",