1
0
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:
codecalm
2026-08-04 01:03:31 +02:00
parent f7dd7db8b4
commit cb46c8837e
6 changed files with 155 additions and 13 deletions
+2 -2
View File
@@ -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"
+6
View File
@@ -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
+125
View File
@@ -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)
})
+3
View File
@@ -4,3 +4,6 @@ dist/
# public/ is fully generated by .build/copy-assets.ts
public/
# generated by `pnpm run capture`
captures/
+15 -10
View File
@@ -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>
+4 -1
View File
@@ -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"
}