swatchkit 0.0.9 → 0.0.11

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
@@ -23,7 +23,7 @@ my-project/
23
23
  ├── css/
24
24
  │ ├── tokens.css # Generated design tokens (CSS custom properties)
25
25
  │ └── styles.css # Starter stylesheet (imports tokens.css)
26
- ├── swatches/
26
+ ├── swatchkit/
27
27
  │ ├── _layout.html # Layout template (you own this)
28
28
  │ └── tokens/ # Token definitions + documentation patterns
29
29
  │ ├── colors.json
@@ -54,13 +54,32 @@ Then add it to your `package.json` scripts:
54
54
 
55
55
  ## Features
56
56
 
57
- ### 1. The Magic Folder
58
- By default, SwatchKit looks for a `swatches/` folder in your project root.
59
- * **Single File:** Drop `card.html` into `swatches/`. It appears in the library.
60
- * **Component Folder:** Drop a folder like `swatches/carousel/` containing `index.html`. It works the same way.
57
+ ### 1. The Magic Folder & Project Structure
58
+ By default, SwatchKit looks for a `swatchkit/` folder in your project root.
59
+
60
+ **Organize by Folder:**
61
+ SwatchKit automatically turns subfolders into sections in the documentation sidebar.
62
+
63
+ ```
64
+ swatchkit/
65
+ ├── tokens/ # Section: "Design Tokens"
66
+ │ └── colors.json
67
+ ├── components/ # Section: "Components"
68
+ │ ├── button.html
69
+ │ └── card/
70
+ │ └── index.html
71
+ ├── compositions/ # Section: "Compositions"
72
+ │ └── sidebar.html
73
+ └── utilities/ # Section: "Utilities"
74
+ └── flow.html
75
+ ```
76
+
77
+ * **Files at root:** Go to the "Patterns" section.
78
+ * **Subfolders:** Create a new section (e.g. `utilities/` -> "Utilities").
79
+ * **Tokens:** Special folder for JSON definitions.
61
80
 
62
81
  ### 2. Design Token Engine
63
- SwatchKit scaffolds a design system for you. Edit the JSON files in `swatches/tokens/`, and SwatchKit auto-generates `css/tokens.css`.
82
+ SwatchKit scaffolds a design system for you. Edit the JSON files in `swatchkit/tokens/`, and SwatchKit auto-generates `css/tokens.css`.
64
83
 
65
84
  **Supported Tokens:**
66
85
  * **Colors** (`colors.json`): Generates palettes.
@@ -80,7 +99,7 @@ SwatchKit can auto-calculate fluid typography and spacing scales.
80
99
  * **Fluid:** Provide `min` and `max` (e.g. `16` and `18`).
81
100
  * **Auto-Fluid:** Provide just ONE side (`min` or `max`), and SwatchKit calculates the other using a default ratio (1.125).
82
101
 
83
- **Example (`swatches/tokens/text-sizes.json`):**
102
+ **Example (`swatchkit/tokens/text-sizes.json`):**
84
103
  ```json
85
104
  {
86
105
  "title": "Text Sizes",
@@ -110,7 +129,7 @@ SwatchKit can auto-calculate fluid typography and spacing scales.
110
129
 
111
130
  You can mix modular scales with manual overrides.
112
131
 
113
- **Example (`swatches/tokens/text-leading.json`):**
132
+ **Example (`swatchkit/tokens/text-leading.json`):**
114
133
  ```json
115
134
  {
116
135
  "base": 1,
@@ -141,7 +160,7 @@ body {
141
160
  The pattern library uses **your stylesheet**, so components render exactly as they will in your app.
142
161
 
143
162
  ### 6. Custom Layouts
144
- When you run `swatchkit init`, we create `swatches/_layout.html`.
163
+ When you run `swatchkit init`, we create `swatchkit/_layout.html`.
145
164
  **You own this file.**
146
165
  * Link to your own stylesheets.
147
166
  * Add custom fonts, scripts, or meta tags.
@@ -151,7 +170,7 @@ SwatchKit injects content into the `<!-- PATTERNS -->`, `<!-- SIDEBAR_LINKS -->`
151
170
 
152
171
  ### 7. JavaScript Bundling
153
172
  If your component needs client-side JS:
154
- 1. Create a folder: `swatches/carousel/`.
173
+ 1. Create a folder: `swatchkit/carousel/`.
155
174
  2. Add `index.html` (Markup).
156
175
  3. Add `script.js` (Logic).
157
176
 
@@ -172,7 +191,7 @@ swatchkit [command] [options]
172
191
  | :--- | :--- | :--- |
173
192
  | `--watch` | `-w` | Watch files and rebuild on change. |
174
193
  | `--config` | `-c` | Path to config file. |
175
- | `--input` | `-i` | Pattern directory (Default: `swatches/`). |
194
+ | `--input` | `-i` | Pattern directory (Default: `swatchkit/`). |
176
195
  | `--outDir` | `-o` | Output directory (Default: `public/swatchkit`). |
177
196
  | `--force` | `-f` | Overwrite layout file during init. |
178
197
 
@@ -191,15 +210,6 @@ module.exports = {
191
210
  css: './assets/css',
192
211
 
193
212
  // Exclude files (supports glob patterns)
194
- exclude: ['*.test.js', 'temp*'],
195
-
196
- // Override token settings
197
- tokens: {
198
- input: './design-tokens', // Where token JSON files live
199
- leading: {
200
- ratio: 1.25, // Modular scale ratio
201
- base: 1
202
- }
203
- }
213
+ exclude: ['*.test.js', 'temp*']
204
214
  };
205
215
  ```
package/build.js CHANGED
@@ -68,7 +68,7 @@ function loadConfig(configPath) {
68
68
  }
69
69
 
70
70
  // --- 2.5 Glob Matching Helper ---
71
- function matchesPattern(filename, pattern) {
71
+ function matchesGlob(filename, pattern) {
72
72
  // Simple wildcard support
73
73
  if (pattern.includes("*")) {
74
74
  const parts = pattern.split("*");
@@ -88,28 +88,25 @@ function matchesPattern(filename, pattern) {
88
88
  return filename === pattern;
89
89
  }
90
90
 
91
+ function toTitleCase(str) {
92
+ return str.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
93
+ }
94
+
91
95
  // --- 3. Smart Defaults & Path Resolution ---
92
96
  function resolveSettings(cliOptions, fileConfig) {
93
97
  const cwd = process.cwd();
94
98
 
95
99
  // Helper to find patterns dir
96
- function findPatternsDir() {
100
+ function findSwatchkitDir() {
97
101
  // 1. Explicit input
98
102
  if (cliOptions.input) return path.resolve(cwd, cliOptions.input);
99
103
  if (fileConfig.input) return path.resolve(cwd, fileConfig.input);
100
104
 
101
- // 2. Search candidates
102
- const candidates = ["swatches", "src/swatches"];
103
- for (const cand of candidates) {
104
- const absPath = path.join(cwd, cand);
105
- if (fs.existsSync(absPath)) return absPath;
106
- }
107
-
108
- // 3. Fallback default (swatches)
109
- return path.join(cwd, "swatches");
105
+ // 2. Default
106
+ return path.join(cwd, "swatchkit");
110
107
  }
111
108
 
112
- const patternsDir = findPatternsDir();
109
+ const swatchkitDir = findSwatchkitDir();
113
110
 
114
111
  // Output Dir
115
112
  // Default: public/swatchkit
@@ -129,13 +126,13 @@ function resolveSettings(cliOptions, fileConfig) {
129
126
  // Default: swatches/tokens/ (not src/tokens/)
130
127
  const tokensDir = fileConfig.tokens?.input
131
128
  ? path.resolve(cwd, fileConfig.tokens.input)
132
- : path.join(patternsDir, "tokens");
129
+ : path.join(swatchkitDir, "tokens");
133
130
 
134
131
  // Exclude patterns
135
132
  const exclude = fileConfig.exclude || [];
136
133
 
137
134
  return {
138
- patternsDir,
135
+ swatchkitDir,
139
136
  outDir,
140
137
  cssDir,
141
138
  tokensDir,
@@ -144,14 +141,14 @@ function resolveSettings(cliOptions, fileConfig) {
144
141
  // Internal layout template (relative to this script)
145
142
  internalLayout: path.join(__dirname, "src/layout.html"),
146
143
  // Project specific layout override
147
- projectLayout: path.join(patternsDir, "_layout.html"),
144
+ projectLayout: path.join(swatchkitDir, "_layout.html"),
148
145
 
149
146
  // Derived paths
150
147
  distCssDir: path.join(outDir, "css"),
151
148
  distTokensCssFile: path.join(outDir, "css", "tokens.css"),
152
149
  distJsDir: path.join(outDir, "js"),
153
150
  outputFile: path.join(outDir, "index.html"),
154
- outputJsFile: path.join(outDir, "js/patterns.js"),
151
+ outputJsFile: path.join(outDir, "js/swatches.js"),
155
152
  tokensCssFile: path.join(cssDir, "tokens.css"),
156
153
  stylesCssFile: path.join(cssDir, "styles.css"),
157
154
  };
@@ -161,10 +158,10 @@ function resolveSettings(cliOptions, fileConfig) {
161
158
  function runInit(settings, options) {
162
159
  console.log("[SwatchKit] Initializing...");
163
160
 
164
- // Ensure patterns directory exists
165
- if (!fs.existsSync(settings.patternsDir)) {
166
- console.log(`Creating patterns directory: ${settings.patternsDir}`);
167
- fs.mkdirSync(settings.patternsDir, { recursive: true });
161
+ // Ensure swatchkit directory exists
162
+ if (!fs.existsSync(settings.swatchkitDir)) {
163
+ console.log(`Creating swatchkit directory: ${settings.swatchkitDir}`);
164
+ fs.mkdirSync(settings.swatchkitDir, { recursive: true });
168
165
  }
169
166
 
170
167
  // Create swatches/tokens directory (for both JSON definitions and HTML patterns)
@@ -197,20 +194,7 @@ function runInit(settings, options) {
197
194
  copyDefault('text-weights.json', 'text-weights.json');
198
195
 
199
196
  // 3. Create Text Leading Token
200
- const leadingFile = path.join(tokensDir, "text-leading.json");
201
- if (!fs.existsSync(leadingFile)) {
202
- const srcPath = path.join(__dirname, 'src/blueprints/text-leading.json');
203
- let sampleLeading = JSON.parse(fs.readFileSync(srcPath, 'utf-8'));
204
-
205
- // Get settings from config or defaults
206
- const leadingConfig = settings.fileConfig?.tokens?.leading || {};
207
- if (leadingConfig.base) sampleLeading.base = leadingConfig.base;
208
- if (leadingConfig.ratio) sampleLeading.ratio = leadingConfig.ratio;
209
- if (leadingConfig.items) sampleLeading.items = leadingConfig.items;
210
-
211
- fs.writeFileSync(leadingFile, JSON.stringify(sampleLeading, null, 2));
212
- console.log(`Created sample tokens file at ${leadingFile}`);
213
- }
197
+ copyDefault('text-leading.json', 'text-leading.json');
214
198
 
215
199
  // 4. Create Viewports Token
216
200
  copyDefault('viewports.json', 'viewports.json');
@@ -283,15 +267,15 @@ function runInit(settings, options) {
283
267
  }
284
268
 
285
269
  // --- 5. Build Logic ---
286
- function scanDirectory(dir, scriptsCollector, exclude = []) {
287
- const patterns = [];
288
- if (!fs.existsSync(dir)) return patterns;
270
+ function scanSwatches(dir, scriptsCollector, exclude = []) {
271
+ const swatches = [];
272
+ if (!fs.existsSync(dir)) return swatches;
289
273
 
290
274
  const items = fs.readdirSync(dir);
291
275
 
292
276
  items.forEach((item) => {
293
277
  // Skip excluded items
294
- if (exclude.some(pattern => matchesPattern(item, pattern))) return;
278
+ if (exclude.some(pattern => matchesGlob(item, pattern))) return;
295
279
 
296
280
  // Skip _layout.html or hidden files
297
281
  if (item.startsWith("_") || item.startsWith(".")) return;
@@ -301,7 +285,7 @@ function scanDirectory(dir, scriptsCollector, exclude = []) {
301
285
 
302
286
  let name, content, id;
303
287
 
304
- // Handle Directory Pattern
288
+ // Handle Component Directory
305
289
  if (stat.isDirectory()) {
306
290
  const indexFile = path.join(itemPath, "index.html");
307
291
 
@@ -321,7 +305,7 @@ function scanDirectory(dir, scriptsCollector, exclude = []) {
321
305
  "utf-8",
322
306
  );
323
307
  scriptsCollector.push(`
324
- /* --- Pattern: ${name} / File: ${jsFile} --- */
308
+ /* --- Swatch: ${name} / File: ${jsFile} --- */
325
309
  (function() {
326
310
  ${scriptContent}
327
311
  })();
@@ -329,7 +313,7 @@ ${scriptContent}
329
313
  });
330
314
  }
331
315
  }
332
- // Handle Single File Pattern
316
+ // Handle Single File
333
317
  else if (item.endsWith(".html")) {
334
318
  name = path.basename(item, ".html");
335
319
  id = name;
@@ -344,27 +328,27 @@ ${scriptContent}
344
328
  ${scriptContent}
345
329
  })();
346
330
  `);
347
- // Don't add to patterns list, just scripts
331
+ // Don't add to swatches list, just scripts
348
332
  return;
349
333
  }
350
334
 
351
335
  if (name && content) {
352
- patterns.push({ name, id, content });
336
+ swatches.push({ name, id, content });
353
337
  }
354
338
  });
355
339
 
356
- return patterns;
340
+ return swatches;
357
341
  }
358
342
 
359
343
  function build(settings) {
360
344
  console.log(`[SwatchKit] Starting build...`);
361
- console.log(` Patterns: ${settings.patternsDir}`);
345
+ console.log(` Source: ${settings.swatchkitDir}`);
362
346
  console.log(` Output: ${settings.outDir}`);
363
347
 
364
- // 1. Check if patterns directory exists
365
- if (!fs.existsSync(settings.patternsDir)) {
348
+ // 1. Check if source directory exists
349
+ if (!fs.existsSync(settings.swatchkitDir)) {
366
350
  console.error(
367
- `Error: Patterns directory not found at ${settings.patternsDir}`,
351
+ `Error: SwatchKit directory not found at ${settings.swatchkitDir}`,
368
352
  );
369
353
  console.error('Run "swatchkit init" to get started.');
370
354
  process.exit(1);
@@ -392,43 +376,96 @@ function build(settings) {
392
376
  });
393
377
  }
394
378
 
395
- // 4. Read patterns & JS
396
- console.log("Processing patterns...");
379
+ // 4. Read swatches & JS
380
+ console.log("Processing swatches...");
397
381
  const scripts = [];
382
+ const sections = {}; // Map<SectionName, Array<Swatch>>
383
+
384
+ if (fs.existsSync(settings.swatchkitDir)) {
385
+ const items = fs.readdirSync(settings.swatchkitDir);
386
+ const exclude = settings.exclude || [];
387
+
388
+ // Scan subdirectories (Sections)
389
+ items.forEach(item => {
390
+ if (exclude.some(p => matchesGlob(item, p))) return;
391
+ if (item.startsWith(".") || item.startsWith("_")) return;
392
+
393
+ const itemPath = path.join(settings.swatchkitDir, item);
394
+ if (fs.lstatSync(itemPath).isDirectory()) {
395
+ const hasIndex = fs.existsSync(path.join(itemPath, "index.html"));
396
+
397
+ if (!hasIndex) {
398
+ // It is a Section Container (e.g. "Utilities")
399
+ const sectionName = item === 'tokens' ? 'Design Tokens' : toTitleCase(item);
400
+ const swatches = scanSwatches(itemPath, scripts, exclude);
401
+ if (swatches.length > 0) {
402
+ sections[sectionName] = swatches;
403
+ }
404
+ }
405
+ }
406
+ });
398
407
 
399
- // Pass 1: Tokens (in [patternsDir]/tokens/)
400
- const tokensDir = path.join(settings.patternsDir, "tokens");
401
- const tokenPages = scanDirectory(tokensDir, scripts);
402
-
403
- // Pass 2: Patterns (in [patternsDir], excluding 'tokens' and user excludes)
404
- const userExcludes = settings.exclude || [];
405
- const patternPages = scanDirectory(settings.patternsDir, scripts, ["tokens", ...userExcludes]);
406
-
407
- // Combine for content generation (Tokens first, then Patterns)
408
- const allPatterns = [...tokenPages, ...patternPages];
408
+ // Scan root swatches (Files + Component Folders at root)
409
+ const rootSwatches = [];
410
+ items.forEach(item => {
411
+ if (exclude.some(p => matchesGlob(item, p))) return;
412
+ if (item.startsWith(".") || item.startsWith("_")) return;
413
+
414
+ const itemPath = path.join(settings.swatchkitDir, item);
415
+ const stat = fs.statSync(itemPath);
416
+
417
+ if (stat.isFile() && item.endsWith('.html')) {
418
+ const name = path.basename(item, ".html");
419
+ const content = fs.readFileSync(itemPath, "utf-8");
420
+ rootSwatches.push({ name, id: name, content });
421
+ } else if (stat.isDirectory()) {
422
+ const indexFile = path.join(itemPath, "index.html");
423
+ if (fs.existsSync(indexFile)) {
424
+ // Component folder swatch at root
425
+ const name = item;
426
+ const content = fs.readFileSync(indexFile, "utf-8");
427
+ rootSwatches.push({ name, id: name, content });
428
+
429
+ // Collect JS
430
+ const jsFiles = fs.readdirSync(itemPath).filter(f => f.endsWith(".js"));
431
+ jsFiles.forEach(jsFile => {
432
+ const scriptContent = fs.readFileSync(path.join(itemPath, jsFile), "utf-8");
433
+ scripts.push(`/* ${name}/${jsFile} */ (function(){${scriptContent}})();`);
434
+ });
435
+ }
436
+ }
437
+ });
438
+
439
+ if (rootSwatches.length > 0) {
440
+ sections["Patterns"] = rootSwatches;
441
+ }
442
+ }
409
443
 
410
444
  // 5. Generate HTML fragments
411
445
 
412
446
  // Sidebar generation with grouping
413
447
  let sidebarLinks = "";
448
+ let swatchBlocks = "";
449
+
450
+ // Helper to sort sections: Tokens first, then A-Z, Patterns last
451
+ const sortedKeys = Object.keys(sections).sort((a, b) => {
452
+ if (a === 'Design Tokens') return -1;
453
+ if (b === 'Design Tokens') return 1;
454
+ if (a === 'Patterns') return 1;
455
+ if (b === 'Patterns') return -1;
456
+ return a.localeCompare(b);
457
+ });
414
458
 
415
- if (tokenPages.length > 0) {
416
- sidebarLinks += `<h3>Design Tokens</h3>\n`;
417
- sidebarLinks += tokenPages
459
+ sortedKeys.forEach(section => {
460
+ const swatches = sections[section];
461
+ sidebarLinks += `<h3>${section}</h3>\n`;
462
+ sidebarLinks += swatches
418
463
  .map((p) => `<a href="#${p.id}">${p.name}</a>`)
419
464
  .join("\n");
420
465
  sidebarLinks += `\n`;
421
- }
422
466
 
423
- if (patternPages.length > 0) {
424
- sidebarLinks += `<h3>Patterns</h3>\n`;
425
- sidebarLinks += patternPages
426
- .map((p) => `<a href="#${p.id}">${p.name}</a>`)
427
- .join("\n");
428
- }
429
-
430
- const patternBlocks = allPatterns
431
- .map((p) => {
467
+ // Generate Blocks
468
+ swatchBlocks += swatches.map((p) => {
432
469
  const escapedContent = p.content
433
470
  .replace(/&/g, "&amp;")
434
471
  .replace(/</g, "&lt;")
@@ -438,13 +475,13 @@ function build(settings) {
438
475
 
439
476
  return `
440
477
  <section id="${p.id}">
441
- <h2>${p.name}</h2>
478
+ <h2>${p.name} <small style="font-weight: normal; opacity: 0.6; font-size: 0.7em">(${section})</small></h2>
442
479
  <div class="preview">${p.content}</div>
443
480
  <pre><code>${escapedContent}</code></pre>
444
481
  </section>
445
482
  `;
446
- })
447
- .join("\n");
483
+ }).join("\n");
484
+ });
448
485
 
449
486
  // 6. Write JS Bundle
450
487
  if (scripts.length > 0) {
@@ -453,7 +490,7 @@ function build(settings) {
453
490
  `Bundled ${scripts.length} scripts to ${settings.outputJsFile}`,
454
491
  );
455
492
  } else {
456
- fs.writeFileSync(settings.outputJsFile, "// No pattern scripts found");
493
+ fs.writeFileSync(settings.outputJsFile, "// No swatch scripts found");
457
494
  }
458
495
 
459
496
  // 7. Load Layout
@@ -471,7 +508,7 @@ function build(settings) {
471
508
 
472
509
  const finalHtml = layoutContent
473
510
  .replace("<!-- SIDEBAR_LINKS -->", sidebarLinks)
474
- .replace("<!-- PATTERNS -->", patternBlocks)
511
+ .replace("<!-- SWATCHES -->", swatchBlocks)
475
512
  .replace("<!-- HEAD_EXTRAS -->", headExtras);
476
513
 
477
514
  // 8. Write output
@@ -483,7 +520,7 @@ function build(settings) {
483
520
  // --- 6. Watch Logic ---
484
521
  function watch(settings) {
485
522
  const watchPaths = [
486
- settings.patternsDir,
523
+ settings.swatchkitDir,
487
524
  settings.tokensDir,
488
525
  settings.projectLayout,
489
526
  settings.stylesCssFile
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swatchkit",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "A lightweight tool for creating HTML pattern libraries.",
5
5
  "main": "build.js",
6
6
  "bin": {
package/src/layout.html CHANGED
@@ -83,8 +83,8 @@
83
83
  </nav>
84
84
  </aside>
85
85
  <main>
86
- <!-- PATTERNS -->
86
+ <!-- SWATCHES -->
87
87
  </main>
88
- <script src="js/patterns.js"></script>
88
+ <script src="js/swatches.js"></script>
89
89
  </body>
90
90
  </html>