diff --git a/.build/import-icons.ts b/.build/import-icons.ts new file mode 100644 index 000000000..36e4087ab --- /dev/null +++ b/.build/import-icons.ts @@ -0,0 +1,60 @@ +#!/usr/bin/env node +// Regenerates shared/data/icons.json and icons-info.json from the installed +// @tabler/icons package. `pnpm run update:icons` bumps the package first. +// +// Run: pnpm run import:icons +import { readFileSync, writeFileSync } from 'node:fs' +import { join, dirname } from 'node:path' +import { fileURLToPath } from 'node:url' + +interface IconTag { + styles: { + outline?: unknown + filled?: unknown + } +} + +interface IconEntry { + name: string + svg: { + outline: string | null + filled: string | null + } +} + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const repoRoot = join(__dirname, '..') + +const iconsPackage = join(repoRoot, 'node_modules/@tabler/icons') +const iconsTags: Record = JSON.parse(readFileSync(join(iconsPackage, 'icons.json'), 'utf8')) +const { version }: { version: string } = JSON.parse(readFileSync(join(iconsPackage, 'package.json'), 'utf8')) + +const prepareSvgFile = (svg: string): string => { + return svg.replace(/\n/g, '').replace(/>\s+<').replace(/\s+/g, ' ') +} + +const svgList: Record = {} +for (const iconName in iconsTags) { + const iconData = iconsTags[iconName] + svgList[iconName] = { + name: iconName, + svg: { + outline: iconData.styles.outline ? prepareSvgFile(readFileSync(join(iconsPackage, `icons/outline/${iconName}.svg`), 'utf8')) : null, + filled: iconData.styles.filled ? prepareSvgFile(readFileSync(join(iconsPackage, `icons/filled/${iconName}.svg`), 'utf8')) : null, + }, + } +} + +writeFileSync( + join(repoRoot, 'shared/data/icons-info.json'), + JSON.stringify({ + version, + count: Object.values(svgList).reduce((acc, icon) => { + return acc + (icon.svg.outline ? 1 : 0) + (icon.svg.filled ? 1 : 0) + }, 0), + }), +) + +writeFileSync(join(repoRoot, 'shared/data/icons.json'), JSON.stringify(svgList)) + +console.log(`import-icons: imported ${Object.keys(svgList).length} icons from @tabler/icons v${version}`) diff --git a/.build/import-illustrations.ts b/.build/import-illustrations.ts new file mode 100644 index 000000000..03d43d742 --- /dev/null +++ b/.build/import-illustrations.ts @@ -0,0 +1,79 @@ +#!/usr/bin/env node +// Imports illustrations from the tabler/tabler-illustrations repo: the light and +// dark PNGs, the name list and the version. The free inline SVGs in +// shared/data/free-illustrations.json are hand-picked and stay as they are. +// +// Run: pnpm run import:illustrations +import { execFileSync } from 'node:child_process' +import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join, dirname, relative } from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const repoRoot = join(__dirname, '..') +const imageDir = join(repoRoot, 'shared', 'static', 'illustrations') +const dataFile = join(repoRoot, 'shared', 'data', 'illustrations.json') +const infoFile = join(repoRoot, 'shared', 'data', 'illustrations-info.json') + +let cloneDir: string | undefined + +function resolveSourceDir(): string { + const candidates = [process.env.TABLER_ILLUSTRATIONS_DIR, join(repoRoot, '..', 'tabler-illustrations')].filter((dir): dir is string => Boolean(dir)) + for (const dir of candidates) { + if (existsSync(join(dir, 'dist', 'png', 'light'))) return dir + } + cloneDir = mkdtempSync(join(tmpdir(), 'tabler-illustrations-')) + console.log('No local checkout found, cloning tabler/tabler-illustrations…') + execFileSync('git', ['clone', '--depth', '1', 'https://github.com/tabler/tabler-illustrations.git', cloneDir], { stdio: 'inherit' }) + return cloneDir +} + +const pngNames = (dir: string) => + readdirSync(dir) + .filter((file) => file.endsWith('.png')) + .sort() + .map((file) => file.slice(0, -'.png'.length)) + +try { + const sourceDir = resolveSourceDir() + const lightDir = join(sourceDir, 'dist', 'png', 'light') + const darkDir = join(sourceDir, 'dist', 'png', 'dark') + const packageFile = join(sourceDir, 'package.json') + for (const path of [lightDir, darkDir, packageFile]) { + if (!existsSync(path)) throw new Error(`import-illustrations: missing ${path} — not a tabler-illustrations checkout?`) + } + + const illustrations = pngNames(lightDir) + const darkIllustrations = pngNames(darkDir) + if (illustrations.join() !== darkIllustrations.join()) { + const light = new Set(illustrations) + const dark = new Set(darkIllustrations) + const onlyLight = illustrations.filter((name) => !dark.has(name)) + const onlyDark = darkIllustrations.filter((name) => !light.has(name)) + throw new Error(`import-illustrations: light/dark sets differ (only light: ${onlyLight.join(', ') || '—'}; only dark: ${onlyDark.join(', ') || '—'})`) + } + + const previous = existsSync(join(imageDir, 'light')) ? new Set(pngNames(join(imageDir, 'light'))) : new Set() + for (const variant of ['light', 'dark'] as const) { + const targetDir = join(imageDir, variant) + rmSync(targetDir, { recursive: true, force: true }) + mkdirSync(targetDir, { recursive: true }) + for (const name of illustrations) { + copyFileSync(join(variant === 'light' ? lightDir : darkDir, `${name}.png`), join(targetDir, `${name}.png`)) + } + } + + writeFileSync(dataFile, JSON.stringify(illustrations)) + + const { version }: { version: string } = JSON.parse(readFileSync(packageFile, 'utf8')) + writeFileSync(infoFile, JSON.stringify({ version, count: illustrations.length })) + + const added = illustrations.filter((name) => !previous.has(name)) + const removed = [...previous].filter((name) => !illustrations.includes(name)) + console.log(`Imported ${illustrations.length} illustrations v${version} from ${relative(repoRoot, sourceDir)} (${added.length} added, ${removed.length} removed)`) + if (added.length) console.log(` added: ${added.join(', ')}`) + if (removed.length) console.log(` removed: ${removed.join(', ')}`) +} finally { + if (cloneDir) rmSync(cloneDir, { recursive: true, force: true }) +} diff --git a/.build/import-payments.ts b/.build/import-payments.ts index bda50a512..a4e92d1d9 100644 --- a/.build/import-payments.ts +++ b/.build/import-payments.ts @@ -1,16 +1,8 @@ #!/usr/bin/env node -// Imports payment provider assets from the tabler/tabler-payments repo, so the -// payments plugin never drifts from the icon source of truth: -// - core/img/payments/ <- src/light/*.svg + src/dark/*.svg (as *-dark.svg) -// - $payment-providers in core/scss/_variables.scss -// - shared/data/payments.json <- payments.json (name + logo, demo data) +// Imports payment provider assets from the tabler/tabler-payments repo: the light +// and dark SVGs, the $payment-providers list and the demo data. // -// Source checkout resolution order: -// 1. TABLER_PAYMENTS_DIR env var -// 2. ../tabler-payments (sibling checkout) -// 3. fresh shallow clone into a temp dir (the repo is private — needs git auth) -// -// Run: pnpm run import-payments +// Run: pnpm run import:payments // Then: pnpm run generate-tokens — refreshes PaymentProvider in shared/lib/tokens.ts import { execFileSync } from 'node:child_process' import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs' @@ -37,8 +29,7 @@ function resolveSourceDir(): string { return cloneDir } -// Provider order everywhere follows the SVG filename (with extension), so -// 'amazon-pay' sorts before 'amazon' — same as an `ls` of the source dir. +// Sorted by file name, so 'amazon-pay' comes before 'amazon' — as in an `ls`. const svgNames = (dir: string) => readdirSync(dir) .filter((file) => file.endsWith('.svg')) @@ -64,7 +55,6 @@ try { throw new Error(`import-payments: light/dark sets differ (only light: ${onlyLight.join(', ') || '—'}; only dark: ${onlyDark.join(', ') || '—'})`) } - // 1. core/img/payments — clean replace, dark variants get a `-dark` suffix const previous = existsSync(imgDir) ? new Set(svgNames(imgDir).map((name) => name.replace(/-dark$/, ''))) : new Set() rmSync(imgDir, { recursive: true, force: true }) mkdirSync(imgDir, { recursive: true }) @@ -73,14 +63,12 @@ try { copyFileSync(join(darkDir, `${name}.svg`), join(imgDir, `${name}-dark.svg`)) } - // 2. $payment-providers in core/scss/_variables.scss const scss = readFileSync(scssFile, 'utf8') const listPattern = /(\$payment-providers: \(\n)[\s\S]*?(\n\);)/ if (!listPattern.test(scss)) throw new Error(`import-payments: $payment-providers list not found in ${scssFile}`) const listBody = providers.map((name) => ` '${name}'`).join(',\n') writeFileSync(scssFile, scss.replace(listPattern, `$1${listBody}$2`)) - // 3. shared/data/payments.json — demo data keeps its `{ name, logo }` shape const meta = JSON.parse(readFileSync(metaFile, 'utf8')) as { name: string; logo: string }[] const names = new Map(meta.map((entry) => [entry.logo, entry.name])) for (const logo of names.keys()) { diff --git a/.changeset/move-import-scripts-to-root.md b/.changeset/move-import-scripts-to-root.md new file mode 100644 index 000000000..7a1234b50 --- /dev/null +++ b/.changeset/move-import-scripts-to-root.md @@ -0,0 +1,5 @@ +--- +"@tabler/preview": patch +--- + +Moved the `import:icons` and `import:illustrations` scripts to the repository root, next to `import:payments`. diff --git a/docs/content/illustrations/index.mdx b/docs/content/illustrations/index.mdx index 4e9989dc2..ff13d36d8 100644 --- a/docs/content/illustrations/index.mdx +++ b/docs/content/illustrations/index.mdx @@ -6,6 +6,7 @@ summary: Tabler Illustrations is a collection of customizable SVG illustrations --- import { Image } from 'astro:assets' +import { site } from '@shared/lib/site' import coverLight from '../../images/cover-illustrations.jpg' import coverDark from '../../images/cover-illustrations-dark.jpg' @@ -14,7 +15,7 @@ import coverDark from '../../images/cover-illustrations-dark.jpg' Tabler Illustrations -Tabler Illustrations is a set of 130 illustrations for empty states, error pages, onboarding screens and marketing sections. Every illustration comes in a light and a dark variant, so it fits both color modes of your interface. +Tabler Illustrations is a set of { site.illustrationsCount } illustrations for empty states, error pages, onboarding screens and marketing sections. Every illustration comes in a light and a dark variant, so it fits both color modes of your interface. ## What's inside diff --git a/package.json b/package.json index a6cba967f..32f82303e 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,10 @@ "html-diff": "tsx .build/html-diff.ts check", "html-diff:baseline": "tsx .build/html-diff.ts baseline", "generate-tokens": "tsx .build/generate-tokens.ts", - "import-payments": "tsx .build/import-payments.ts", + "import:icons": "tsx .build/import-icons.ts", + "import:illustrations": "tsx .build/import-illustrations.ts", + "import:payments": "tsx .build/import-payments.ts", + "update:icons": "pnpm up -w @tabler/icons@latest && pnpm run import:icons", "check:tokens": "tsx .build/generate-tokens.ts check", "check:docs-links": "tsx .build/check-docs-links.ts", "check:css-vars": "tsx .build/check-css-vars.ts", @@ -46,6 +49,7 @@ "@changesets/cli": "^3.0.1", "@mdx-js/mdx": "^3.1.1", "@stylistic/stylelint-config": "^2.0.0", + "@tabler/icons": "^3.46.0", "@types/node": "^26.4.0", "adm-zip": "^0.6.0", "autoprefixer": "^10.5.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 72ebf905d..1657d7b79 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,6 +24,9 @@ importers: '@stylistic/stylelint-config': specifier: ^2.0.0 version: 2.0.0(stylelint@16.26.1(supports-color@5.5.0)(typescript@7.0.2)) + '@tabler/icons': + specifier: ^3.46.0 + version: 3.46.0 '@types/node': specifier: ^26.4.0 version: 26.4.0 @@ -276,9 +279,6 @@ importers: '@astrojs/mdx': specifier: ^7.0.8 version: 7.0.8(@astrojs/markdown-satteri@0.3.8)(astro@7.2.9(@astrojs/markdown-remark@7.2.4(supports-color@5.5.0))(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.4.0)(@vercel/functions@3.9.5(ws@8.21.0))(sass@1.103.1)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))(supports-color@5.5.0) - '@tabler/icons': - specifier: ^3.46.0 - version: 3.46.0 '@types/js-beautify': specifier: ^1.14.3 version: 1.14.3 diff --git a/preview/.build/import-icons.mjs b/preview/.build/import-icons.mjs deleted file mode 100644 index 526453ba9..000000000 --- a/preview/.build/import-icons.mjs +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env node - -// Regenerate shared/data/icons.json and icons-info.json from the installed -// @tabler/icons package. `pnpm run import-icons` refreshes the data files from -// the installed version; `pnpm run update-icons` bumps the package first. -import { readFileSync, writeFileSync } from 'node:fs' -import { join, dirname } from 'node:path' -import { fileURLToPath } from 'node:url' - -const __dirname = dirname(fileURLToPath(import.meta.url)) - -const iconsPackage = join(__dirname, '../node_modules/@tabler/icons') -const iconsTags = JSON.parse(readFileSync(join(iconsPackage, 'icons.json'), 'utf8')) -const { version } = JSON.parse(readFileSync(join(iconsPackage, 'package.json'), 'utf8')) - -const prepareSvgFile = (svg) => { - return svg.replace(/\n/g, '').replace(/>\s+<').replace(/\s+/g, ' ') -} - -let svgList = {} -for (let iconName in iconsTags) { - let iconData = iconsTags[iconName] - svgList[iconName] = { - name: iconName, - svg: { - outline: iconData.styles.outline ? prepareSvgFile(readFileSync(join(iconsPackage, `icons/outline/${iconName}.svg`), 'utf8')) : null, - filled: iconData.styles.filled ? prepareSvgFile(readFileSync(join(iconsPackage, `icons/filled/${iconName}.svg`), 'utf8')) : null, - }, - } -} - -writeFileSync( - join(__dirname, '../../shared/data/icons-info.json'), - JSON.stringify({ - version, - count: Object.values(svgList).reduce((acc, icon) => { - return acc + (icon.svg.outline ? 1 : 0) + (icon.svg.filled ? 1 : 0) - }, 0), - }), -) - -writeFileSync(join(__dirname, '../../shared/data/icons.json'), JSON.stringify(svgList)) - -console.log(`import-icons: imported ${Object.keys(svgList).length} icons from @tabler/icons v${version}`) diff --git a/preview/.build/import-illustrations.mjs b/preview/.build/import-illustrations.mjs deleted file mode 100644 index 31626317b..000000000 --- a/preview/.build/import-illustrations.mjs +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env node - -// Regenerate shared/data/illustrations.json from the PNG files in -// shared/static/illustrations/light. Run `pnpm run import-illustrations` -// after adding or removing illustration files. -import { globSync, writeFileSync } from 'node:fs' -import { join, basename, dirname } from 'node:path' -import { fileURLToPath } from 'node:url' - -const __dirname = dirname(fileURLToPath(import.meta.url)) - -const illustrations = globSync(join(__dirname, '../../shared/static/illustrations/light/*.png')) - .map((file) => basename(file, '.png')) - .sort() - -writeFileSync(join(__dirname, '../../shared/data/illustrations.json'), JSON.stringify(illustrations)) - -console.log(`import-illustrations: imported ${illustrations.length} illustrations`) diff --git a/preview/package.json b/preview/package.json index a2210d465..11dc92ba4 100644 --- a/preview/package.json +++ b/preview/package.json @@ -18,9 +18,6 @@ "build": "pnpm run assets && astro build", "preview": "astro preview", "clean": "shx rm -rf dist public/dist public/preview public/css .astro tmp-assets", - "import-icons": "node .build/import-icons.mjs", - "update-icons": "pnpm up @tabler/icons@latest && pnpm run import-icons", - "import-illustrations": "node .build/import-illustrations.mjs", "type-check": "astro check" }, "dependencies": { @@ -31,7 +28,6 @@ "@astrojs/check": "^0.9.4", "@astrojs/markdown-satteri": "^0.3.8", "@astrojs/mdx": "^7.0.8", - "@tabler/icons": "^3.46.0", "@types/js-beautify": "^1.14.3", "js-beautify": "^2.0.3", "prettier": "^3.9.6", diff --git a/shared/data/illustrations-info.json b/shared/data/illustrations-info.json new file mode 100644 index 000000000..8549a1006 --- /dev/null +++ b/shared/data/illustrations-info.json @@ -0,0 +1 @@ +{"version":"1.16.0","count":125} \ No newline at end of file diff --git a/shared/lib/site.ts b/shared/lib/site.ts index e9ccc9123..7966cd1e1 100644 --- a/shared/lib/site.ts +++ b/shared/lib/site.ts @@ -1,5 +1,6 @@ import corePackage from '@tabler/core/package.json' import iconsInfo from '../data/icons-info.json' +import illustrationsInfo from '../data/illustrations-info.json' const version = corePackage.version @@ -19,6 +20,9 @@ export const site = { // From shared/data/icons-info.json. iconsCount: 5986, iconsVersion: iconsInfo.version, + // From shared/data/illustrations-info.json. + illustrationsCount: illustrationsInfo.count, + illustrationsVersion: illustrationsInfo.version, descriptionShort: 'Premium and Open Source dashboard template with responsive and high quality UI.', description: 'Tabler is packed with beautifully crafted components and powerful features. Jump in and start building a stunning dashboard — all for free!', themeColor: '#066fd1',