swatchkit 0.0.10 → 0.0.12

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
 
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)
@@ -270,15 +267,15 @@ function runInit(settings, options) {
270
267
  }
271
268
 
272
269
  // --- 5. Build Logic ---
273
- function scanDirectory(dir, scriptsCollector, exclude = []) {
274
- const patterns = [];
275
- if (!fs.existsSync(dir)) return patterns;
270
+ function scanSwatches(dir, scriptsCollector, exclude = []) {
271
+ const swatches = [];
272
+ if (!fs.existsSync(dir)) return swatches;
276
273
 
277
274
  const items = fs.readdirSync(dir);
278
275
 
279
276
  items.forEach((item) => {
280
277
  // Skip excluded items
281
- if (exclude.some(pattern => matchesPattern(item, pattern))) return;
278
+ if (exclude.some(pattern => matchesGlob(item, pattern))) return;
282
279
 
283
280
  // Skip _layout.html or hidden files
284
281
  if (item.startsWith("_") || item.startsWith(".")) return;
@@ -288,7 +285,7 @@ function scanDirectory(dir, scriptsCollector, exclude = []) {
288
285
 
289
286
  let name, content, id;
290
287
 
291
- // Handle Directory Pattern
288
+ // Handle Component Directory
292
289
  if (stat.isDirectory()) {
293
290
  const indexFile = path.join(itemPath, "index.html");
294
291
 
@@ -308,7 +305,7 @@ function scanDirectory(dir, scriptsCollector, exclude = []) {
308
305
  "utf-8",
309
306
  );
310
307
  scriptsCollector.push(`
311
- /* --- Pattern: ${name} / File: ${jsFile} --- */
308
+ /* --- Swatch: ${name} / File: ${jsFile} --- */
312
309
  (function() {
313
310
  ${scriptContent}
314
311
  })();
@@ -316,7 +313,7 @@ ${scriptContent}
316
313
  });
317
314
  }
318
315
  }
319
- // Handle Single File Pattern
316
+ // Handle Single File
320
317
  else if (item.endsWith(".html")) {
321
318
  name = path.basename(item, ".html");
322
319
  id = name;
@@ -331,27 +328,27 @@ ${scriptContent}
331
328
  ${scriptContent}
332
329
  })();
333
330
  `);
334
- // Don't add to patterns list, just scripts
331
+ // Don't add to swatches list, just scripts
335
332
  return;
336
333
  }
337
334
 
338
335
  if (name && content) {
339
- patterns.push({ name, id, content });
336
+ swatches.push({ name, id, content });
340
337
  }
341
338
  });
342
339
 
343
- return patterns;
340
+ return swatches;
344
341
  }
345
342
 
346
343
  function build(settings) {
347
344
  console.log(`[SwatchKit] Starting build...`);
348
- console.log(` Patterns: ${settings.patternsDir}`);
345
+ console.log(` Source: ${settings.swatchkitDir}`);
349
346
  console.log(` Output: ${settings.outDir}`);
350
347
 
351
- // 1. Check if patterns directory exists
352
- if (!fs.existsSync(settings.patternsDir)) {
348
+ // 1. Check if source directory exists
349
+ if (!fs.existsSync(settings.swatchkitDir)) {
353
350
  console.error(
354
- `Error: Patterns directory not found at ${settings.patternsDir}`,
351
+ `Error: SwatchKit directory not found at ${settings.swatchkitDir}`,
355
352
  );
356
353
  console.error('Run "swatchkit init" to get started.');
357
354
  process.exit(1);
@@ -379,43 +376,96 @@ function build(settings) {
379
376
  });
380
377
  }
381
378
 
382
- // 4. Read patterns & JS
383
- console.log("Processing patterns...");
379
+ // 4. Read swatches & JS
380
+ console.log("Processing swatches...");
384
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
+ });
385
407
 
386
- // Pass 1: Tokens (in [patternsDir]/tokens/)
387
- const tokensDir = path.join(settings.patternsDir, "tokens");
388
- const tokenPages = scanDirectory(tokensDir, scripts);
389
-
390
- // Pass 2: Patterns (in [patternsDir], excluding 'tokens' and user excludes)
391
- const userExcludes = settings.exclude || [];
392
- const patternPages = scanDirectory(settings.patternsDir, scripts, ["tokens", ...userExcludes]);
393
-
394
- // Combine for content generation (Tokens first, then Patterns)
395
- 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
+ }
396
443
 
397
444
  // 5. Generate HTML fragments
398
445
 
399
446
  // Sidebar generation with grouping
400
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
+ });
401
458
 
402
- if (tokenPages.length > 0) {
403
- sidebarLinks += `<h3>Design Tokens</h3>\n`;
404
- sidebarLinks += tokenPages
459
+ sortedKeys.forEach(section => {
460
+ const swatches = sections[section];
461
+ sidebarLinks += `<h3>${section}</h3>\n`;
462
+ sidebarLinks += swatches
405
463
  .map((p) => `<a href="#${p.id}">${p.name}</a>`)
406
464
  .join("\n");
407
465
  sidebarLinks += `\n`;
408
- }
409
466
 
410
- if (patternPages.length > 0) {
411
- sidebarLinks += `<h3>Patterns</h3>\n`;
412
- sidebarLinks += patternPages
413
- .map((p) => `<a href="#${p.id}">${p.name}</a>`)
414
- .join("\n");
415
- }
416
-
417
- const patternBlocks = allPatterns
418
- .map((p) => {
467
+ // Generate Blocks
468
+ swatchBlocks += swatches.map((p) => {
419
469
  const escapedContent = p.content
420
470
  .replace(/&/g, "&amp;")
421
471
  .replace(/</g, "&lt;")
@@ -425,13 +475,13 @@ function build(settings) {
425
475
 
426
476
  return `
427
477
  <section id="${p.id}">
428
- <h2>${p.name}</h2>
478
+ <h2>${p.name} <small style="font-weight: normal; opacity: 0.6; font-size: 0.7em">(${section})</small></h2>
429
479
  <div class="preview">${p.content}</div>
430
480
  <pre><code>${escapedContent}</code></pre>
431
481
  </section>
432
482
  `;
433
- })
434
- .join("\n");
483
+ }).join("\n");
484
+ });
435
485
 
436
486
  // 6. Write JS Bundle
437
487
  if (scripts.length > 0) {
@@ -440,7 +490,7 @@ function build(settings) {
440
490
  `Bundled ${scripts.length} scripts to ${settings.outputJsFile}`,
441
491
  );
442
492
  } else {
443
- fs.writeFileSync(settings.outputJsFile, "// No pattern scripts found");
493
+ fs.writeFileSync(settings.outputJsFile, "// No swatch scripts found");
444
494
  }
445
495
 
446
496
  // 7. Load Layout
@@ -458,7 +508,7 @@ function build(settings) {
458
508
 
459
509
  const finalHtml = layoutContent
460
510
  .replace("<!-- SIDEBAR_LINKS -->", sidebarLinks)
461
- .replace("<!-- PATTERNS -->", patternBlocks)
511
+ .replace("<!-- SWATCHES -->", swatchBlocks)
462
512
  .replace("<!-- HEAD_EXTRAS -->", headExtras);
463
513
 
464
514
  // 8. Write output
@@ -470,7 +520,7 @@ function build(settings) {
470
520
  // --- 6. Watch Logic ---
471
521
  function watch(settings) {
472
522
  const watchPaths = [
473
- settings.patternsDir,
523
+ settings.swatchkitDir,
474
524
  settings.tokensDir,
475
525
  settings.projectLayout,
476
526
  settings.stylesCssFile
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swatchkit",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
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>