juxscript 1.1.367 → 1.1.369

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/bin/cli.js CHANGED
@@ -289,9 +289,11 @@ Usage:
289
289
  jux serve Start production server
290
290
  jux serve --hot Start dev server with hot reload
291
291
  jux comp [name] Add a component preset to your project
292
+ jux comp [name] -f Force overwrite (backs up existing files)
292
293
 
293
294
  Options:
294
295
  --help, -h Show this help message
296
+ --force, -f Overwrite existing files (creates .bak backups)
295
297
  `);
296
298
  }
297
299
 
@@ -301,6 +303,7 @@ Options:
301
303
  // ═══════════════════════════════════════════════════════════════
302
304
  async function runComp(compName) {
303
305
  const presetsDir = path.join(PACKAGE_ROOT, 'presets');
306
+ const forceFlag = args.includes('--force') || args.includes('-f');
304
307
 
305
308
  if (!fs.existsSync(presetsDir)) {
306
309
  console.error('❌ No presets directory found in juxscript package.');
@@ -342,87 +345,109 @@ async function runComp(compName) {
342
345
  fs.mkdirSync(targetSrcDir, { recursive: true });
343
346
  }
344
347
 
345
- // Copy all files from preset to project src dir
346
- const presetFiles = fs.readdirSync(presetDir, { withFileTypes: true });
348
+ // Resolve a safe destination folder name
349
+ const destFolderName = resolveDestFolderName(targetSrcDir, compName);
350
+ const destDir = path.join(targetSrcDir, destFolderName);
351
+
352
+ // List what will be copied
353
+ const presetFiles = listFilesRecursive(presetDir);
354
+
355
+ console.log(`\n📦 Preset: ${compName}`);
356
+ console.log(` Source: presets/${compName}/`);
357
+ console.log(` Target: ${path.relative(process.cwd(), destDir)}/\n`);
358
+
359
+ if (destFolderName !== compName) {
360
+ console.log(` ⚠️ "${compName}" already exists, using "${destFolderName}" instead.\n`);
361
+ }
362
+
363
+ console.log(` Files to copy:`);
364
+ presetFiles.forEach(f => console.log(` 📄 ${f}`));
365
+ console.log('');
366
+
367
+ // If dest exists (shouldn't with our naming, but just in case with --force)
368
+ if (fs.existsSync(destDir) && !forceFlag) {
369
+ const proceed = await promptYesNo(` Directory "${destFolderName}" exists. Overwrite contents?`);
370
+ if (!proceed) {
371
+ console.log(' Cancelled.\n');
372
+ process.exit(0);
373
+ }
374
+ }
375
+
376
+ // Copy the entire preset folder
377
+ fs.mkdirSync(destDir, { recursive: true });
347
378
  let copied = 0;
379
+ let backed = 0;
348
380
 
349
- for (const entry of presetFiles) {
350
- if (entry.isFile()) {
351
- const srcPath = path.join(presetDir, entry.name);
352
- const destPath = path.join(targetSrcDir, entry.name);
353
-
354
- if (fs.existsSync(destPath)) {
355
- const overwrite = await promptYesNo(` ⚠️ "${entry.name}" already exists. Overwrite?`);
356
- if (!overwrite) {
357
- console.log(` ⏭️ Skipped ${entry.name}`);
358
- continue;
359
- }
360
- }
381
+ for (const relFile of presetFiles) {
382
+ const srcPath = path.join(presetDir, relFile);
383
+ const destPath = path.join(destDir, relFile);
361
384
 
362
- fs.copyFileSync(srcPath, destPath);
363
- console.log(` ✅ ${entry.name}`);
364
- copied++;
385
+ // Ensure subdirectory exists
386
+ const destFileDir = path.dirname(destPath);
387
+ if (!fs.existsSync(destFileDir)) {
388
+ fs.mkdirSync(destFileDir, { recursive: true });
389
+ }
390
+
391
+ if (fs.existsSync(destPath)) {
392
+ // Backup existing
393
+ const backupPath = destPath + '.bak';
394
+ fs.copyFileSync(destPath, backupPath);
395
+ backed++;
396
+ console.log(` 🔄 ${relFile} (backed up to ${relFile}.bak)`);
397
+ } else {
398
+ console.log(` ✅ ${relFile}`);
365
399
  }
400
+
401
+ fs.copyFileSync(srcPath, destPath);
402
+ copied++;
366
403
  }
367
404
 
368
- console.log(`\n✅ Copied ${copied} file(s) from preset "${compName}" to ${path.relative(process.cwd(), targetSrcDir)}/\n`);
405
+ console.log(`\n✅ Done: ${copied} file(s) copied to ${path.relative(process.cwd(), destDir)}/`);
406
+ if (backed > 0) {
407
+ console.log(` ${backed} existing file(s) backed up with .bak extension`);
408
+ }
409
+ console.log('');
369
410
  }
370
411
 
371
- function resolveProjectSrcDir() {
372
- const projectRoot = process.cwd();
373
- const configPath = path.join(projectRoot, 'juxconfig.js');
412
+ /**
413
+ * Resolve a safe folder name: compName -> compName-jux -> compName-1, compName-2, ...
414
+ */
415
+ function resolveDestFolderName(parentDir, baseName) {
416
+ const first = path.join(parentDir, baseName);
417
+ if (!fs.existsSync(first)) return baseName;
374
418
 
375
- if (fs.existsSync(configPath)) {
376
- try {
377
- const configContent = fs.readFileSync(configPath, 'utf8');
378
- const srcDirMatch = configContent.match(/srcDir\s*:\s*['"]([^'"]+)['"]/);
379
- if (srcDirMatch) {
380
- return path.resolve(projectRoot, srcDirMatch[1]);
381
- }
382
- } catch (_) { }
419
+ const juxName = baseName + '-jux';
420
+ const second = path.join(parentDir, juxName);
421
+ if (!fs.existsSync(second)) return juxName;
422
+
423
+ for (let i = 1; i <= 99; i++) {
424
+ const numbered = `${baseName}-${i}`;
425
+ if (!fs.existsSync(path.join(parentDir, numbered))) return numbered;
383
426
  }
384
427
 
385
- return path.resolve(projectRoot, 'jux');
428
+ // Last resort — timestamp
429
+ return `${baseName}-${Date.now()}`;
386
430
  }
387
431
 
388
- function promptPresetSelection(presets) {
389
- return new Promise((resolve) => {
390
- console.log('\n📦 Available component presets:\n');
391
- presets.forEach((p, i) => {
392
- // Read files in preset to show what's included
393
- const presetDir = path.join(PACKAGE_ROOT, 'presets', p);
394
- const files = fs.readdirSync(presetDir).filter(f => !f.startsWith('.'));
395
- console.log(` ${i + 1}) ${p} (${files.join(', ')})`);
396
- });
397
- console.log(` 0) Cancel\n`);
398
-
399
- const rl = createInterface({ input: process.stdin, output: process.stdout });
400
- rl.question('Select a preset (number or name): ', (answer) => {
401
- rl.close();
402
- const trimmed = answer.trim();
403
-
404
- // By number
405
- const num = parseInt(trimmed, 10);
406
- if (num === 0) return resolve(null);
407
- if (num >= 1 && num <= presets.length) return resolve(presets[num - 1]);
408
-
409
- // By name
410
- if (presets.includes(trimmed)) return resolve(trimmed);
411
-
412
- console.error(`❌ Invalid selection: "${trimmed}"`);
413
- resolve(null);
414
- });
415
- });
416
- }
432
+ /**
433
+ * List all files in a directory recursively, returning relative paths
434
+ */
435
+ function listFilesRecursive(dir, base = '') {
436
+ const results = [];
437
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
417
438
 
418
- function promptYesNo(question) {
419
- return new Promise((resolve) => {
420
- const rl = createInterface({ input: process.stdin, output: process.stdout });
421
- rl.question(`${question} (y/N): `, (answer) => {
422
- rl.close();
423
- resolve(answer.trim().toLowerCase() === 'y');
424
- });
425
- });
439
+ for (const entry of entries) {
440
+ if (entry.name.startsWith('.')) continue;
441
+ const rel = base ? path.join(base, entry.name) : entry.name;
442
+
443
+ if (entry.isDirectory()) {
444
+ results.push(...listFilesRecursive(path.join(dir, entry.name), rel));
445
+ } else {
446
+ results.push(rel);
447
+ }
448
+ }
449
+
450
+ return results;
426
451
  }
427
452
 
428
453
  // ═══════════════════════════════════════════════════════════════
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.367",
3
+ "version": "1.1.369",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "./dist/lib/index.js",
@@ -24,6 +24,7 @@
24
24
  "dom-structure-map.json",
25
25
  "bin",
26
26
  "create",
27
+ "presets",
27
28
  "machinery",
28
29
  "juxconfig.example.js",
29
30
  "README.md",
File without changes
File without changes
@@ -0,0 +1,235 @@
1
+ import { jux, pageState } from 'juxscript';
2
+
3
+ var COLLAPSE_ICON_OPEN = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2"></rect><path d="M9 3v18"></path></svg>';
4
+ var COLLAPSE_ICON_CLOSED = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="transform:scaleX(-1)"><rect width="18" height="18" x="3" y="3" rx="2"></rect><path d="M9 3v18"></path></svg>';
5
+
6
+ export function juxSidebar(id, sections, options) {
7
+ options = options || {};
8
+ var _title = options.title || 'App';
9
+ var _logo = options.logo || _title.charAt(0).toUpperCase();
10
+ var _footer = options.footer || '';
11
+ var _width = options.width || '260px';
12
+ var _collapsedWidth = options.collapsedWidth || '52px';
13
+ var _target = options.target;
14
+ var _density = options.density || 'normal';
15
+ var _collapsible = options.collapsible !== undefined ? options.collapsible : false;
16
+ var _collapsed = options.collapsed || false;
17
+ var _sectionCount = 0;
18
+ var _rendered = false;
19
+ var _footerRendered = false;
20
+
21
+ var DENSITY = {
22
+ dense: { headerPad: '10px', itemPad: '4px 10px', labelPad: '6px 10px 2px', footerPad: '8px 12px', gap: '0px', fontSize: '12px', iconSize: '13px' },
23
+ normal: { headerPad: '16px', itemPad: '8px 12px', labelPad: '8px 12px 4px', footerPad: '12px 16px', gap: '1px', fontSize: '13px', iconSize: '15px' },
24
+ inflated: { headerPad: '20px', itemPad: '12px 16px', labelPad: '12px 16px 6px', footerPad: '16px 20px', gap: '4px', fontSize: '14px', iconSize: '17px' }
25
+ };
26
+
27
+ function normalizeItem(item) {
28
+ if (typeof item === 'string') {
29
+ var path = item.startsWith('/') ? item : '/' + item;
30
+ var seg = path.split('/').filter(Boolean).pop() || 'Home';
31
+ return { id: id + '-' + seg.toLowerCase().replace(/[^a-z0-9]/g, '-'), label: seg.charAt(0).toUpperCase() + seg.slice(1), path: path };
32
+ }
33
+ // Support RouteInfo shape { path, name, file } from jux.routes.all()
34
+ var itemPath = item.path || '';
35
+ var seg = itemPath.split('/').filter(Boolean).pop() || 'home';
36
+ return {
37
+ id: item.id || id + '-' + seg.toLowerCase().replace(/[^a-z0-9]/g, '-'),
38
+ label: item.label || item.name || seg.charAt(0).toUpperCase() + seg.slice(1),
39
+ path: itemPath,
40
+ icon: item.icon
41
+ };
42
+ }
43
+
44
+ function normalizeSections(input) {
45
+ if (!input || !Array.isArray(input) || input.length === 0) return [];
46
+ var isFlat = input.every(function (i) { return typeof i === 'string' || (typeof i === 'object' && !i.items); });
47
+ if (isFlat) return [{ items: input.map(normalizeItem) }];
48
+ return input.map(function (s) {
49
+ if (typeof s === 'string') return { items: [normalizeItem(s)] };
50
+ return { label: s.label, items: (s.items || []).map(normalizeItem) };
51
+ });
52
+ }
53
+
54
+ function ensureShell() {
55
+ if (_rendered) return;
56
+ _rendered = true;
57
+ injectStyles();
58
+ var sidebarClass = 'jux-sidebar jux-sidebar--' + _density;
59
+ if (_collapsed) sidebarClass += ' jux-sidebar--collapsed';
60
+ jux.div(id, { class: sidebarClass, style: 'width:' + (_collapsed ? _collapsedWidth : _width), target: _target });
61
+ jux.div(id + '-header', { class: 'jux-sidebar-header', target: id });
62
+ jux.div(id + '-logo', { class: 'jux-sidebar-logo', target: id + '-header' });
63
+ renderLogo();
64
+ jux.span(id + '-title', { class: 'jux-sidebar-title-text', content: _title, target: id + '-header' });
65
+ if (_collapsible) {
66
+ jux.div(id + '-collapse-btn', {
67
+ class: 'jux-sidebar-collapse-btn',
68
+ target: id + '-header'
69
+ });
70
+ var btnEl = document.getElementById(id + '-collapse-btn');
71
+ if (btnEl) btnEl.innerHTML = _collapsed ? COLLAPSE_ICON_CLOSED : COLLAPSE_ICON_OPEN;
72
+ }
73
+ }
74
+
75
+ function renderLogo() {
76
+ var logoEl = document.getElementById(id + '-logo');
77
+ if (!logoEl) return;
78
+ if (_logo.startsWith('<svg') || _logo.startsWith('<SVG')) {
79
+ logoEl.innerHTML = _logo;
80
+ } else if (_logo.startsWith('/') || _logo.startsWith('http') || _logo.startsWith('data:')) {
81
+ logoEl.innerHTML = '<img src="' + _logo + '" alt="logo" class="jux-sidebar-logo-img" />';
82
+ } else {
83
+ logoEl.textContent = _logo;
84
+ }
85
+ }
86
+
87
+ function renderSection(section) {
88
+ ensureShell();
89
+ var i = _sectionCount++;
90
+ if (i > 0) jux.div(id + '-divider-' + i, { class: 'jux-sidebar-divider', target: id });
91
+ if (section.label) jux.div(id + '-label-' + i, { class: 'jux-sidebar-label', content: section.label, target: id });
92
+ jux.nav(id + '-nav-' + i, { target: id, items: section.items });
93
+ }
94
+
95
+ function renderFooter() {
96
+ if (_footerRendered || !_footer) return;
97
+ _footerRendered = true;
98
+ jux.div(id + '-footer', { class: 'jux-sidebar-footer', content: _footer, target: id });
99
+ }
100
+
101
+ function applyCollapsed(collapsed) {
102
+ _collapsed = collapsed;
103
+ var el = document.getElementById(id);
104
+ if (!el) return;
105
+ if (_collapsed) {
106
+ el.classList.add('jux-sidebar--collapsed');
107
+ el.style.width = _collapsedWidth;
108
+ } else {
109
+ el.classList.remove('jux-sidebar--collapsed');
110
+ el.style.width = _width;
111
+ }
112
+ if (_collapsible) {
113
+ var btnEl = document.getElementById(id + '-collapse-btn');
114
+ if (btnEl) btnEl.innerHTML = _collapsed ? COLLAPSE_ICON_CLOSED : COLLAPSE_ICON_OPEN;
115
+ }
116
+ }
117
+
118
+ function injectStyles() {
119
+ jux.style('menu-styles', '\
120
+ body { margin: 0; }\
121
+ .jux-sidebar { display:flex; flex-direction:column; min-height:100vh; background:hsl(var(--card)); color:hsl(var(--card-foreground)); border-right:1px solid hsl(var(--border)); font-family:var(--font-sans,system-ui,sans-serif); transition:width 0.2s ease; overflow:hidden; }\
122
+ .jux-sidebar-header { display:flex; align-items:center; gap:8px; font-size:14px; font-weight:600; border-bottom:1px solid hsl(var(--border)); min-height:20px; position:relative; }\
123
+ .jux-sidebar-logo { width:20px; height:20px; min-width:20px; border-radius:6px; background:hsl(var(--primary)); display:flex; align-items:center; justify-content:center; color:hsl(var(--primary-foreground)); font-size:11px; font-weight:700; flex-shrink:0; overflow:hidden; }\
124
+ .jux-sidebar-logo-img { width:100%; height:100%; object-fit:cover; border-radius:inherit; }\
125
+ .jux-sidebar-logo svg { width:14px; height:14px; }\
126
+ .jux-sidebar-title-text { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; transition:opacity 0.2s ease, max-width 0.2s ease; max-width:200px; flex:1; }\
127
+ .jux-sidebar-label { font-size:11px; font-weight:500; color:hsl(var(--muted-foreground)); text-transform:uppercase; letter-spacing:0.05em; white-space:nowrap; overflow:hidden; transition:opacity 0.2s ease, max-height 0.2s ease; max-height:30px; }\
128
+ .jux-sidebar-footer { margin-top:auto; border-top:1px solid hsl(var(--border)); font-size:11px; color:hsl(var(--muted-foreground)); white-space:nowrap; overflow:hidden; transition:opacity 0.2s ease; }\
129
+ .jux-sidebar-divider { height:1px; background:hsl(var(--border)); margin:4px 8px; }\
130
+ .jux-nav-item { display:flex; align-items:center; border-radius:var(--radius,6px); font-weight:500; color:hsl(var(--foreground)); cursor:pointer; transition:background 0.15s; user-select:none; text-decoration:none; white-space:nowrap; overflow:hidden; }\
131
+ .jux-nav-item:hover { background:hsl(var(--accent)); color:hsl(var(--accent-foreground)); }\
132
+ .jux-nav-item--active { background:hsl(var(--accent)); color:hsl(var(--accent-foreground)); font-weight:600; }\
133
+ .jux-nav-item-icon { text-align:center; flex-shrink:0; }\
134
+ .jux-nav-item-text { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; transition:opacity 0.2s ease; }\
135
+ \
136
+ .jux-sidebar-collapse-btn { width:28px; height:28px; display:flex; align-items:center; justify-content:center; cursor:pointer; border-radius:6px; color:hsl(var(--muted-foreground)); flex-shrink:0; transition:background 0.15s, color 0.15s; margin-left:auto; }\
137
+ .jux-sidebar-collapse-btn:hover { background:hsl(var(--accent)); color:hsl(var(--accent-foreground)); }\
138
+ .jux-sidebar-collapse-btn svg { display:block; }\
139
+ \
140
+ .jux-sidebar--collapsed .jux-sidebar-header { flex-direction:column; align-items:center; gap:4px; padding-left:4px!important; padding-right:4px!important; }\
141
+ .jux-sidebar--collapsed .jux-sidebar-title-text { opacity:0; max-width:0; height:0; overflow:hidden; margin:0; }\
142
+ .jux-sidebar--collapsed .jux-sidebar-label { opacity:0; max-height:0; padding-top:0!important; padding-bottom:0!important; margin:0; }\
143
+ .jux-sidebar--collapsed .jux-sidebar-footer { opacity:0; height:0; padding:0!important; border:none; }\
144
+ .jux-sidebar--collapsed .jux-nav-item-text { opacity:0; width:0; overflow:hidden; }\
145
+ .jux-sidebar--collapsed .jux-nav-item { justify-content:center; gap:0!important; }\
146
+ .jux-sidebar--collapsed .jux-sidebar-collapse-btn { margin-left:0; width:24px; height:24px; }\
147
+ .jux-sidebar--collapsed .jux-sidebar-collapse-btn svg { width:14px; height:14px; }\
148
+ .jux-sidebar--collapsed .jux-sidebar-logo { margin:0; }\
149
+ \
150
+ .jux-sidebar--dense .jux-sidebar-header { padding:' + DENSITY.dense.headerPad + '; }\
151
+ .jux-sidebar--dense .jux-sidebar-label { padding:' + DENSITY.dense.labelPad + '; }\
152
+ .jux-sidebar--dense .jux-sidebar-footer { padding:' + DENSITY.dense.footerPad + '; }\
153
+ .jux-sidebar--dense .jux-nav-item { padding:' + DENSITY.dense.itemPad + '; margin:' + DENSITY.dense.gap + ' 0; font-size:' + DENSITY.dense.fontSize + '; gap:8px; }\
154
+ .jux-sidebar--dense .jux-nav-item-icon { width:16px; font-size:' + DENSITY.dense.iconSize + '; }\
155
+ \
156
+ .jux-sidebar--normal .jux-sidebar-header { padding:' + DENSITY.normal.headerPad + '; }\
157
+ .jux-sidebar--normal .jux-sidebar-label { padding:' + DENSITY.normal.labelPad + '; }\
158
+ .jux-sidebar--normal .jux-sidebar-footer { padding:' + DENSITY.normal.footerPad + '; }\
159
+ .jux-sidebar--normal .jux-nav-item { padding:' + DENSITY.normal.itemPad + '; margin:' + DENSITY.normal.gap + ' 0; font-size:' + DENSITY.normal.fontSize + '; gap:10px; }\
160
+ .jux-sidebar--normal .jux-nav-item-icon { width:18px; font-size:' + DENSITY.normal.iconSize + '; }\
161
+ \
162
+ .jux-sidebar--inflated .jux-sidebar-header { padding:' + DENSITY.inflated.headerPad + '; }\
163
+ .jux-sidebar--inflated .jux-sidebar-label { padding:' + DENSITY.inflated.labelPad + '; }\
164
+ .jux-sidebar--inflated .jux-sidebar-footer { padding:' + DENSITY.inflated.footerPad + '; }\
165
+ .jux-sidebar--inflated .jux-nav-item { padding:' + DENSITY.inflated.itemPad + '; margin:' + DENSITY.inflated.gap + ' 0; font-size:' + DENSITY.inflated.fontSize + '; gap:12px; }\
166
+ .jux-sidebar--inflated .jux-nav-item-icon { width:20px; font-size:' + DENSITY.inflated.iconSize + '; }\
167
+ ');
168
+ }
169
+
170
+ var api = {
171
+ id: id,
172
+ title: function (val) { _title = val; return api; },
173
+ logo: function (val) { _logo = val; return api; },
174
+ footer: function (val) {
175
+ _footer = val;
176
+ if (_rendered) renderFooter();
177
+ return api;
178
+ },
179
+ width: function (val) { _width = val; return api; },
180
+ collapsedWidth: function (val) { _collapsedWidth = val; return api; },
181
+ target: function (val) { _target = val; return api; },
182
+ density: function (val) { _density = val; return api; },
183
+ collapsible: function (val) { _collapsible = val !== undefined ? val : true; return api; },
184
+
185
+ section: function (label, items) {
186
+ renderSection({ label: label, items: (items || []).map(normalizeItem) });
187
+ return api;
188
+ },
189
+
190
+ items: function (items) {
191
+ renderSection({ items: (items || []).map(normalizeItem) });
192
+ return api;
193
+ },
194
+
195
+ render: function () {
196
+ ensureShell();
197
+ renderFooter();
198
+ if (_collapsible) {
199
+ pageState.__watch(function () {
200
+ if (pageState[id + '-collapse-btn'].click) {
201
+ api.toggleCollapse();
202
+ }
203
+ });
204
+ }
205
+ return api;
206
+ },
207
+
208
+ addSection: function (label, items) { return api.section(label, items); },
209
+ setTitle: function (val) { pageState[id + '-title'].content = val; return api; },
210
+ setFooter: function (val) { pageState[id + '-footer'].content = val; return api; },
211
+ show: function () { pageState[id].visible = true; return api; },
212
+ hide: function () { pageState[id].visible = false; return api; },
213
+ toggle: function () { pageState[id].visible = !pageState[id].visible; return api; },
214
+ collapse: function () { applyCollapsed(true); return api; },
215
+ expand: function () { applyCollapsed(false); return api; },
216
+ toggleCollapse: function () { applyCollapsed(!_collapsed); return api; },
217
+ isCollapsed: function () { return _collapsed; },
218
+ getElement: function () { return document.getElementById(id); }
219
+ };
220
+
221
+ if (sections && Array.isArray(sections) && sections.length > 0) {
222
+ var normalized = normalizeSections(sections);
223
+ normalized.forEach(renderSection);
224
+ renderFooter();
225
+ if (_collapsible) {
226
+ pageState.__watch(function () {
227
+ if (pageState[id + '-collapse-btn'].click) {
228
+ api.toggleCollapse();
229
+ }
230
+ });
231
+ }
232
+ }
233
+
234
+ return api;
235
+ }
@@ -0,0 +1,7 @@
1
+ import { jux } from 'juxscript';
2
+ import { juxSidebar } from './index.jux';
3
+ const allRoutes = jux.routes.all();
4
+
5
+ juxSidebar('my-sidebar', allRoutes,
6
+ { title: 'Simple App', logo: 'J', footer: 'v1.0', density: 'inflated', collapsible: true });
7
+