tokka 0.4.0 → 0.4.1

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.
@@ -439,6 +439,9 @@ declare function generateCSSOutput(tokens: ResolvedToken[], system: System, opti
439
439
  declare function generateTailwindConfig(tokens: ResolvedToken[], _system: System): Record<string, any>;
440
440
  /**
441
441
  * Generate Tailwind config file content
442
+ *
443
+ * Note: This generates a Tailwind CSS v3-compatible config.
444
+ * Tailwind v4 support (CSS-first configuration) is planned for a future update.
442
445
  */
443
446
  declare function generateTailwindOutput(tokens: ResolvedToken[], system: System): string;
444
447
 
package/compiler/index.js CHANGED
@@ -515,6 +515,8 @@ function generateTailwindOutput(tokens, system) {
515
515
  return `/**
516
516
  * Tailwind config for ${system.name}
517
517
  * Generated by figma-base - do not edit directly
518
+ *
519
+ * Compatible with Tailwind CSS v3.x
518
520
  */
519
521
  export default ${JSON.stringify(config, null, 2)}
520
522
  `;
@@ -604,6 +606,9 @@ function formatColorValue(value) {
604
606
  return value;
605
607
  }
606
608
  function formatTokenValue(token, value) {
609
+ if (!value) {
610
+ return "";
611
+ }
607
612
  if (value.startsWith("{") && value.endsWith("}")) {
608
613
  return value;
609
614
  }
@@ -628,7 +633,7 @@ function generateFigmaTokens(tokens, _system) {
628
633
  current = current[parts[i]];
629
634
  }
630
635
  const lastPart = parts[parts.length - 1];
631
- const rawValue = token.resolvedValue || token.value;
636
+ const rawValue = token.resolvedValue || token.value || "";
632
637
  const formattedValue = formatTokenValue(token, rawValue);
633
638
  current[lastPart] = {
634
639
  value: formattedValue,
@@ -648,7 +653,7 @@ function generateFigmaTokens(tokens, _system) {
648
653
  current = current[parts[i]];
649
654
  }
650
655
  const lastPart = parts[parts.length - 1];
651
- const rawValue = token.references && token.references.length > 0 ? `{${token.references[0]}}` : token.resolvedValue || token.value;
656
+ const rawValue = token.references && token.references.length > 0 ? `{${token.references[0]}}` : token.resolvedValue || token.value || "";
652
657
  const formattedValue = formatTokenValue(token, rawValue);
653
658
  current[lastPart] = {
654
659
  value: formattedValue,
@@ -669,7 +674,7 @@ function generateFigmaTokens(tokens, _system) {
669
674
  current = current[parts[i]];
670
675
  }
671
676
  const lastPart = parts[parts.length - 1];
672
- const rawValue = token.references && token.references.length > 0 ? `{${token.references[0]}}` : token.resolvedValue || token.value;
677
+ const rawValue = token.references && token.references.length > 0 ? `{${token.references[0]}}` : token.resolvedValue || token.value || "";
673
678
  const formattedValue = formatTokenValue(token, rawValue);
674
679
  current[lastPart] = {
675
680
  value: formattedValue,
@@ -22,37 +22,43 @@ function Calendar({
22
22
  caption: "flex justify-center pt-1 relative items-center",
23
23
  caption_label: "text-sm font-medium",
24
24
  nav: "space-x-1 flex items-center",
25
- nav_button: cn(
25
+ button_previous: cn(
26
26
  buttonVariants({ variant: "outline" }),
27
- "size-7 bg-transparent p-0 opacity-50 hover:opacity-100"
27
+ "size-7 bg-transparent p-0 opacity-50 hover:opacity-100 absolute left-1"
28
28
  ),
29
- nav_button_previous: "absolute left-1",
30
- nav_button_next: "absolute right-1",
31
- table: "w-full border-collapse space-y-1",
32
- head_row: "flex",
33
- head_cell:
29
+ button_next: cn(
30
+ buttonVariants({ variant: "outline" }),
31
+ "size-7 bg-transparent p-0 opacity-50 hover:opacity-100 absolute right-1"
32
+ ),
33
+ month_grid: "w-full border-collapse space-y-1",
34
+ weekdays: "flex",
35
+ weekday:
34
36
  "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
35
- row: "flex w-full mt-2",
36
- cell: "size-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
37
- day: cn(
37
+ week: "flex w-full mt-2",
38
+ day: "size-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
39
+ day_button: cn(
38
40
  buttonVariants({ variant: "ghost" }),
39
41
  "size-9 p-0 font-normal aria-selected:opacity-100"
40
42
  ),
41
- day_range_end: "day-range-end",
42
- day_selected:
43
+ range_end: "day-range-end",
44
+ selected:
43
45
  "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
44
- day_today: "bg-accent text-accent-foreground",
45
- day_outside:
46
+ today: "bg-accent text-accent-foreground",
47
+ outside:
46
48
  "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
47
- day_disabled: "text-muted-foreground opacity-50",
48
- day_range_middle:
49
+ disabled: "text-muted-foreground opacity-50",
50
+ range_middle:
49
51
  "aria-selected:bg-accent aria-selected:text-accent-foreground",
50
- day_hidden: "invisible",
52
+ hidden: "invisible",
51
53
  ...classNames,
52
54
  }}
53
55
  components={{
54
- IconLeft: () => <ChevronLeft className="size-4" />,
55
- IconRight: () => <ChevronRight className="size-4" />,
56
+ Chevron: (props) => {
57
+ if (props.orientation === "left") {
58
+ return <ChevronLeft className="size-4" />
59
+ }
60
+ return <ChevronRight className="size-4" />
61
+ },
56
62
  }}
57
63
  {...props}
58
64
  />
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import { Command } from "commander";
7
7
  import fs from "fs-extra";
8
8
  import path from "path";
9
9
  import { fileURLToPath } from "url";
10
+ import { execSync } from "child_process";
10
11
  import prompts from "prompts";
11
12
  import ora from "ora";
12
13
  import kleur from "kleur";
@@ -53,7 +54,34 @@ Setting up ${selectedSystem.name}...
53
54
  await fs.ensureDir(path.join(cwd, "dist"));
54
55
  if (options.packageManager !== "skip") {
55
56
  spinner.start("Installing dependencies...");
56
- spinner.succeed("Dependencies installed");
57
+ try {
58
+ const REQUIRED_DEPS = [
59
+ "@radix-ui/react-slot",
60
+ "class-variance-authority",
61
+ "clsx",
62
+ "tailwind-merge"
63
+ ];
64
+ const DEV_DEPS = ["tailwindcss", "postcss", "autoprefixer"];
65
+ let installCmd;
66
+ let devInstallCmd;
67
+ if (options.packageManager === "npm") {
68
+ installCmd = `npm install ${REQUIRED_DEPS.join(" ")}`;
69
+ devInstallCmd = `npm install -D ${DEV_DEPS.join(" ")}`;
70
+ } else if (options.packageManager === "yarn") {
71
+ installCmd = `yarn add ${REQUIRED_DEPS.join(" ")}`;
72
+ devInstallCmd = `yarn add -D ${DEV_DEPS.join(" ")}`;
73
+ } else {
74
+ installCmd = `pnpm add ${REQUIRED_DEPS.join(" ")}`;
75
+ devInstallCmd = `pnpm add -D ${DEV_DEPS.join(" ")}`;
76
+ }
77
+ execSync(installCmd, { cwd, stdio: "pipe" });
78
+ execSync(devInstallCmd, { cwd, stdio: "pipe" });
79
+ spinner.succeed("Dependencies installed");
80
+ } catch (error) {
81
+ spinner.warn("Failed to install dependencies - please install manually");
82
+ console.log(kleur.dim(" npm install @radix-ui/react-slot class-variance-authority clsx tailwind-merge"));
83
+ console.log(kleur.dim(" npm install -D tailwindcss postcss autoprefixer"));
84
+ }
57
85
  }
58
86
  spinner.start("Building tokens...");
59
87
  try {
@@ -86,6 +114,36 @@ Setting up ${selectedSystem.name}...
86
114
  console.error(kleur.red(error.message));
87
115
  process.exit(1);
88
116
  }
117
+ spinner.start("Creating Tailwind config...");
118
+ try {
119
+ const tailwindConfig = `/** @type {import('tailwindcss').Config} */
120
+ module.exports = {
121
+ darkMode: ["class"],
122
+ content: [
123
+ "./src/**/*.{ts,tsx}",
124
+ "./components/**/*.{ts,tsx}",
125
+ "./app/**/*.{ts,tsx}",
126
+ ],
127
+ theme: {
128
+ extend: require("./dist/tailwind/tokens.tailwind.js"),
129
+ },
130
+ plugins: [],
131
+ }
132
+ `;
133
+ await fs.writeFile(path.join(cwd, "tailwind.config.js"), tailwindConfig);
134
+ const postcssConfig = `module.exports = {
135
+ plugins: {
136
+ tailwindcss: {},
137
+ autoprefixer: {},
138
+ },
139
+ }
140
+ `;
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");
146
+ }
89
147
  const lockFile = {
90
148
  version: "1",
91
149
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -98,6 +156,15 @@ Setting up ${selectedSystem.name}...
98
156
  }
99
157
  };
100
158
  await fs.writeJSON(path.join(cwd, "tokka.lock"), lockFile, { spaces: 2 });
159
+ if (options.demo) {
160
+ spinner.start("Creating demo page...");
161
+ try {
162
+ await createDemoApp(cwd, selectedSystem.name);
163
+ spinner.succeed("Demo page created");
164
+ } catch (error) {
165
+ spinner.warn("Failed to create demo page - you can create it manually");
166
+ }
167
+ }
101
168
  console.log(kleur.green().bold("\n\u2713 All done!\n"));
102
169
  console.log("Next steps:");
103
170
  console.log(kleur.cyan(" 1. ") + "Import tokens in your CSS:");
@@ -105,6 +172,10 @@ Setting up ${selectedSystem.name}...
105
172
  console.log(kleur.cyan(" 2. ") + "Add components:");
106
173
  console.log(kleur.dim(" tokka add button input card"));
107
174
  console.log(kleur.cyan(" 3. ") + "Start building!");
175
+ if (options.demo) {
176
+ console.log(kleur.cyan(" 4. ") + "Check out the demo page:");
177
+ console.log(kleur.dim(" Open demo.html in your browser"));
178
+ }
108
179
  console.log("");
109
180
  }
110
181
  async function loadSystems(systemsDir) {
@@ -121,6 +192,111 @@ async function loadSystems(systemsDir) {
121
192
  }
122
193
  return systems;
123
194
  }
195
+ async function createDemoApp(cwd, systemName) {
196
+ const demoHtml = `<!DOCTYPE html>
197
+ <html lang="en" class="light">
198
+ <head>
199
+ <meta charset="UTF-8">
200
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
201
+ <title>tokka Demo - ${systemName}</title>
202
+ <link rel="stylesheet" href="./dist/css/tokens.css">
203
+ <script src="https://cdn.tailwindcss.com"></script>
204
+ <script>
205
+ tailwind.config = ${JSON.stringify(
206
+ {
207
+ darkMode: ["class"],
208
+ theme: {
209
+ extend: {}
210
+ // Token classes will work via CSS custom properties
211
+ }
212
+ },
213
+ null,
214
+ 2
215
+ )}
216
+ </script>
217
+ <style>
218
+ body {
219
+ font-family: system-ui, -apple-system, sans-serif;
220
+ }
221
+ </style>
222
+ </head>
223
+ <body class="bg-background text-foreground">
224
+ <main class="min-h-screen p-8">
225
+ <div class="max-w-4xl mx-auto space-y-8">
226
+ <div class="space-y-2">
227
+ <h1 class="text-4xl font-bold">tokka Demo</h1>
228
+ <p class="text-lg text-muted-foreground">
229
+ ${systemName} design system
230
+ </p>
231
+ </div>
232
+
233
+ <div class="bg-card text-card-foreground border border-border rounded-lg p-6 space-y-4">
234
+ <div>
235
+ <h2 class="text-xl font-semibold">Buttons</h2>
236
+ <p class="text-sm text-muted-foreground">Different button variants using semantic tokens</p>
237
+ </div>
238
+ <div class="flex flex-wrap gap-4">
239
+ <button class="bg-primary text-primary-foreground px-4 py-2 rounded-md hover:bg-primary/90">
240
+ Default
241
+ </button>
242
+ <button class="bg-secondary text-secondary-foreground px-4 py-2 rounded-md hover:bg-secondary/80">
243
+ Secondary
244
+ </button>
245
+ <button class="bg-destructive text-destructive-foreground px-4 py-2 rounded-md hover:bg-destructive/90">
246
+ Destructive
247
+ </button>
248
+ <button class="border border-input bg-background px-4 py-2 rounded-md hover:bg-accent hover:text-accent-foreground">
249
+ Outline
250
+ </button>
251
+ </div>
252
+ </div>
253
+
254
+ <div class="bg-card text-card-foreground border border-border rounded-lg p-6 space-y-4">
255
+ <div>
256
+ <h2 class="text-xl font-semibold">Inputs</h2>
257
+ <p class="text-sm text-muted-foreground">Input fields using semantic tokens</p>
258
+ </div>
259
+ <div class="space-y-4">
260
+ <input
261
+ type="text"
262
+ placeholder="Default input"
263
+ class="w-full px-3 py-2 border border-input rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-ring"
264
+ />
265
+ <input
266
+ type="email"
267
+ placeholder="Email input"
268
+ class="w-full px-3 py-2 border border-input rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-ring"
269
+ />
270
+ </div>
271
+ </div>
272
+
273
+ <div class="bg-card text-card-foreground border border-border rounded-lg p-6 space-y-4">
274
+ <div>
275
+ <h2 class="text-xl font-semibold">Theme Toggle</h2>
276
+ <p class="text-sm text-muted-foreground">Switch between light and dark mode</p>
277
+ </div>
278
+ <button
279
+ onclick="document.documentElement.classList.toggle('dark'); document.documentElement.classList.toggle('light');"
280
+ class="bg-secondary text-secondary-foreground px-4 py-2 rounded-md hover:bg-secondary/80"
281
+ >
282
+ Toggle Dark Mode
283
+ </button>
284
+ </div>
285
+
286
+ <div class="bg-muted text-muted-foreground border border-border rounded-lg p-6">
287
+ <div class="space-y-2">
288
+ <h3 class="font-semibold text-foreground">Next Steps</h3>
289
+ <p class="text-sm">Add React components with: <code class="bg-background px-2 py-1 rounded">tokka add button input card</code></p>
290
+ <p class="text-sm">View all components: <code class="bg-background px-2 py-1 rounded">tokka list components</code></p>
291
+ </div>
292
+ </div>
293
+ </div>
294
+ </main>
295
+ </body>
296
+ </html>
297
+ `;
298
+ await fs.writeFile(path.join(cwd, "demo.html"), demoHtml);
299
+ }
124
300
 
125
301
  // src/commands/add.ts
126
302
  import fs2 from "fs-extra";
@@ -188,6 +364,13 @@ async function addCommand(components) {
188
364
  const spinner = ora2();
189
365
  const componentsDir = path2.join(cwd, "components");
190
366
  await fs2.ensureDir(componentsDir);
367
+ const libDir = path2.join(componentsDir, "lib");
368
+ await fs2.ensureDir(libDir);
369
+ const utilsSource = path2.join(__dirname2, "../components/lib/utils.ts");
370
+ const utilsTarget = path2.join(libDir, "utils.ts");
371
+ if (!await fs2.pathExists(utilsTarget)) {
372
+ await fs2.copy(utilsSource, utilsTarget);
373
+ }
191
374
  for (const component of components) {
192
375
  if (!COMPONENT_REGISTRY[component]) {
193
376
  console.error(kleur2.red(`\u2716 Component "${component}" not found`));
@@ -393,13 +576,10 @@ async function listSystems(json) {
393
576
  }
394
577
  }
395
578
  async function listComponents(json) {
396
- const components = [
397
- { name: "button", status: "available" },
398
- { name: "input", status: "available" },
399
- { name: "card", status: "available" },
400
- { name: "dialog", status: "coming-soon" },
401
- { name: "select", status: "coming-soon" }
402
- ];
579
+ const components = Object.keys(COMPONENT_REGISTRY).map((name) => ({
580
+ name,
581
+ status: "available"
582
+ }));
403
583
  if (json) {
404
584
  console.log(JSON.stringify(components, null, 2));
405
585
  return;
@@ -426,56 +606,31 @@ async function loadSystemsMetadata(systemsDir) {
426
606
  return systems;
427
607
  }
428
608
 
429
- // src/commands/gen.ts
609
+ // src/commands/enable.ts
430
610
  import fs7 from "fs-extra";
431
611
  import path7 from "path";
432
612
  import kleur7 from "kleur";
433
- async function genCommand(target) {
434
- if (target !== "types") {
435
- console.error(kleur7.red(`\u2716 Unknown generation target: ${target}`));
613
+ async function enableCommand(feature) {
614
+ if (feature !== "advanced") {
615
+ console.error(kleur7.red(`\u2716 Unknown feature: ${feature}`));
436
616
  process.exit(1);
437
617
  }
438
618
  const cwd = process.cwd();
439
619
  const configPath = path7.join(cwd, ".figmabase");
620
+ console.log(kleur7.cyan("\n\u{1F680} Enabling advanced mode...\n"));
440
621
  let config = {};
441
622
  if (await fs7.pathExists(configPath)) {
442
623
  config = await fs7.readJSON(configPath);
443
624
  }
444
- if (!config.advanced) {
445
- console.error(kleur7.red("\n\u2716 Type generation requires advanced mode"));
446
- console.error(kleur7.dim(" Run: tokka enable advanced"));
447
- process.exit(1);
448
- }
449
- console.log(kleur7.cyan("\n\u{1F527} Generating TypeScript types from manifests...\n"));
450
- console.log(kleur7.yellow("\u26A0 Type generation not yet implemented"));
451
- console.log(kleur7.dim(" Coming in a future update"));
452
- }
453
-
454
- // src/commands/enable.ts
455
- import fs8 from "fs-extra";
456
- import path8 from "path";
457
- import kleur8 from "kleur";
458
- async function enableCommand(feature) {
459
- if (feature !== "advanced") {
460
- console.error(kleur8.red(`\u2716 Unknown feature: ${feature}`));
461
- process.exit(1);
462
- }
463
- const cwd = process.cwd();
464
- const configPath = path8.join(cwd, ".figmabase");
465
- console.log(kleur8.cyan("\n\u{1F680} Enabling advanced mode...\n"));
466
- let config = {};
467
- if (await fs8.pathExists(configPath)) {
468
- config = await fs8.readJSON(configPath);
469
- }
470
625
  config.advanced = true;
471
- await fs8.writeJSON(configPath, config, { spaces: 2 });
472
- const componentsDir = path8.join(cwd, "tokens/components");
473
- await fs8.ensureDir(componentsDir);
474
- console.log(kleur8.green("\u2713 Advanced mode enabled\n"));
626
+ await fs7.writeJSON(configPath, config, { spaces: 2 });
627
+ const componentsDir = path7.join(cwd, "tokens/components");
628
+ await fs7.ensureDir(componentsDir);
629
+ console.log(kleur7.green("\u2713 Advanced mode enabled\n"));
475
630
  console.log("Advanced features now available:");
476
- console.log(kleur8.cyan(" \u2022 ") + "tokka validate - Strict validation");
477
- console.log(kleur8.cyan(" \u2022 ") + "tokka gen types - Generate TypeScript types");
478
- console.log(kleur8.cyan(" \u2022 ") + "Component token catalogs (tokens/components/)");
631
+ console.log(kleur7.cyan(" \u2022 ") + "tokka validate - Strict validation");
632
+ console.log(kleur7.cyan(" \u2022 ") + "tokka gen types - Generate TypeScript types");
633
+ console.log(kleur7.cyan(" \u2022 ") + "Component token catalogs (tokens/components/)");
479
634
  console.log("");
480
635
  }
481
636
 
@@ -489,5 +644,4 @@ program.command("list").description("List available systems or components").argu
489
644
  program.command("export").description("Export Figma artifacts").argument("<target>", "Export target (figma)").option("-o, --output <dir>", "Output directory").action(exportCommand);
490
645
  program.command("enable").description("Enable advanced features").argument("<feature>", "Feature to enable (advanced)").action(enableCommand);
491
646
  program.command("validate").description("Validate tokens and manifests (requires advanced mode)").action(validateCommand);
492
- program.command("gen").description("Generate code from manifests").argument("<target>", "What to generate (types)").action(genCommand);
493
647
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokka",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
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",
@@ -28,6 +28,20 @@
28
28
  "source": "primitive",
29
29
  "value": "0 0% 100%"
30
30
  },
31
+ {
32
+ "id": "color.blue.600",
33
+ "type": "color",
34
+ "description": "Dark brand blue",
35
+ "source": "primitive",
36
+ "value": "220 90% 48%"
37
+ },
38
+ {
39
+ "id": "color.red.600",
40
+ "type": "color",
41
+ "description": "Dark error red",
42
+ "source": "primitive",
43
+ "value": "0 72% 45%"
44
+ },
31
45
  {
32
46
  "id": "space.1",
33
47
  "type": "dimension",
@@ -42,12 +56,19 @@
42
56
  "source": "primitive",
43
57
  "value": "0.5rem"
44
58
  },
59
+ {
60
+ "id": "space.3",
61
+ "type": "dimension",
62
+ "description": "Medium spacing",
63
+ "source": "primitive",
64
+ "value": "0.75rem"
65
+ },
45
66
  {
46
67
  "id": "space.4",
47
68
  "type": "dimension",
48
69
  "description": "Base spacing",
49
70
  "source": "primitive",
50
- "value": "0.75rem"
71
+ "value": "1rem"
51
72
  },
52
73
  {
53
74
  "id": "radius.none",
@@ -55,6 +76,13 @@
55
76
  "description": "No radius",
56
77
  "source": "primitive",
57
78
  "value": "0"
79
+ },
80
+ {
81
+ "id": "radius.sm",
82
+ "type": "radius",
83
+ "description": "Small radius",
84
+ "source": "primitive",
85
+ "value": "0.125rem"
58
86
  }
59
87
  ]
60
88
  }