strata-css 1.0.4 → 1.1.0
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 +112 -9
- package/bin/strata.js +310 -115
- package/package.json +22 -12
- package/src/components/modules/chart/src/chart.ts +1131 -0
- package/src/components/modules/chart/src/three-global.d.ts +188 -0
- package/src/components/modules/chart/tsconfig.json +19 -0
- package/src/components/modules/init.js +7 -0
- package/src/components/strata.manifest.js +19 -15
- package/src/layers/base.js +573 -571
- package/src/registry/registry.js +2951 -2897
- package/src/components/modules/modal.js +0 -123
- package/src/components/modules/skeleton.js +0 -334
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
**A modern CSS framework combining Bootstrap's component architecture with Tailwind's JIT processing.**
|
|
6
6
|
|
|
7
7
|
[](LICENSE)
|
|
8
|
-
[]()
|
|
9
9
|
[]()
|
|
10
10
|
[](https://www.npmjs.com/package/strata-css)
|
|
11
11
|
[]()
|
|
@@ -41,14 +41,13 @@ Strata is an open source CSS framework that takes the best from Bootstrap and Ta
|
|
|
41
41
|
|
|
42
42
|
## Benchmarks
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
| Metric | Strata | Tailwind |
|
|
44
|
+
| Metric | Strata | Tailwind CSS 3 |
|
|
47
45
|
|---|---|---|
|
|
48
|
-
| Cold build average |
|
|
49
|
-
| Cold build median |
|
|
50
|
-
|
|
|
51
|
-
|
|
46
|
+
| Cold build average | 3.82ms | 7.21ms |
|
|
47
|
+
| Cold build median | 3.78ms | 4.55ms |
|
|
48
|
+
| Cold build p95 | 4.40ms | 6.12ms |
|
|
49
|
+
|
|
50
|
+
> Tailwind figures are official watch-mode reference numbers. Strata numbers are from a cold build with cache invalidated on every run — the most conservative possible measurement. Warm rebuilds (the common case in development) are significantly faster as unchanged output is returned from cache with zero reprocessing.
|
|
52
51
|
|
|
53
52
|
Results generated via `npm run benchmark`. See [`benchmark/`](./benchmark/) for the reproducible script.
|
|
54
53
|
|
|
@@ -75,9 +74,11 @@ npm install strata-css
|
|
|
75
74
|
### Scaffold a new project
|
|
76
75
|
|
|
77
76
|
```bash
|
|
78
|
-
npx strata init
|
|
77
|
+
npx strata-css init
|
|
79
78
|
```
|
|
80
79
|
|
|
80
|
+
> **Note:** Use `strata-css` (with the hyphen), not `strata` — there is an unrelated npm package called `strata` that will be picked up instead.
|
|
81
|
+
|
|
81
82
|
This creates:
|
|
82
83
|
```
|
|
83
84
|
strata.config.js ← configuration
|
|
@@ -376,6 +377,45 @@ element.setAttribute('data-st-collapsed', 'true')
|
|
|
376
377
|
|
|
377
378
|
---
|
|
378
379
|
|
|
380
|
+
## Standalone Packages
|
|
381
|
+
|
|
382
|
+
All Strata plugins are available as independent packages. Use them without Strata, or use them with Strata — the API is identical either way.
|
|
383
|
+
|
|
384
|
+
| Package | Standalone global | With Strata | Install |
|
|
385
|
+
|---|---|---|---|
|
|
386
|
+
| `@strata-css/skeleton-loader` | `SkeletonLoader` | `Strata.skeleton` | `npm i @strata-css/skeleton-loader` |
|
|
387
|
+
| `@strata-css/modal` | `StrataModal` | `Strata.Modal` | `npm i @strata-css/modal` |
|
|
388
|
+
| `@strata-css/chart` | `StrataChart` | `Strata.Chart` | `npm i @strata-css/chart` |
|
|
389
|
+
|
|
390
|
+
### How detection works
|
|
391
|
+
|
|
392
|
+
When `strata.components.js` is loaded it sets `data-strata` on `<html>`. Each plugin checks for this at runtime and registers under the Strata namespace if present, or its own standalone global if not. **No configuration required from you** — it is automatic.
|
|
393
|
+
|
|
394
|
+
### Standalone usage (no Strata)
|
|
395
|
+
|
|
396
|
+
```html
|
|
397
|
+
<!-- Skeleton -->
|
|
398
|
+
<link rel="stylesheet" href="node_modules/@strata-css/skeleton-loader/skeleton-loader.css">
|
|
399
|
+
<script src="node_modules/@strata-css/skeleton-loader/skeleton-loader.js"></script>
|
|
400
|
+
<script>SkeletonLoader.init('.card')</script>
|
|
401
|
+
|
|
402
|
+
<!-- Modal -->
|
|
403
|
+
<link rel="stylesheet" href="node_modules/@strata-css/modal/modal.css">
|
|
404
|
+
<script src="node_modules/@strata-css/modal/modal.js"></script>
|
|
405
|
+
<script>StrataModal.open('#myModal')</script>
|
|
406
|
+
|
|
407
|
+
<!-- Chart (requires Three.js) -->
|
|
408
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
|
409
|
+
<script src="node_modules/@strata-css/chart/chart.js"></script>
|
|
410
|
+
<script>StrataChart.create('#myChart', { type: 'bar', data: [...] })</script>
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Migrating from standalone to Strata
|
|
414
|
+
|
|
415
|
+
If you installed a standalone package first and later add Strata, **no code changes are required.** Strata's presence is detected automatically and the plugin re-registers under the Strata namespace. Your existing markup and JS calls continue to work.
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
379
419
|
## Skeleton Loader
|
|
380
420
|
|
|
381
421
|
Skeleton loading shows animated shimmer placeholders while content is fetching — preventing layout shift and giving users instant visual feedback that something is coming.
|
|
@@ -498,6 +538,69 @@ A card that shimmers while data loads, then transitions to real content:
|
|
|
498
538
|
|
|
499
539
|
---
|
|
500
540
|
|
|
541
|
+
## Modal
|
|
542
|
+
|
|
543
|
+
Strata's modal is attribute-driven. Open and close modals via `data-st-*` attributes or the JS API — no class toggling.
|
|
544
|
+
|
|
545
|
+
### Basic usage
|
|
546
|
+
|
|
547
|
+
```html
|
|
548
|
+
<!-- Trigger -->
|
|
549
|
+
<button data-st-toggle="modal" data-st-target="#myModal">Open Modal</button>
|
|
550
|
+
|
|
551
|
+
<!-- Modal -->
|
|
552
|
+
<div class="modal" id="myModal" aria-hidden="true">
|
|
553
|
+
<div class="modal-dialog">
|
|
554
|
+
<div class="modal-content">
|
|
555
|
+
<div class="modal-header">
|
|
556
|
+
<h5 class="modal-title">Title</h5>
|
|
557
|
+
<button data-st-dismiss="modal">×</button>
|
|
558
|
+
</div>
|
|
559
|
+
<div class="modal-body">
|
|
560
|
+
<p>Modal content here.</p>
|
|
561
|
+
</div>
|
|
562
|
+
<div class="modal-footer">
|
|
563
|
+
<button class="btn-secondary" data-st-dismiss="modal">Close</button>
|
|
564
|
+
<button class="btn-primary">Save</button>
|
|
565
|
+
</div>
|
|
566
|
+
</div>
|
|
567
|
+
</div>
|
|
568
|
+
</div>
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### JS API
|
|
572
|
+
|
|
573
|
+
```js
|
|
574
|
+
Strata.Modal.open('#myModal') // open by selector or element
|
|
575
|
+
Strata.Modal.close() // close current modal
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Static backdrop
|
|
579
|
+
|
|
580
|
+
```html
|
|
581
|
+
<div class="modal" data-st-backdrop="static" ...>
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
Clicking outside the modal shakes it instead of closing it.
|
|
585
|
+
|
|
586
|
+
### Size variants
|
|
587
|
+
|
|
588
|
+
```html
|
|
589
|
+
<div class="modal modal-sm"> <!-- 300px -->
|
|
590
|
+
<div class="modal modal-lg"> <!-- 800px -->
|
|
591
|
+
<div class="modal modal-xl"> <!-- 1140px -->
|
|
592
|
+
<div class="modal modal-fullscreen">
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
### Events
|
|
596
|
+
|
|
597
|
+
```js
|
|
598
|
+
document.addEventListener('st:modal:open', e => console.log('opened', e.detail.modal))
|
|
599
|
+
document.addEventListener('st:modal:close', e => console.log('closed', e.detail.modal))
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
501
604
|
## Custom CSS
|
|
502
605
|
|
|
503
606
|
Strata uses CSS `@layer` internally. Any CSS you write outside a layer automatically wins over Strata styles.
|
package/bin/strata.js
CHANGED
|
@@ -79,12 +79,21 @@ async function build(cssMinify = false, jsMinify = true) {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
// Bundle + optionally minify JS components
|
|
82
|
+
// Load order: init.js (sets data-strata) → packages (detect Strata, register accordingly)
|
|
82
83
|
const componentsDir = path.join(__dirname, '..', 'src', 'components', 'modules')
|
|
83
|
-
const
|
|
84
|
+
const pkgDir = path.join(__dirname, '..', 'packages')
|
|
85
|
+
const packageFiles = [
|
|
86
|
+
path.join(pkgDir, 'modal', 'modal.js'),
|
|
87
|
+
path.join(pkgDir, 'skeleton-loader','skeleton-loader.js'),
|
|
88
|
+
path.join(pkgDir, 'chart', 'chart.js'),
|
|
89
|
+
]
|
|
90
|
+
const jsDest = path.join(path.dirname(outputFile), 'strata.components.js')
|
|
84
91
|
if (fs.existsSync(componentsDir)) {
|
|
85
92
|
const files = fs.readdirSync(componentsDir).filter(f => f.endsWith('.js')).sort()
|
|
86
93
|
const banner = `/*! Strata Components — built ${new Date().toISOString().slice(0,10)} */\n`
|
|
87
|
-
const
|
|
94
|
+
const parts = files.map(f => fs.readFileSync(path.join(componentsDir, f), 'utf8'))
|
|
95
|
+
packageFiles.forEach(p => { if (fs.existsSync(p)) parts.push(fs.readFileSync(p, 'utf8')) })
|
|
96
|
+
const raw = parts.join('\n')
|
|
88
97
|
const output = jsMinify ? banner + minifyJS(raw) : banner + raw
|
|
89
98
|
fs.mkdirSync(path.dirname(jsDest), { recursive: true })
|
|
90
99
|
fs.writeFileSync(jsDest, output)
|
|
@@ -166,71 +175,147 @@ function detectFramework(cwd) {
|
|
|
166
175
|
return 'generic'
|
|
167
176
|
}
|
|
168
177
|
|
|
169
|
-
// ─── Init
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
178
|
+
// ─── Init helpers ─────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
const PACKAGES = [
|
|
181
|
+
{ name: '@strata-css/modal', label: 'modal — attribute-driven modal dialogs' },
|
|
182
|
+
{ name: '@strata-css/skeleton-loader',label: 'skeleton-loader — shimmer loading placeholders' },
|
|
183
|
+
{ name: '@strata-css/chart', label: 'chart — Three.js data visualisations' },
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
const FRAMEWORK_DEV = {
|
|
187
|
+
'astro': 'astro dev',
|
|
188
|
+
'laravel': 'vite',
|
|
189
|
+
'next': 'next dev',
|
|
190
|
+
'sveltekit': 'vite dev',
|
|
191
|
+
'nuxt': 'nuxt dev',
|
|
192
|
+
'react-vite': 'vite',
|
|
193
|
+
'vue-vite': 'vite',
|
|
194
|
+
'generic': 'npm start'
|
|
195
|
+
}
|
|
174
196
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
197
|
+
const FRAMEWORK_BUILD = {
|
|
198
|
+
'astro': 'astro build',
|
|
199
|
+
'laravel': 'vite build',
|
|
200
|
+
'next': 'next build',
|
|
201
|
+
'sveltekit': 'vite build',
|
|
202
|
+
'nuxt': 'nuxt build',
|
|
203
|
+
'react-vite': 'vite build',
|
|
204
|
+
'vue-vite': 'vite build',
|
|
205
|
+
'generic': 'npm run build'
|
|
206
|
+
}
|
|
185
207
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
208
|
+
const FRAMEWORK_LAYOUT = {
|
|
209
|
+
'astro': 'src/layouts/Layout.astro',
|
|
210
|
+
'laravel': 'resources/views/layouts/app.blade.php',
|
|
211
|
+
'next': 'src/app/layout.tsx',
|
|
212
|
+
'sveltekit': 'src/routes/+layout.svelte',
|
|
213
|
+
'nuxt': 'layouts/default.vue',
|
|
214
|
+
'react-vite': 'index.html',
|
|
215
|
+
'vue-vite': 'index.html',
|
|
216
|
+
'generic': 'index.html'
|
|
217
|
+
}
|
|
196
218
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
219
|
+
function askChoice(rl, question, count, defaultChoice = 1) {
|
|
220
|
+
return new Promise(resolve => {
|
|
221
|
+
rl.question(`${question} [${defaultChoice}]: `, answer => {
|
|
222
|
+
const n = parseInt(answer.trim(), 10)
|
|
223
|
+
if (!answer.trim()) return resolve(defaultChoice)
|
|
224
|
+
if (n >= 1 && n <= count) return resolve(n)
|
|
225
|
+
resolve(defaultChoice)
|
|
226
|
+
})
|
|
200
227
|
})
|
|
228
|
+
}
|
|
201
229
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
230
|
+
async function askCheckbox(rl, items) {
|
|
231
|
+
const selected = new Set()
|
|
232
|
+
const render = () => {
|
|
233
|
+
console.log('')
|
|
234
|
+
items.forEach((item, i) => {
|
|
235
|
+
const tick = selected.has(i) ? 'x' : ' '
|
|
236
|
+
console.log(` [${tick}] ${i + 1} ${item.label}`)
|
|
237
|
+
})
|
|
238
|
+
console.log('')
|
|
239
|
+
}
|
|
240
|
+
render()
|
|
241
|
+
while (true) {
|
|
242
|
+
const answer = await ask(rl, ' Toggle number(s) or press Enter to confirm: ')
|
|
243
|
+
if (!answer.trim()) break
|
|
244
|
+
answer.trim().split(/[\s,]+/).forEach(token => {
|
|
245
|
+
const n = parseInt(token, 10) - 1
|
|
246
|
+
if (n >= 0 && n < items.length) {
|
|
247
|
+
if (selected.has(n)) selected.delete(n)
|
|
248
|
+
else selected.add(n)
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
render()
|
|
252
|
+
}
|
|
253
|
+
return Array.from(selected).map(i => items[i])
|
|
254
|
+
}
|
|
212
255
|
|
|
213
|
-
|
|
214
|
-
|
|
256
|
+
function packageUsageSnippet(pkgName) {
|
|
257
|
+
if (pkgName === '@strata-css/modal') return `
|
|
258
|
+
<!-- Modal usage (standalone) -->
|
|
259
|
+
<link rel="stylesheet" href="node_modules/@strata-css/modal/modal.css">
|
|
260
|
+
<script src="node_modules/@strata-css/modal/modal.js"></script>
|
|
261
|
+
|
|
262
|
+
<button data-st-toggle="modal" data-st-target="#myModal">Open</button>
|
|
263
|
+
<div class="modal" id="myModal" aria-hidden="true">
|
|
264
|
+
<div class="modal-dialog"><div class="modal-content">
|
|
265
|
+
<div class="modal-header"><h5 class="modal-title">Title</h5></div>
|
|
266
|
+
<div class="modal-body"><p>Content here.</p></div>
|
|
267
|
+
<div class="modal-footer">
|
|
268
|
+
<button class="btn-secondary" data-st-dismiss="modal">Close</button>
|
|
269
|
+
</div>
|
|
270
|
+
</div></div>
|
|
271
|
+
</div>`
|
|
272
|
+
|
|
273
|
+
if (pkgName === '@strata-css/skeleton-loader') return `
|
|
274
|
+
<!-- Skeleton loader usage (standalone) -->
|
|
275
|
+
<link rel="stylesheet" href="node_modules/@strata-css/skeleton-loader/skeleton-loader.css">
|
|
276
|
+
<script src="node_modules/@strata-css/skeleton-loader/skeleton-loader.js"></script>
|
|
277
|
+
|
|
278
|
+
<div class="card" data-st-skeleton="true">
|
|
279
|
+
<div class="card-body"><p>Content loading...</p></div>
|
|
280
|
+
</div>
|
|
281
|
+
<script>
|
|
282
|
+
SkeletonLoader.init()
|
|
283
|
+
fetchData().then(() => SkeletonLoader.reveal())
|
|
284
|
+
</script>`
|
|
285
|
+
|
|
286
|
+
if (pkgName === '@strata-css/chart') return `
|
|
287
|
+
<!-- Chart usage (standalone, requires Three.js) -->
|
|
288
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
|
289
|
+
<script src="node_modules/@strata-css/chart/chart.js"></script>
|
|
290
|
+
|
|
291
|
+
<canvas id="myChart"></canvas>
|
|
292
|
+
<script>
|
|
293
|
+
StrataChart.create('#myChart', {
|
|
294
|
+
type: 'bar',
|
|
295
|
+
data: [{ label: 'Jan', value: 42 }, { label: 'Feb', value: 67 }]
|
|
296
|
+
})
|
|
297
|
+
</script>`
|
|
215
298
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
console.log(' Examples:')
|
|
219
|
-
console.log(' src/layouts/Layout.astro')
|
|
220
|
-
console.log(' resources/views/layouts/app.blade.php')
|
|
221
|
-
console.log(' src/App.jsx')
|
|
222
|
-
console.log(' index.html')
|
|
223
|
-
const htmlFile = await ask(rl,
|
|
224
|
-
' Path (leave blank to skip): ')
|
|
299
|
+
return ''
|
|
300
|
+
}
|
|
225
301
|
|
|
226
|
-
|
|
227
|
-
|
|
302
|
+
function buildContentGlob(framework) {
|
|
303
|
+
const base = './src/**/*.{html,jsx,tsx,vue,astro,svelte,js,ts}'
|
|
304
|
+
if (framework === 'laravel') {
|
|
305
|
+
return [base, './resources/views/**/*.blade.php']
|
|
306
|
+
}
|
|
307
|
+
return [base]
|
|
308
|
+
}
|
|
228
309
|
|
|
229
|
-
|
|
310
|
+
function scaffoldStrataCore(cwd, isESM, output, framework) {
|
|
230
311
|
const configFile = isESM ? 'strata.config.cjs' : 'strata.config.js'
|
|
231
312
|
const postcssFile = isESM ? 'postcss.config.cjs' : 'postcss.config.js'
|
|
313
|
+
const globs = buildContentGlob(framework)
|
|
314
|
+
const globStr = globs.length === 1
|
|
315
|
+
? `["${globs[0]}"]`
|
|
316
|
+
: `[\n "${globs.join('",\n "')}"\n ]`
|
|
232
317
|
|
|
233
|
-
const configContent = `module.exports = {\n content:
|
|
318
|
+
const configContent = `module.exports = {\n content: ${globStr},\n input: "./strata.css",\n output: "${output}"\n}\n`
|
|
234
319
|
const postcssContent = `module.exports = {\n plugins: [\n require('strata-css'),\n require('autoprefixer')\n ]\n}\n`
|
|
235
320
|
const strataCssContent = `@strata base;\n@strata components;\n@strata utilities;\n`
|
|
236
321
|
|
|
@@ -242,79 +327,189 @@ async function init() {
|
|
|
242
327
|
console.log(` ✔ Created: strata.css`)
|
|
243
328
|
console.log(` ✔ Created: ${postcssFile}`)
|
|
244
329
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
330
|
+
return { configFile, postcssFile }
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function updatePackageScripts(cwd, framework, installConcurrently) {
|
|
334
|
+
const strataWatch = 'node node_modules/strata-css/bin/strata.js --watch'
|
|
335
|
+
const strataBuild = 'node node_modules/strata-css/bin/strata.js --build'
|
|
336
|
+
const devCmd = FRAMEWORK_DEV[framework] || 'npm start'
|
|
337
|
+
const buildCmd = FRAMEWORK_BUILD[framework] || 'npm run build'
|
|
338
|
+
|
|
339
|
+
const pkgPath = path.resolve(cwd, 'package.json')
|
|
340
|
+
let pkg
|
|
341
|
+
try { pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) } catch { return }
|
|
342
|
+
if (!pkg.scripts) pkg.scripts = {}
|
|
343
|
+
|
|
344
|
+
const hasConcurrently = installConcurrently ||
|
|
345
|
+
pkg.dependencies?.['concurrently'] ||
|
|
346
|
+
pkg.devDependencies?.['concurrently']
|
|
347
|
+
|
|
348
|
+
pkg.scripts.dev = hasConcurrently
|
|
349
|
+
? `concurrently "${strataWatch}" "${devCmd}"`
|
|
350
|
+
: `${strataWatch} & ${devCmd}`
|
|
351
|
+
pkg.scripts.build = `${strataBuild} && ${buildCmd}`
|
|
352
|
+
pkg.scripts['strata:watch'] = strataWatch
|
|
353
|
+
pkg.scripts['strata:build'] = strataBuild
|
|
354
|
+
|
|
355
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2))
|
|
356
|
+
console.log(' ✔ Updated: package.json scripts')
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function injectIntoLayout(cwd, htmlFile, outputCssPath, injectScript) {
|
|
360
|
+
const htmlPath = path.resolve(cwd, htmlFile.trim())
|
|
361
|
+
if (!fs.existsSync(htmlPath)) {
|
|
362
|
+
console.log(` ✗ File not found: ${htmlFile.trim()} — skipped`)
|
|
363
|
+
return
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
let content = fs.readFileSync(htmlPath, 'utf8')
|
|
367
|
+
const cssHref = outputCssPath.replace('./', '/')
|
|
368
|
+
const cssLink = `\t\t<link rel="stylesheet" href="${cssHref}">`
|
|
369
|
+
const scriptTag = `\t\t<script src="${cssHref.replace('strata.output.css', 'strata.components.js')}"></script>`
|
|
370
|
+
const themeAttr = 'data-st-theme="light"'
|
|
371
|
+
|
|
372
|
+
if (!content.includes('strata.output.css')) {
|
|
373
|
+
content = content.replace('</head>', `${cssLink}\n\t</head>`)
|
|
253
374
|
}
|
|
375
|
+
if (injectScript && !content.includes('strata.components.js')) {
|
|
376
|
+
content = content.replace('</body>', `${scriptTag}\n\t</body>`)
|
|
377
|
+
}
|
|
378
|
+
if (!content.includes('data-st-theme')) {
|
|
379
|
+
content = content.replace('<html', `<html ${themeAttr}`)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
fs.writeFileSync(htmlPath, content)
|
|
383
|
+
console.log(` ✔ Updated: ${htmlFile.trim()}`)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ─── Init ─────────────────────────────────────────────────────────────
|
|
387
|
+
async function init() {
|
|
388
|
+
const isESM = isESMProject(cwd)
|
|
389
|
+
const framework = detectFramework(cwd)
|
|
390
|
+
const output = detectOutputPath(cwd)
|
|
391
|
+
const exec = require('child_process').execSync
|
|
392
|
+
|
|
393
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
394
|
+
|
|
395
|
+
console.log('')
|
|
396
|
+
console.log(' strata Universal installer')
|
|
397
|
+
console.log('')
|
|
398
|
+
console.log(` ◼ Framework detected : ${framework}`)
|
|
399
|
+
console.log(` ◼ Project type : ${isESM ? 'ESM' : 'CommonJS'}`)
|
|
400
|
+
console.log(` ◼ Output path : ${output}`)
|
|
401
|
+
console.log('')
|
|
402
|
+
console.log(' install What would you like to install?')
|
|
403
|
+
console.log('')
|
|
404
|
+
console.log(' 1 Strata core only')
|
|
405
|
+
console.log(' 2 Strata + all packages (modal, skeleton-loader, chart)')
|
|
406
|
+
console.log(' 3 Strata + select packages')
|
|
407
|
+
console.log(' 4 Single package (no Strata core)')
|
|
408
|
+
console.log(' 5 Multiple packages (no Strata core)')
|
|
409
|
+
console.log('')
|
|
254
410
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
console.log('
|
|
411
|
+
const installChoice = await askChoice(rl, ' Choice', 5, 1)
|
|
412
|
+
console.log('')
|
|
413
|
+
|
|
414
|
+
const withCore = installChoice <= 3
|
|
415
|
+
const withPackages = installChoice >= 2
|
|
416
|
+
|
|
417
|
+
// ── Determine which packages to install ──────────────────────────────
|
|
418
|
+
let selectedPackages = []
|
|
419
|
+
|
|
420
|
+
if (installChoice === 2) {
|
|
421
|
+
selectedPackages = PACKAGES.slice()
|
|
422
|
+
} else if (installChoice === 3) {
|
|
423
|
+
console.log(' packages Which packages? (type numbers to toggle, Enter to confirm)')
|
|
424
|
+
selectedPackages = await askCheckbox(rl, PACKAGES)
|
|
425
|
+
} else if (installChoice === 4) {
|
|
426
|
+
console.log(' package Which package?')
|
|
427
|
+
console.log('')
|
|
428
|
+
PACKAGES.forEach((p, i) => console.log(` ${i + 1} ${p.label}`))
|
|
429
|
+
console.log('')
|
|
430
|
+
const n = await askChoice(rl, ' Choice', PACKAGES.length, 1)
|
|
431
|
+
selectedPackages = [PACKAGES[n - 1]]
|
|
432
|
+
console.log('')
|
|
433
|
+
} else if (installChoice === 5) {
|
|
434
|
+
console.log(' packages Which packages? (type numbers to toggle, Enter to confirm)')
|
|
435
|
+
selectedPackages = await askCheckbox(rl, PACKAGES)
|
|
279
436
|
}
|
|
280
437
|
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
438
|
+
// ── Strata core setup questions ──────────────────────────────────────
|
|
439
|
+
let installConcurrently = false
|
|
440
|
+
let updateScripts = false
|
|
441
|
+
let htmlFile = ''
|
|
442
|
+
|
|
443
|
+
if (withCore) {
|
|
444
|
+
installConcurrently = await askYesNo(rl,
|
|
445
|
+
' deps Install concurrently for clean parallel dev?')
|
|
446
|
+
|
|
447
|
+
updateScripts = await askYesNo(rl,
|
|
448
|
+
' scripts Auto-update package.json dev and build scripts?')
|
|
449
|
+
|
|
450
|
+
const defaultLayout = FRAMEWORK_LAYOUT[framework] || 'index.html'
|
|
451
|
+
console.log('')
|
|
452
|
+
console.log(` html Where is your main layout file?`)
|
|
453
|
+
console.log(` Default for ${framework}: ${defaultLayout}`)
|
|
454
|
+
htmlFile = await ask(rl, ` Path (leave blank for default): `)
|
|
455
|
+
if (!htmlFile.trim()) htmlFile = defaultLayout
|
|
456
|
+
console.log('')
|
|
457
|
+
}
|
|
288
458
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
459
|
+
rl.close()
|
|
460
|
+
|
|
461
|
+
// ── Execute: Strata core scaffold ─────────────────────────────────────
|
|
462
|
+
if (withCore) {
|
|
463
|
+
scaffoldStrataCore(cwd, isESM, output, framework)
|
|
295
464
|
|
|
296
|
-
|
|
297
|
-
console.log(
|
|
298
|
-
|
|
299
|
-
console.log(
|
|
465
|
+
if (installConcurrently) {
|
|
466
|
+
console.log(' ◼ Installing concurrently...')
|
|
467
|
+
exec('npm install --save-dev concurrently', { stdio: 'inherit', cwd })
|
|
468
|
+
console.log(' ✔ concurrently installed')
|
|
300
469
|
}
|
|
470
|
+
|
|
471
|
+
if (updateScripts) updatePackageScripts(cwd, framework, installConcurrently)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// ── Execute: install selected packages ───────────────────────────────
|
|
475
|
+
if (selectedPackages.length > 0) {
|
|
476
|
+
const pkgNames = selectedPackages.map(p => p.name).join(' ')
|
|
477
|
+
console.log(` ◼ Installing ${pkgNames}...`)
|
|
478
|
+
exec(`npm install ${pkgNames}`, { stdio: 'inherit', cwd })
|
|
479
|
+
console.log(` ✔ Packages installed`)
|
|
301
480
|
}
|
|
302
481
|
|
|
303
|
-
//
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
path.resolve(cwd, output.replace('./', '')),
|
|
308
|
-
{ cwd }
|
|
309
|
-
)
|
|
310
|
-
console.log(' ✔ Initial build complete')
|
|
482
|
+
// ── Execute: inject into layout ──────────────────────────────────────
|
|
483
|
+
if (withCore && htmlFile) {
|
|
484
|
+
injectIntoLayout(cwd, htmlFile, output, withPackages && selectedPackages.length > 0)
|
|
485
|
+
}
|
|
311
486
|
|
|
487
|
+
// ── Execute: initial CSS build ───────────────────────────────────────
|
|
488
|
+
if (withCore) {
|
|
489
|
+
console.log(' ◼ Running initial build...')
|
|
490
|
+
await build(false, false)
|
|
491
|
+
console.log(' ✔ Initial build complete')
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// ── Done ─────────────────────────────────────────────────────────────
|
|
312
495
|
console.log('')
|
|
313
496
|
console.log(' strata Setup complete!')
|
|
314
497
|
console.log('')
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
498
|
+
|
|
499
|
+
if (withCore) {
|
|
500
|
+
console.log(' next Run npm run dev to start.')
|
|
501
|
+
console.log('')
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (!withCore && selectedPackages.length > 0) {
|
|
505
|
+
console.log(' usage Copy the snippets below into your project:')
|
|
506
|
+
selectedPackages.forEach(p => {
|
|
507
|
+
console.log('')
|
|
508
|
+
console.log(` ── ${p.name} ──`)
|
|
509
|
+
console.log(packageUsageSnippet(p.name))
|
|
510
|
+
})
|
|
511
|
+
console.log('')
|
|
512
|
+
}
|
|
318
513
|
}
|
|
319
514
|
|
|
320
515
|
// ─── Run ──────────────────────────────────────────────────────────────
|