mirror of
https://github.com/tabler/tabler.git
synced 2026-08-05 19:03:18 +04:00
Update package.json and pnpm-lock.yaml for dependency management; enhance .gitignore for captures; modify ScreenshotLayout for clarity and responsiveness
This commit is contained in:
+2
-2
@@ -18,10 +18,10 @@
|
||||
"lint:fix": "pnpm run lint-md-fix && pnpm run format-prettier",
|
||||
"lint-md": "markdownlint --config .markdownlint-docs.json \"docs/pages/**/*.mdx\"",
|
||||
"lint-md-fix": "markdownlint --fix --config .markdownlint-docs.json \"docs/pages/**/*.mdx\"",
|
||||
"lint-prettier": "prettier --check \"core/scss/**/*.scss\" \"core/js/**/*.{js,ts}\" \"core/.build/**/*.{ts,mts,mjs}\" \".build/**/*.ts\" \"preview/scss/**/*.scss\" \"preview/js/**/*.{js,ts}\" \"{preview,docs,screenshots,shared}/**/*.astro\" \"{preview,docs,screenshots}/**/*.{mjs,mts}\" \"shared/**/*.{js,mjs,mts,ts}\" --cache",
|
||||
"lint-prettier": "prettier --check \"core/scss/**/*.scss\" \"core/js/**/*.{js,ts}\" \"core/.build/**/*.{ts,mts,mjs}\" \".build/**/*.ts\" \"screenshots/.build/**/*.ts\" \"preview/scss/**/*.scss\" \"preview/js/**/*.{js,ts}\" \"{preview,docs,screenshots,shared}/**/*.astro\" \"{preview,docs,screenshots}/**/*.{mjs,mts}\" \"shared/**/*.{js,mjs,mts,ts}\" --cache",
|
||||
"lint-scss-vars": "pnpm --filter @tabler/core css-lint",
|
||||
"format": "pnpm run format-prettier && pnpm run reformat-md",
|
||||
"format-prettier": "prettier --write \"core/scss/**/*.scss\" \"core/js/**/*.{js,ts}\" \"core/.build/**/*.{ts,mts,mjs}\" \".build/**/*.ts\" \"preview/scss/**/*.scss\" \"preview/js/**/*.{js,ts}\" \"{preview,docs,screenshots,shared}/**/*.astro\" \"{preview,docs,screenshots}/**/*.{mjs,mts}\" \"shared/**/*.{js,mjs,mts,ts}\" --cache",
|
||||
"format-prettier": "prettier --write \"core/scss/**/*.scss\" \"core/js/**/*.{js,ts}\" \"core/.build/**/*.{ts,mts,mjs}\" \".build/**/*.ts\" \"screenshots/.build/**/*.ts\" \"preview/scss/**/*.scss\" \"preview/js/**/*.{js,ts}\" \"{preview,docs,screenshots,shared}/**/*.astro\" \"{preview,docs,screenshots}/**/*.{mjs,mts}\" \"shared/**/*.{js,mjs,mts,ts}\" --cache",
|
||||
"check": "pnpm run lint && pnpm run type-check",
|
||||
"zip-package": "tsx .build/zip-package.ts",
|
||||
"start": "pnpm dev"
|
||||
|
||||
Generated
+6
@@ -283,6 +283,12 @@ importers:
|
||||
'@astrojs/check':
|
||||
specifier: ^0.9.4
|
||||
version: 0.9.10(prettier-plugin-astro@0.14.1)(prettier@3.9.6)(typescript@5.9.3)
|
||||
'@types/node':
|
||||
specifier: ^26.1.1
|
||||
version: 26.1.1
|
||||
playwright:
|
||||
specifier: 1.62.0
|
||||
version: 1.62.0
|
||||
shx:
|
||||
specifier: ^0.4.0
|
||||
version: 0.4.0
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env node
|
||||
// Serves the built screenshots/dist via `astro preview` and captures a light
|
||||
// and dark (?theme=dark) PNG — at 1x and @2x — of every page's #screenshot
|
||||
// canvas (the full 1024x768 frame — logo, gradient backdrop and fake cursor
|
||||
// included, not just the cropped component card).
|
||||
// Run via `pnpm run capture` (builds first) — pass slugs as args to capture
|
||||
// only specific pages, e.g. `pnpm run capture button badge`.
|
||||
import { chromium, type Page } from 'playwright'
|
||||
import { spawn, type ChildProcess } from 'node:child_process'
|
||||
import { existsSync, mkdirSync, readdirSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const root = path.join(__dirname, '..')
|
||||
const distDir = path.join(root, 'dist')
|
||||
const outDir = path.join(root, 'captures')
|
||||
|
||||
const PORT = Number(process.env.CAPTURE_PORT ?? 4020)
|
||||
const baseUrl = `http://localhost:${PORT}`
|
||||
|
||||
function discoverSlugs(): string[] {
|
||||
if (!existsSync(distDir)) {
|
||||
console.error(`No build found at ${path.relative(process.cwd(), distDir)} — run \`astro build\` first (or use \`pnpm run capture\`).`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const filter = process.argv.slice(2)
|
||||
return readdirSync(distDir)
|
||||
.filter((file) => file.endsWith('.html') && file !== 'index.html')
|
||||
.map((file) => file.replace(/\.html$/, ''))
|
||||
.filter((slug) => filter.length === 0 || filter.includes(slug))
|
||||
.sort()
|
||||
}
|
||||
|
||||
// Spawns the `astro` binary directly (not via `pnpm exec astro`) so `child.pid`
|
||||
// is the actual server process — killing a `pnpm exec` wrapper doesn't reliably
|
||||
// kill the process it launches, which is how earlier runs left zombie preview
|
||||
// servers squatting on the port for subsequent runs to collide with.
|
||||
const astroBin = path.join(root, 'node_modules/.bin/astro')
|
||||
|
||||
function startPreviewServer(): Promise<ChildProcess> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(astroBin, ['preview', '--port', String(PORT)], {
|
||||
cwd: root,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
})
|
||||
|
||||
const timer = setTimeout(() => reject(new Error(`astro preview didn't come up on port ${PORT} within 20s`)), 20_000)
|
||||
|
||||
const onData = (data: Buffer) => {
|
||||
if (data.toString().includes('Local')) {
|
||||
clearTimeout(timer)
|
||||
child.stdout?.off('data', onData)
|
||||
resolve(child)
|
||||
}
|
||||
}
|
||||
child.stdout?.on('data', onData)
|
||||
child.stderr?.on('data', (data) => process.stderr.write(data))
|
||||
child.once('error', reject)
|
||||
child.once('exit', (code) => {
|
||||
if (code !== null && code !== 0) reject(new Error(`astro preview exited with code ${code}`))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function filenameFor(slug: string, theme: 'light' | 'dark', scale: 1 | 2): string {
|
||||
const themeSuffix = theme === 'dark' ? '-dark' : ''
|
||||
const scaleSuffix = scale === 2 ? '@2x' : ''
|
||||
return `${slug}${themeSuffix}${scaleSuffix}.png`
|
||||
}
|
||||
|
||||
async function captureOne(page: Page, slug: string, theme: 'light' | 'dark', scale: 1 | 2) {
|
||||
await page.goto(`${baseUrl}/${slug}?theme=${theme}`, { waitUntil: 'load' })
|
||||
await page.waitForFunction(() => document.documentElement.dataset.screenshotReady === 'true', { timeout: 15_000 })
|
||||
|
||||
const filename = filenameFor(slug, theme, scale)
|
||||
await page.locator('#screenshot').screenshot({ path: path.join(outDir, filename) })
|
||||
console.log(` ✓ ${filename}`)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const slugs = discoverSlugs()
|
||||
if (slugs.length === 0) {
|
||||
console.error('No matching pages found to capture.')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
mkdirSync(outDir, { recursive: true })
|
||||
|
||||
console.log(`Starting preview server on port ${PORT}…`)
|
||||
const server = await startPreviewServer()
|
||||
// Interrupting the script (Ctrl+C) must not leave the preview server behind
|
||||
// squatting on the port for the next run to collide with.
|
||||
const killServer = () => server.kill()
|
||||
process.once('SIGINT', killServer)
|
||||
process.once('SIGTERM', killServer)
|
||||
|
||||
try {
|
||||
const browser = await chromium.launch()
|
||||
const viewport = { width: 1280, height: 800 }
|
||||
// Two pages, not one reused with setViewportSize — deviceScaleFactor is
|
||||
// fixed at context/page creation and can't be changed on an existing page.
|
||||
const page1x = await browser.newPage({ viewport, deviceScaleFactor: 1 })
|
||||
const page2x = await browser.newPage({ viewport, deviceScaleFactor: 2 })
|
||||
|
||||
for (const slug of slugs) {
|
||||
console.log(slug)
|
||||
await captureOne(page1x, slug, 'light', 1)
|
||||
await captureOne(page1x, slug, 'dark', 1)
|
||||
await captureOne(page2x, slug, 'light', 2)
|
||||
await captureOne(page2x, slug, 'dark', 2)
|
||||
}
|
||||
|
||||
await browser.close()
|
||||
console.log(`\n${slugs.length * 4} screenshots written to ${path.relative(process.cwd(), outDir)}/`)
|
||||
} finally {
|
||||
server.kill()
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -4,3 +4,6 @@ dist/
|
||||
|
||||
# public/ is fully generated by .build/copy-assets.ts
|
||||
public/
|
||||
|
||||
# generated by `pnpm run capture`
|
||||
captures/
|
||||
|
||||
@@ -18,7 +18,7 @@ interface Props {
|
||||
rtl?: boolean
|
||||
/** the Tabler logo above the card (+ balancing spacer below it) */
|
||||
showLogo?: boolean
|
||||
/** wrap the component in the elevated .screenshot-card; when false the slot renders bare */
|
||||
/** wrap the component in the elevated .data-screenshot-targetscreenshot-card; when false the slot renders bare */
|
||||
showCard?: boolean
|
||||
/** caps the [data-screenshot-target] width at columns * 320px */
|
||||
columns?: number
|
||||
@@ -78,9 +78,9 @@ const libJsFiles = pageLibEntries.flatMap(([, lib]) => (lib.js ?? []).map((file)
|
||||
padding: 0 4rem;
|
||||
}
|
||||
|
||||
.screenshot-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
.screenshot-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.screenshot-wrapper > .card,
|
||||
.screenshot-card {
|
||||
@@ -153,12 +153,17 @@ const libJsFiles = pageLibEntries.flatMap(([, lib]) => (lib.js ?? []).map((file)
|
||||
<PageScripts />
|
||||
|
||||
<script>
|
||||
// Signals to the screenshot tool that fonts, images and JS-driven
|
||||
// component init (dropdowns, tooltips, ...) are all settled.
|
||||
// window "load" (not just fonts.ready) guarantees the deferred
|
||||
// tabler(.min).js above has already run.
|
||||
const loaded = new Promise((resolve) => window.addEventListener('load', resolve, { once: true }))
|
||||
Promise.all([document.fonts.ready, loaded]).then(() => {
|
||||
// Signals to the screenshot tool that fonts and JS-driven component init
|
||||
// (dropdowns, tooltips, charts via PageScripts, ...) are settled.
|
||||
// DOMContentLoaded — not window "load" — on purpose: deferred scripts
|
||||
// (tabler(.min).js above, and everything PageScripts drained) already run
|
||||
// before DOMContentLoaded per spec, and unlike "load" it doesn't block on
|
||||
// slow/unreachable subresources (e.g. an external chat GIF).
|
||||
const domReady = new Promise((resolve) => {
|
||||
if (document.readyState !== 'loading') resolve(undefined)
|
||||
else document.addEventListener('DOMContentLoaded', () => resolve(undefined), { once: true })
|
||||
})
|
||||
Promise.all([document.fonts.ready, domReady]).then(() => {
|
||||
requestAnimationFrame(() => requestAnimationFrame(() => (document.documentElement.dataset.screenshotReady = 'true')))
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"clean": "shx rm -rf dist public .astro",
|
||||
"capture": "turbo build --filter=@tabler/screenshots && tsx .build/capture.ts",
|
||||
"clean": "shx rm -rf dist public captures/* .astro",
|
||||
"type-check": "astro check"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -20,6 +21,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"@types/node": "^26.1.1",
|
||||
"playwright": "1.62.0",
|
||||
"shx": "^0.4.0",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user