swatchkit 0.0.2 → 0.0.4

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/README.md CHANGED
@@ -9,14 +9,14 @@ It follows the "Magic Folder" principle: drop files in, and a library comes out.
9
9
  Try it instantly in any project:
10
10
 
11
11
  ```bash
12
- # 1. Initialize the layout
12
+ # 1. Initialize the layout and design tokens
13
13
  npx swatchkit init
14
14
 
15
15
  # 2. Build the library
16
16
  npx swatchkit
17
17
  ```
18
18
 
19
- This will create a `swatches/` folder and build your site to `public/swatchkit/`.
19
+ This will create a `swatches/` folder, generate a `src/tokens/colors.json` file, and build your site to `public/swatchkit/`.
20
20
 
21
21
  ---
22
22
 
@@ -36,23 +36,31 @@ Then add it to your `package.json` scripts:
36
36
  }
37
37
  ```
38
38
 
39
- ## How It Works
39
+ ## Features
40
40
 
41
41
  ### 1. The Magic Folder
42
42
  By default, SwatchKit looks for a `swatches/` folder in your project root.
43
43
  * **Single File:** Drop `card.html` into `swatches/`. It appears in the library.
44
44
  * **Component Folder:** Drop a folder like `swatches/carousel/` containing `index.html`. It works the same way.
45
45
 
46
- ### 2. Custom Layouts
46
+ ### 2. Design Tokens (Colors)
47
+ SwatchKit includes a basic **Token Engine**.
48
+ * **Source:** Edit `src/tokens/colors.json`.
49
+ * **Output:** SwatchKit auto-generates `src/css/tokens.css` with CSS variables.
50
+ * **Docs:** It creates `swatches/colors.html` to visualize your palette.
51
+
52
+ *(More token types like Typography and Spacing coming soon!)*
53
+
54
+ ### 3. Custom Layouts
47
55
  When you run `swatchkit init`, we create `swatches/_layout.html`.
48
56
  **You own this file.**
49
57
  * Add your own `<link rel="stylesheet" href="/css/app.css">`.
50
58
  * Add custom fonts, scripts, or meta tags.
51
59
  * Change the HTML structure, logo, or classes.
52
60
 
53
- SwatchKit simply injects the content into the `<!-- PATTERNS -->` and `<!-- SIDEBAR_LINKS -->` placeholders.
61
+ SwatchKit simply injects the content into the `<!-- PATTERNS -->`, `<!-- SIDEBAR_LINKS -->`, and `<!-- HEAD_EXTRAS -->` placeholders.
54
62
 
55
- ### 3. JavaScript Bundling
63
+ ### 4. JavaScript Bundling
56
64
  If your component needs client-side JS:
57
65
  1. Create a folder: `swatches/carousel/`.
58
66
  2. Add `index.html` (Markup).
@@ -68,7 +76,7 @@ swatchkit [command] [options]
68
76
 
69
77
  ### Commands
70
78
  * `swatchkit` (Default): Builds the library.
71
- * `swatchkit init`: Scaffolds the `_layout.html` file.
79
+ * `swatchkit init`: Scaffolds the layout and token files.
72
80
 
73
81
  ### Flags
74
82
  | Flag | Short | Description |
@@ -90,4 +98,4 @@ module.exports = {
90
98
  // Override default output directory
91
99
  outDir: './dist/docs'
92
100
  };
93
- ```
101
+ ```
package/build.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
- const fs = require('fs');
3
- const path = require('path');
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+ const { processTokens } = require("./src/tokens");
4
5
 
5
6
  /**
6
7
  * SwatchKit Build Script
@@ -15,27 +16,27 @@ function parseArgs(args) {
15
16
  config: null,
16
17
  input: null,
17
18
  outDir: null,
18
- force: false
19
+ force: false,
19
20
  };
20
21
 
21
22
  for (let i = 2; i < args.length; i++) {
22
23
  const arg = args[i];
23
- if (arg === 'init') {
24
- options.command = 'init';
25
- } else if (arg === '-w' || arg === '--watch') {
24
+ if (arg === "init") {
25
+ options.command = "init";
26
+ } else if (arg === "-w" || arg === "--watch") {
26
27
  options.watch = true;
27
- } else if (arg === '-f' || arg === '--force') {
28
+ } else if (arg === "-f" || arg === "--force") {
28
29
  options.force = true;
29
- } else if (arg === '-c' || arg === '--config') {
30
+ } else if (arg === "-c" || arg === "--config") {
30
31
  // Handle case where flag is last arg
31
32
  if (i + 1 < args.length) {
32
33
  options.config = args[++i];
33
34
  }
34
- } else if (arg === '-i' || arg === '--input') {
35
+ } else if (arg === "-i" || arg === "--input") {
35
36
  if (i + 1 < args.length) {
36
37
  options.input = args[++i];
37
38
  }
38
- } else if (arg === '-o' || arg === '--outDir') {
39
+ } else if (arg === "-o" || arg === "--outDir") {
39
40
  if (i + 1 < args.length) {
40
41
  options.outDir = args[++i];
41
42
  }
@@ -50,7 +51,7 @@ function loadConfig(configPath) {
50
51
  if (configPath) {
51
52
  finalPath = path.resolve(process.cwd(), configPath);
52
53
  } else {
53
- finalPath = path.join(process.cwd(), 'swatchkit.config.js');
54
+ finalPath = path.join(process.cwd(), "swatchkit.config.js");
54
55
  }
55
56
 
56
57
  if (fs.existsSync(finalPath)) {
@@ -58,7 +59,7 @@ function loadConfig(configPath) {
58
59
  console.log(`[SwatchKit] Loading config from ${finalPath}`);
59
60
  return require(finalPath);
60
61
  } catch (e) {
61
- console.error('[SwatchKit] Error loading config file:', e.message);
62
+ console.error("[SwatchKit] Error loading config file:", e.message);
62
63
  return {};
63
64
  }
64
65
  }
@@ -76,47 +77,51 @@ function resolveSettings(cliOptions, fileConfig) {
76
77
  if (fileConfig.input) return path.resolve(cwd, fileConfig.input);
77
78
 
78
79
  // 2. Search candidates
79
- const candidates = ['swatches', 'src/swatches', 'src/patterns'];
80
+ const candidates = ["swatches", "src/swatches", "src/patterns"];
80
81
  for (const cand of candidates) {
81
82
  const absPath = path.join(cwd, cand);
82
83
  if (fs.existsSync(absPath)) return absPath;
83
84
  }
84
85
 
85
86
  // 3. Fallback default (swatches)
86
- return path.join(cwd, 'swatches');
87
+ return path.join(cwd, "swatches");
87
88
  }
88
89
 
89
90
  const patternsDir = findPatternsDir();
90
91
 
91
92
  // Output Dir
92
93
  // Default: public/swatchkit
93
- const outDir = cliOptions.outDir
94
+ const outDir = cliOptions.outDir
94
95
  ? path.resolve(cwd, cliOptions.outDir)
95
- : (fileConfig.outDir ? path.resolve(cwd, fileConfig.outDir) : path.join(cwd, 'public/swatchkit'));
96
+ : fileConfig.outDir
97
+ ? path.resolve(cwd, fileConfig.outDir)
98
+ : path.join(cwd, "public/swatchkit");
96
99
 
97
100
  // CSS Dir (Legacy support: src/css)
98
- const cssDir = path.join(cwd, 'src/css');
101
+ const cssDir = path.join(cwd, "src/css");
99
102
 
100
103
  return {
101
104
  patternsDir,
102
105
  outDir,
103
106
  cssDir,
104
107
  // Internal layout template (relative to this script)
105
- internalLayout: path.join(__dirname, 'src/layout.html'),
108
+ internalLayout: path.join(__dirname, "src/layout.html"),
106
109
  // Project specific layout override
107
- projectLayout: path.join(patternsDir, '_layout.html'),
108
-
110
+ projectLayout: path.join(patternsDir, "_layout.html"),
111
+
109
112
  // Derived paths
110
- distCssDir: path.join(outDir, 'css'),
111
- distJsDir: path.join(outDir, 'js'),
112
- outputFile: path.join(outDir, 'index.html'),
113
- outputJsFile: path.join(outDir, 'js/patterns.js')
113
+ distCssDir: path.join(outDir, "css"),
114
+ distTokensCssFile: path.join(outDir, "css", "tokens.css"),
115
+ distJsDir: path.join(outDir, "js"),
116
+ outputFile: path.join(outDir, "index.html"),
117
+ outputJsFile: path.join(outDir, "js/patterns.js"),
118
+ tokensCssFile: path.join(cssDir, "tokens.css"),
114
119
  };
115
120
  }
116
121
 
117
122
  // --- 4. Init Command Logic ---
118
123
  function runInit(settings, options) {
119
- console.log('[SwatchKit] Initializing...');
124
+ console.log("[SwatchKit] Initializing...");
120
125
 
121
126
  // Ensure patterns directory exists
122
127
  if (!fs.existsSync(settings.patternsDir)) {
@@ -124,20 +129,120 @@ function runInit(settings, options) {
124
129
  fs.mkdirSync(settings.patternsDir, { recursive: true });
125
130
  }
126
131
 
132
+ // Create src/tokens directory and sample colors.json
133
+ const tokensDir = path.join(process.cwd(), "src/tokens");
134
+ if (!fs.existsSync(tokensDir)) {
135
+ console.log(`Creating tokens directory: ${tokensDir}`);
136
+ fs.mkdirSync(tokensDir, { recursive: true });
137
+ }
138
+
139
+ const colorsFile = path.join(tokensDir, "colors.json");
140
+ if (!fs.existsSync(colorsFile)) {
141
+ const sampleColors = {
142
+ title: "Colors",
143
+ description:
144
+ "Hex color codes that can be shared, cross-platform. They can be converted at point of usage, such as HSL for web or CMYK for print.",
145
+ items: [
146
+ { name: "Dark", value: "#171406" },
147
+ { name: "Dark Glare", value: "#2d2816" },
148
+ { name: "Mid", value: "#ABA9A2" },
149
+ { name: "Light", value: "#ffffff" },
150
+ { name: "Primary", value: "#fcad26" },
151
+ ],
152
+ };
153
+ fs.writeFileSync(colorsFile, JSON.stringify(sampleColors, null, 2));
154
+ console.log(`Created sample tokens file at ${colorsFile}`);
155
+ }
156
+
157
+ // Create sample colors.html pattern
158
+ const colorsPatternFile = path.join(settings.patternsDir, "colors.html");
159
+ if (!fs.existsSync(colorsPatternFile)) {
160
+ const colorsHtml = `
161
+ <style>
162
+ .swatch-grid {
163
+ display: grid;
164
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
165
+ gap: 1rem;
166
+ padding: 1rem;
167
+ }
168
+ .swatch {
169
+ border: 1px solid #ddd;
170
+ border-radius: 8px;
171
+ overflow: hidden;
172
+ }
173
+ .swatch-color {
174
+ height: 100px;
175
+ width: 100%;
176
+ }
177
+ .swatch-info {
178
+ padding: 0.5rem;
179
+ font-family: monospace;
180
+ font-size: 0.9rem;
181
+ background: #f9f9f9;
182
+ }
183
+ </style>
184
+
185
+ <div class="swatch-grid">
186
+ <div class="swatch">
187
+ <div class="swatch-color" style="background-color: var(--color-dark);"></div>
188
+ <div class="swatch-info">
189
+ <strong>Dark</strong><br>
190
+ var(--color-dark)
191
+ </div>
192
+ </div>
193
+ <div class="swatch">
194
+ <div class="swatch-color" style="background-color: var(--color-dark-glare);"></div>
195
+ <div class="swatch-info">
196
+ <strong>Dark Glare</strong><br>
197
+ var(--color-dark-glare)
198
+ </div>
199
+ </div>
200
+ <div class="swatch">
201
+ <div class="swatch-color" style="background-color: var(--color-mid);"></div>
202
+ <div class="swatch-info">
203
+ <strong>Mid</strong><br>
204
+ var(--color-mid)
205
+ </div>
206
+ </div>
207
+ <div class="swatch">
208
+ <div class="swatch-color" style="background-color: var(--color-light);"></div>
209
+ <div class="swatch-info">
210
+ <strong>Light</strong><br>
211
+ var(--color-light)
212
+ </div>
213
+ </div>
214
+ <div class="swatch">
215
+ <div class="swatch-color" style="background-color: var(--color-primary);"></div>
216
+ <div class="swatch-info">
217
+ <strong>Primary</strong><br>
218
+ var(--color-primary)
219
+ </div>
220
+ </div>
221
+ </div>
222
+ `;
223
+ fs.writeFileSync(colorsPatternFile, colorsHtml.trim());
224
+ console.log(`Created sample pattern at ${colorsPatternFile}`);
225
+ }
226
+
227
+ // Generate initial CSS
228
+ processTokens(process.cwd(), settings.cssDir);
229
+
127
230
  const targetLayout = settings.projectLayout;
128
-
231
+
129
232
  if (fs.existsSync(targetLayout) && !options.force) {
130
233
  console.warn(`Warning: Layout file already exists at ${targetLayout}`);
131
- console.warn('Use --force to overwrite.');
234
+ console.warn("Use --force to overwrite.");
132
235
  return;
133
236
  }
134
237
 
135
238
  if (fs.existsSync(settings.internalLayout)) {
136
- const layoutContent = fs.readFileSync(settings.internalLayout, 'utf-8');
239
+ const layoutContent = fs.readFileSync(settings.internalLayout, "utf-8");
137
240
  fs.writeFileSync(targetLayout, layoutContent);
138
241
  console.log(`Created layout file at ${targetLayout}`);
139
242
  } else {
140
- console.error(`Error: Internal layout file not found at ${settings.internalLayout}`);
243
+ console.error(
244
+ `Error: Internal layout file not found at ${settings.internalLayout}`,
245
+ );
141
246
  process.exit(1);
142
247
  }
143
248
  }
@@ -150,123 +255,149 @@ function build(settings) {
150
255
 
151
256
  // 1. Check if patterns directory exists
152
257
  if (!fs.existsSync(settings.patternsDir)) {
153
- console.error(`Error: Patterns directory not found at ${settings.patternsDir}`);
258
+ console.error(
259
+ `Error: Patterns directory not found at ${settings.patternsDir}`,
260
+ );
154
261
  console.error('Run "swatchkit init" to get started.');
155
262
  process.exit(1);
156
263
  }
157
264
 
158
265
  // 2. Ensure dist directories exist
159
- [settings.outDir, settings.distCssDir, settings.distJsDir].forEach(dir => {
266
+ [settings.outDir, settings.distCssDir, settings.distJsDir].forEach((dir) => {
160
267
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
161
268
  });
162
269
 
270
+ // 2.5 Process Tokens
271
+ processTokens(process.cwd(), settings.cssDir);
272
+
163
273
  // 3. Copy CSS files
164
274
  if (fs.existsSync(settings.cssDir)) {
165
- console.log('Copying CSS...');
166
- const cssFiles = fs.readdirSync(settings.cssDir).filter(file => file.endsWith('.css'));
167
- cssFiles.forEach(file => {
168
- fs.copyFileSync(path.join(settings.cssDir, file), path.join(settings.distCssDir, file));
169
- });
275
+ console.log("Copying CSS...");
276
+ const cssFiles = fs
277
+ .readdirSync(settings.cssDir)
278
+ .filter((file) => file.endsWith(".css"));
279
+ cssFiles.forEach((file) => {
280
+ fs.copyFileSync(
281
+ path.join(settings.cssDir, file),
282
+ path.join(settings.distCssDir, file),
283
+ );
284
+ });
170
285
  }
171
286
 
172
287
  // 4. Read patterns & JS
173
- console.log('Processing patterns...');
288
+ console.log("Processing patterns...");
174
289
  const patterns = [];
175
290
  const scripts = [];
176
291
 
177
292
  const items = fs.readdirSync(settings.patternsDir);
178
293
 
179
- items.forEach(item => {
180
- // Skip _layout.html or hidden files
181
- if (item.startsWith('_') || item.startsWith('.')) return;
182
-
183
- const itemPath = path.join(settings.patternsDir, item);
184
- const stat = fs.statSync(itemPath);
185
-
186
- let name, content, id;
187
-
188
- // Handle Directory Pattern
189
- if (stat.isDirectory()) {
190
- const indexFile = path.join(itemPath, 'index.html');
191
-
192
- if (fs.existsSync(indexFile)) {
193
- name = item;
194
- id = item;
195
- content = fs.readFileSync(indexFile, 'utf-8');
196
-
197
- // Find all .js files
198
- const jsFiles = fs.readdirSync(itemPath).filter(file => file.endsWith('.js'));
199
-
200
- jsFiles.forEach(jsFile => {
201
- const scriptContent = fs.readFileSync(path.join(itemPath, jsFile), 'utf-8');
202
- scripts.push(`
294
+ items.forEach((item) => {
295
+ // Skip _layout.html or hidden files
296
+ if (item.startsWith("_") || item.startsWith(".")) return;
297
+
298
+ const itemPath = path.join(settings.patternsDir, item);
299
+ const stat = fs.statSync(itemPath);
300
+
301
+ let name, content, id;
302
+
303
+ // Handle Directory Pattern
304
+ if (stat.isDirectory()) {
305
+ const indexFile = path.join(itemPath, "index.html");
306
+
307
+ if (fs.existsSync(indexFile)) {
308
+ name = item;
309
+ id = item;
310
+ content = fs.readFileSync(indexFile, "utf-8");
311
+
312
+ // Find all .js files
313
+ const jsFiles = fs
314
+ .readdirSync(itemPath)
315
+ .filter((file) => file.endsWith(".js"));
316
+
317
+ jsFiles.forEach((jsFile) => {
318
+ const scriptContent = fs.readFileSync(
319
+ path.join(itemPath, jsFile),
320
+ "utf-8",
321
+ );
322
+ scripts.push(`
203
323
  /* --- Pattern: ${name} / File: ${jsFile} --- */
204
324
  (function() {
205
325
  ${scriptContent}
206
326
  })();
207
327
  `);
208
- });
209
- }
210
- }
211
- // Handle Single File Pattern
212
- else if (item.endsWith('.html')) {
213
- name = path.basename(item, '.html');
214
- id = name;
215
- content = fs.readFileSync(itemPath, 'utf-8');
328
+ });
216
329
  }
330
+ }
331
+ // Handle Single File Pattern
332
+ else if (item.endsWith(".html")) {
333
+ name = path.basename(item, ".html");
334
+ id = name;
335
+ content = fs.readFileSync(itemPath, "utf-8");
336
+ }
217
337
 
218
- if (name && content) {
219
- patterns.push({ name, id, content });
220
- }
221
- });
338
+ if (name && content) {
339
+ patterns.push({ name, id, content });
340
+ }
341
+ });
222
342
 
223
343
  // 5. Generate HTML fragments
224
- const sidebarLinks = patterns.map(p =>
225
- `<a href="#${p.id}">${p.name}</a>`
226
- ).join('\n');
227
-
228
- const patternBlocks = patterns.map(p => {
229
- const escapedContent = p.content
230
- .replace(/&/g, "&amp;")
231
- .replace(/</g, "&lt;")
232
- .replace(/>/g, "&gt;")
233
- .replace(/"/g, "&quot;")
234
- .replace(/'/g, "&#039;");
235
-
236
- return `
344
+ const sidebarLinks = patterns
345
+ .map((p) => `<a href="#${p.id}">${p.name}</a>`)
346
+ .join("\n");
347
+
348
+ const patternBlocks = patterns
349
+ .map((p) => {
350
+ const escapedContent = p.content
351
+ .replace(/&/g, "&amp;")
352
+ .replace(/</g, "&lt;")
353
+ .replace(/>/g, "&gt;")
354
+ .replace(/"/g, "&quot;")
355
+ .replace(/'/g, "&#039;");
356
+
357
+ return `
237
358
  <section id="${p.id}">
238
359
  <h2>${p.name}</h2>
239
360
  <div class="preview">${p.content}</div>
240
361
  <pre><code>${escapedContent}</code></pre>
241
362
  </section>
242
363
  `;
243
- }).join('\n');
364
+ })
365
+ .join("\n");
244
366
 
245
367
  // 6. Write JS Bundle
246
368
  if (scripts.length > 0) {
247
- fs.writeFileSync(settings.outputJsFile, scripts.join('\n'));
248
- console.log(`Bundled ${scripts.length} scripts to ${settings.outputJsFile}`);
369
+ fs.writeFileSync(settings.outputJsFile, scripts.join("\n"));
370
+ console.log(
371
+ `Bundled ${scripts.length} scripts to ${settings.outputJsFile}`,
372
+ );
249
373
  } else {
250
- fs.writeFileSync(settings.outputJsFile, '// No pattern scripts found');
374
+ fs.writeFileSync(settings.outputJsFile, "// No pattern scripts found");
251
375
  }
252
376
 
253
377
  // 7. Load Layout
254
378
  let layoutContent;
255
379
  if (fs.existsSync(settings.projectLayout)) {
256
380
  console.log(`Using custom layout: ${settings.projectLayout}`);
257
- layoutContent = fs.readFileSync(settings.projectLayout, 'utf-8');
381
+ layoutContent = fs.readFileSync(settings.projectLayout, "utf-8");
258
382
  } else {
259
383
  // console.log(`Using internal layout`);
260
- layoutContent = fs.readFileSync(settings.internalLayout, 'utf-8');
384
+ layoutContent = fs.readFileSync(settings.internalLayout, "utf-8");
385
+ }
386
+
387
+ let headExtras = "";
388
+ // Check if the tokens file exists in the *output* directory (where we copied it to)
389
+ if (fs.existsSync(settings.distTokensCssFile)) {
390
+ headExtras = '<link rel="stylesheet" href="css/tokens.css">';
261
391
  }
262
-
392
+
263
393
  const finalHtml = layoutContent
264
- .replace('<!-- SIDEBAR_LINKS -->', sidebarLinks)
265
- .replace('<!-- PATTERNS -->', patternBlocks);
394
+ .replace("<!-- SIDEBAR_LINKS -->", sidebarLinks)
395
+ .replace("<!-- PATTERNS -->", patternBlocks)
396
+ .replace("<!-- HEAD_EXTRAS -->", headExtras);
266
397
 
267
398
  // 8. Write output
268
399
  fs.writeFileSync(settings.outputFile, finalHtml);
269
-
400
+
270
401
  console.log(`Build complete! Generated ${settings.outputFile}`);
271
402
  }
272
403
 
@@ -276,15 +407,16 @@ try {
276
407
  const fileConfig = loadConfig(cliOptions.config);
277
408
  const settings = resolveSettings(cliOptions, fileConfig);
278
409
 
279
- if (cliOptions.command === 'init') {
410
+ if (cliOptions.command === "init") {
280
411
  runInit(settings, cliOptions);
281
412
  } else {
282
413
  build(settings);
283
414
  if (cliOptions.watch) {
284
- console.log('[SwatchKit] Watch mode is not yet fully implemented.');
415
+ console.log("[SwatchKit] Watch mode is not yet fully implemented.");
285
416
  }
286
417
  }
287
418
  } catch (error) {
288
- console.error('[SwatchKit] Error:', error.message);
419
+ console.error("[SwatchKit] Error:", error.message);
289
420
  process.exit(1);
290
421
  }
422
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swatchkit",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "A lightweight tool for creating HTML pattern libraries.",
5
5
  "main": "build.js",
6
6
  "bin": {
package/src/layout.html CHANGED
@@ -5,6 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>SwatchKit</title>
7
7
  <link rel="stylesheet" href="/css/global.css" />
8
+ <!-- HEAD_EXTRAS -->
8
9
  <style>
9
10
  body {
10
11
  display: flex;
@@ -86,4 +87,4 @@
86
87
  </main>
87
88
  <script src="js/patterns.js"></script>
88
89
  </body>
89
- </html>
90
+ </html>