From f27c70ad38e4fdb4b6ce4bbcbc1992b62c742408 Mon Sep 17 00:00:00 2001 From: codecalm Date: Mon, 3 Aug 2026 01:26:19 +0200 Subject: [PATCH] Fix Prettier formatting regression and Astro type-check errors `Render Modal as real markup` (#2742) reformatted ~140 files back to tabs/semicolons, breaking the Prettier config added by the Astro quality gates (#2749). Re-run prettier --write to restore it, and fix the type errors it had been masking: HTMLElement casts and `this` typing in ChangePasswordModalContent/ConfirmDeleteModalContent scripts, plain-JS syntax in the inline-injected progress.astro script, narrowed prop unions in Button/Check/Spinner, nullable icon svg casts in Icons/Rating, and a duplicate firstLetters/invalid `placeholder` attribute in Select.astro. Also fix shared/vitest.config.mts's stale `astro/lib/**/*.test.ts` include glob left over from the shared source restructure, which made `pnpm test` report zero test files. --- .build/build-css.ts | 130 +- .build/copy-assets.ts | 230 +-- .build/modules.d.ts | 24 +- docs/astro.config.mjs | 2 +- docs/components/DocsMenu.astro | 116 +- docs/components/DocsNavbar.astro | 106 +- docs/components/DocsPagination.astro | 172 +- docs/components/DocsToc.astro | 51 +- docs/components/Example.astro | 119 +- docs/layouts/DocsMdxLayout.astro | 2 +- docs/pages/ui/base/markdown/index.astro | 2 +- preview/astro.config.mjs | 318 ++-- preview/pages/2-step-verification-code.astro | 149 +- preview/pages/all-elements.astro | 1582 ++++++++--------- preview/pages/auth-lock.astro | 6 +- preview/pages/avatars.astro | 310 ++-- preview/pages/badges.astro | 381 ++-- preview/pages/blank.astro | 6 +- preview/pages/buttons.astro | 372 ++-- preview/pages/changelog.astro | 10 +- preview/pages/colorpicker.astro | 53 +- preview/pages/colors.astro | 451 +++-- preview/pages/dashboard-crypto.astro | 621 ++++--- preview/pages/email-inbox.astro | 389 ++-- preview/pages/emails.astro | 183 +- preview/pages/flags.astro | 55 +- preview/pages/gallery.astro | 44 +- preview/pages/illustrations.astro | 360 ++-- preview/pages/inline-player.astro | 49 +- preview/pages/job-listing.astro | 268 ++- preview/pages/license.astro | 114 +- preview/pages/lightbox.astro | 37 +- preview/pages/map-fullsize.astro | 46 +- preview/pages/maps.astro | 58 +- preview/pages/markdown.astro | 2 +- preview/pages/marketing/pricing.astro | 26 +- preview/pages/modals.astro | 317 ++-- preview/pages/music.astro | 42 +- preview/pages/navigation.astro | 60 +- preview/pages/onboarding.astro | 236 +-- preview/pages/pay.astro | 195 +- preview/pages/playground-categories.astro | 618 +++---- preview/pages/playground-layout.astro | 36 +- preview/pages/playground.astro | 451 +++-- preview/pages/progress.astro | 456 +++-- preview/pages/screenshot.astro | 124 +- preview/pages/sign-in-cover.astro | 44 +- preview/pages/sign-in-link.astro | 30 +- preview/pages/signatures.astro | 192 +- preview/pages/social-icons.astro | 175 +- preview/pages/tags.astro | 194 +- preview/pages/tasks-list.astro | 297 ++-- preview/pages/text-features.astro | 132 +- preview/pages/tour.astro | 433 +++-- preview/pages/users.astro | 102 +- shared/components/CaptureModal.astro | 4 +- shared/components/CaptureScript.astro | 4 +- shared/components/PageScripts.astro | 4 +- shared/components/cards/AuthLockCard.astro | 42 +- .../components/cards/CardBackgroundIcon.astro | 17 +- shared/components/cards/CardImage.astro | 41 +- shared/components/cards/CardRibbonText.astro | 15 +- shared/components/cards/CardRibbonTop.astro | 17 +- shared/components/cards/CardTabs.astro | 59 +- shared/components/cards/CarouselCard.astro | 155 +- shared/components/cards/Code.astro | 12 +- shared/components/cards/CreditCard.astro | 90 +- shared/components/cards/CrmStats.astro | 87 +- shared/components/cards/EmptyTeam.astro | 26 +- .../components/cards/ForgotPasswordCard.astro | 1 - shared/components/cards/GalleryPhoto.astro | 78 +- shared/components/cards/Icons.astro | 67 +- shared/components/cards/InvoiceCard.astro | 1 + shared/components/cards/PricingCard.astro | 74 +- shared/components/cards/ProfileContact.astro | 48 +- shared/components/cards/ProjectKanban.astro | 73 +- shared/components/cards/ProjectProgress.astro | 76 +- shared/components/cards/RecentActivity.astro | 64 +- shared/components/cards/SignUpCard.astro | 1 - shared/components/cards/SocialTraffic.astro | 65 +- shared/components/cards/TabsCard.astro | 177 +- shared/components/cards/UserCardBg.astro | 45 +- shared/components/cards/UserCardBig.astro | 34 +- shared/components/cards/UserInfo.astro | 84 +- shared/components/cards/UsersList.astro | 168 +- shared/components/cards/UsersList2.astro | 86 +- .../components/cards/UsersListHeaders.astro | 104 +- .../cards/charts/SocialReferrals.astro | 24 +- .../components/cards/music/TracksList.astro | 94 +- .../components/cards/placeholder/Card1.astro | 1 + .../components/cards/placeholder/Card2.astro | 1 + .../components/cards/placeholder/Card3.astro | 1 + .../components/cards/placeholder/Card4.astro | 1 + .../components/cards/placeholder/Card5.astro | 1 + .../components/cards/placeholder/Card6.astro | 48 +- shared/components/forms/FormElements1.astro | 250 ++- shared/components/js/TablerList.astro | 28 +- shared/components/layout/Footer.astro | 106 +- .../layout/HeaderActionsBreadcrumb.astro | 4 +- .../layout/HeaderActionsButtons.astro | 57 +- .../layout/HeaderActionsPhotos.astro | 22 +- shared/components/layout/HeaderUptime.astro | 58 +- .../marketing/MarketingNavbar.astro | 84 +- .../components/marketing/hero/Browser.astro | 84 +- shared/components/marketing/hero/Side.astro | 132 +- .../marketing/sections/Features.astro | 52 +- .../marketing/sections/Features2.astro | 124 +- .../marketing/sections/Features3.astro | 124 +- .../marketing/sections/Testimonials.astro | 126 +- .../modals/AddTaskModalContent.astro | 66 +- .../modals/ChangePasswordModalContent.astro | 179 +- .../modals/ConfirmDeleteModalContent.astro | 80 +- shared/components/modals/Modal.astro | 54 +- shared/components/navbar/NavbarLogo.astro | 77 +- shared/components/navbar/NavbarMenu.astro | 186 +- shared/components/navbar/NavbarMenuItem.astro | 105 +- shared/components/navbar/NavbarSearch.astro | 14 +- shared/components/navbar/NavbarSide.astro | 104 +- shared/components/navbar/NavbarSideApps.astro | 76 +- .../navbar/NavbarSideNotifications.astro | 182 +- shared/components/navbar/NavbarSideUser.astro | 54 +- shared/components/navbar/Sidebar.astro | 53 +- shared/components/parts/Datagrid.astro | 150 +- shared/components/parts/NavAside.astro | 144 +- shared/components/parts/Tasks.astro | 222 ++- shared/layouts/BaseLayout.astro | 330 ++-- shared/layouts/DefaultLayout.astro | 229 +-- shared/layouts/ErrorLayout.astro | 50 +- shared/layouts/MarketingLayout.astro | 300 ++-- shared/layouts/PayLayout.astro | 46 +- shared/layouts/RedirectLayout.astro | 41 +- shared/ui/ActivityPart.astro | 88 +- shared/ui/AdvancedTable.astro | 343 ++-- shared/ui/Alert.astro | 152 +- shared/ui/Avatar.astro | 119 +- shared/ui/AvatarList.astro | 38 +- shared/ui/AvatarUpload.astro | 17 +- shared/ui/Badge.astro | 67 +- shared/ui/Breadcrumb.astro | 68 +- shared/ui/Button.astro | 156 +- shared/ui/ChartRadial.astro | 120 +- shared/ui/Colorpicker.astro | 76 +- shared/ui/DropdownMenu.astro | 228 +-- shared/ui/DropdownMenuAll.astro | 66 +- shared/ui/Dropzone.astro | 58 +- shared/ui/Empty.astro | 81 +- shared/ui/Fullcalendar.astro | 88 +- shared/ui/Illustration.astro | 23 +- shared/ui/NavSegmented.astro | 121 +- shared/ui/Pagination.astro | 177 +- shared/ui/Payment.astro | 17 +- shared/ui/ProgressBg.astro | 42 +- shared/ui/ProgressDescription.astro | 35 +- shared/ui/Rating.astro | 85 +- shared/ui/Select.astro | 4 +- shared/ui/Signature.astro | 66 +- shared/ui/Spinner.astro | 29 +- shared/ui/SwitchIcon.astro | 42 +- shared/ui/Table.astro | 201 +-- shared/ui/ThemeSettings.astro | 266 ++- shared/ui/Timeline.astro | 191 +- shared/ui/Toast.astro | 99 +- shared/ui/Trending.astro | 19 +- shared/ui/Wysiwyg.astro | 50 +- shared/ui/form/Check.astro | 95 +- shared/vitest.config.mts | 2 +- 166 files changed, 9678 insertions(+), 10612 deletions(-) diff --git a/.build/build-css.ts b/.build/build-css.ts index 46dcd5f62..b3b5e3c00 100644 --- a/.build/build-css.ts +++ b/.build/build-css.ts @@ -30,8 +30,8 @@ const args = process.argv.slice(2) const flags = args.filter((arg) => arg.startsWith('--')) const [scssDir, outDir] = args.filter((arg) => !arg.startsWith('--')) if (!scssDir || !outDir) { - console.error('usage: tsx build-css.ts [--rtl] [--minify]') - process.exit(1) + console.error('usage: tsx build-css.ts [--rtl] [--minify]') + process.exit(1) } const withRtl = flags.includes('--rtl') const withMinify = flags.includes('--minify') @@ -43,96 +43,96 @@ const pendingWrites: { file: string; content: string }[] = [] // Skipping identical content keeps file watchers quiet for outputs a given scss // edit did not actually change (the dev servers full-reload on these writes). function queueWrite(file: string, content: string) { - if (existsSync(file) && readFileSync(file, 'utf8') === content) return - pendingWrites.push({ file, content }) + if (existsSync(file) && readFileSync(file, 'utf8') === content) return + pendingWrites.push({ file, content }) } // sass + autoprefixer for one entry: scss/x.scss → outDir/x.css (+ .map) async function compile(entry: string): Promise<{ outFile: string; result: Result }> { - const outFile = join(outDir, `${basename(entry, '.scss')}.css`) - const compiled = compileSass(join(scssDir, entry), { loadPaths: ['node_modules'], style: 'expanded' }) - // The sass CLI ends its output files with a newline; the JS API's css string - // does not. Keep the byte-identical CLI behavior (the annotation comment then - // lands after a blank line, exactly like `postcss --replace` produced). - const result = await postcss([autoprefixer({ cascade: false })]).process(`${compiled.css}\n`, { - from: outFile, - to: outFile, - map: mapOptions, - }) - queueWrite(outFile, result.css) - if (result.map) queueWrite(`${outFile}.map`, result.map.toString()) - written.push(outFile) - return { outFile, result } + const outFile = join(outDir, `${basename(entry, '.scss')}.css`) + const compiled = compileSass(join(scssDir, entry), { loadPaths: ['node_modules'], style: 'expanded' }) + // The sass CLI ends its output files with a newline; the JS API's css string + // does not. Keep the byte-identical CLI behavior (the annotation comment then + // lands after a blank line, exactly like `postcss --replace` produced). + const result = await postcss([autoprefixer({ cascade: false })]).process(`${compiled.css}\n`, { + from: outFile, + to: outFile, + map: mapOptions, + }) + queueWrite(outFile, result.css) + if (result.map) queueWrite(`${outFile}.map`, result.map.toString()) + written.push(outFile) + return { outFile, result } } // rtlcss over the prefixed output: outDir/x.css → outDir/x.rtl.css (+ .map) async function rtl(entry: string, outFile: string, base: Result): Promise { - const rtlFile = join(outDir, `${basename(entry, '.scss')}.rtl.css`) - // Same input the CLI read from disk: the prefixed css including its - // sourceMappingURL annotation, so postcss picks up the previous map. - // The previous map is not on disk yet (writes are buffered) — pass it in. - const result = await postcss([autoprefixer({ cascade: false }), rtlcss()]).process(base.css, { - from: outFile, - to: rtlFile, - map: { ...mapOptions, prev: base.map.toString() }, - }) - queueWrite(rtlFile, result.css) - if (result.map) queueWrite(`${rtlFile}.map`, result.map.toString()) - written.push(rtlFile) + const rtlFile = join(outDir, `${basename(entry, '.scss')}.rtl.css`) + // Same input the CLI read from disk: the prefixed css including its + // sourceMappingURL annotation, so postcss picks up the previous map. + // The previous map is not on disk yet (writes are buffered) — pass it in. + const result = await postcss([autoprefixer({ cascade: false }), rtlcss()]).process(base.css, { + from: outFile, + to: rtlFile, + map: { ...mapOptions, prev: base.map.toString() }, + }) + queueWrite(rtlFile, result.css) + if (result.map) queueWrite(`${rtlFile}.map`, result.map.toString()) + written.push(rtlFile) } // All compile work is done before the first write: compiles take seconds while // writes take milliseconds, so watchers see one tight burst instead of writes // spread across the whole build (which would need a long reload debounce). function flushWrites(): void { - for (const { file, content } of pendingWrites) { - writeFileSync(file, content) - console.log(`build-css: ${file}`) - } + for (const { file, content } of pendingWrites) { + writeFileSync(file, content) + console.log(`build-css: ${file}`) + } } // clean-css over every produced file: outDir/x.css → outDir/x.min.css (+ .map). // Mirrors clean-css-cli's option coercion and batch output naming/annotation // (see the clean-css-cli package's index.js). async function minify(files: string[]): Promise { - const minified = await new CleanCSS({ - batch: true, - format: 'breakWith=lf', - inline: 'local', - level: { 1: true }, - rebase: true, - rebaseTo: resolve(outDir), - returnPromise: true, - sourceMap: true, - sourceMapInlineSources: true, - }).minify(files) - for (const inputFile of files) { - const fileResult = minified[inputFile] - if (!fileResult) throw new Error(`build-css: no minify result for ${inputFile}`) - if (fileResult.errors.length > 0) throw new Error(fileResult.errors.join('\n')) - for (const warning of fileResult.warnings) console.warn(`build-css: ${warning}`) - const minFile = inputFile.replace(/\.css$/, '.min.css') - writeFileSync(minFile, `${fileResult.styles}${EOL}/*# sourceMappingURL=${basename(minFile)}.map */`) - writeFileSync(`${minFile}.map`, fileResult.sourceMap.toString()) - console.log(`build-css: ${minFile}`) - } + const minified = await new CleanCSS({ + batch: true, + format: 'breakWith=lf', + inline: 'local', + level: { 1: true }, + rebase: true, + rebaseTo: resolve(outDir), + returnPromise: true, + sourceMap: true, + sourceMapInlineSources: true, + }).minify(files) + for (const inputFile of files) { + const fileResult = minified[inputFile] + if (!fileResult) throw new Error(`build-css: no minify result for ${inputFile}`) + if (fileResult.errors.length > 0) throw new Error(fileResult.errors.join('\n')) + for (const warning of fileResult.warnings) console.warn(`build-css: ${warning}`) + const minFile = inputFile.replace(/\.css$/, '.min.css') + writeFileSync(minFile, `${fileResult.styles}${EOL}/*# sourceMappingURL=${basename(minFile)}.map */`) + writeFileSync(`${minFile}.map`, fileResult.sourceMap.toString()) + console.log(`build-css: ${minFile}`) + } } // Wrapped in a function because tsx compiles root-level .ts as CJS, where // top-level await is unavailable. async function main() { - const entries = readdirSync(scssDir).filter((file) => file.endsWith('.scss') && !file.startsWith('_')) - mkdirSync(outDir, { recursive: true }) + const entries = readdirSync(scssDir).filter((file) => file.endsWith('.scss') && !file.startsWith('_')) + mkdirSync(outDir, { recursive: true }) - for (const entry of entries) { - const { outFile, result } = await compile(entry) - if (withRtl) await rtl(entry, outFile, result) - } - flushWrites() - if (withMinify) await minify(written) + for (const entry of entries) { + const { outFile, result } = await compile(entry) + if (withRtl) await rtl(entry, outFile, result) + } + flushWrites() + if (withMinify) await minify(written) } main().catch((error) => { - console.error(error) - process.exit(1) + console.error(error) + process.exit(1) }) diff --git a/.build/copy-assets.ts b/.build/copy-assets.ts index 813a4b799..002825aa0 100644 --- a/.build/copy-assets.ts +++ b/.build/copy-assets.ts @@ -15,23 +15,23 @@ import { dirname, join, relative, sep } from 'node:path' // node_modules), and `astro check` verifies the produced object against the // real AstroIntegration type at the usage site in each astro.config.mjs anyway. interface Logger { - info(message: string): void - warn(message: string): void + info(message: string): void + warn(message: string): void } interface DevServer { - watcher: { - add(paths: string[]): void - on(event: string, callback: (file: string) => void): void - } - hot: { send(payload: { type: string }): void } + watcher: { + add(paths: string[]): void + on(event: string, callback: (file: string) => void): void + } + hot: { send(payload: { type: string }): void } } interface Integration { - name: string - hooks: { - 'astro:config:setup'?: (options: { command: string }) => void - 'astro:config:done'?: (options: { logger: Logger }) => void - 'astro:server:setup'?: (options: { server: DevServer; logger: Logger }) => void - } + name: string + hooks: { + 'astro:config:setup'?: (options: { command: string }) => void + 'astro:config:done'?: (options: { logger: Logger }) => void + 'astro:server:setup'?: (options: { server: DevServer; logger: Logger }) => void + } } /** @@ -42,115 +42,115 @@ interface Integration { * concurrently). */ interface CopyEntry { - from: string - to: string - label: string - required?: boolean - requiredFile?: string - allowDestinationFallback?: boolean + from: string + to: string + label: string + required?: boolean + requiredFile?: string + allowDestinationFallback?: boolean } interface CopyAssetsOptions { - /** repo root, for log-friendly relative paths */ - repo: string - /** the package's public/ directory (fully generated) */ - publicDir: string - copies: CopyEntry[] - /** generated source dirs to live-sync into publicDir while `astro dev` runs */ - syncDirs?: { from: string; to: string }[] - /** dirs written to directly by watchers — already in place, only trigger a browser reload */ - reloadDirs?: string[] + /** repo root, for log-friendly relative paths */ + repo: string + /** the package's public/ directory (fully generated) */ + publicDir: string + copies: CopyEntry[] + /** generated source dirs to live-sync into publicDir while `astro dev` runs */ + syncDirs?: { from: string; to: string }[] + /** dirs written to directly by watchers — already in place, only trigger a browser reload */ + reloadDirs?: string[] } export function copyAssets({ repo, publicDir, copies, syncDirs = [], reloadDirs = [] }: CopyAssetsOptions): Integration { - let command: string + let command: string - function rebuildPublicDir(logger: Logger) { - // Always start from a clean public/ (mirrors Bootstrap's own docs - // integration): running this twice in a row, or without a prior - // `pnpm run clean`, must never accumulate stale/nested content — - // see preview/.build/vite.config.mts for the unbounded-growth story. - rmSync(publicDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }) - mkdirSync(publicDir, { recursive: true }) + function rebuildPublicDir(logger: Logger) { + // Always start from a clean public/ (mirrors Bootstrap's own docs + // integration): running this twice in a row, or without a prior + // `pnpm run clean`, must never accumulate stale/nested content — + // see preview/.build/vite.config.mts for the unbounded-growth story. + rmSync(publicDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }) + mkdirSync(publicDir, { recursive: true }) - for (const { from, to, label, required = true, requiredFile, allowDestinationFallback } of copies) { - if (!existsSync(from) || (requiredFile && !existsSync(requiredFile))) { - const message = `copy-assets: missing ${requiredFile ?? from}` - if (required) throw new Error(`${message} — build ${label} first`) - logger.warn(`${message} (skipped — build ${label} to get it)`) - continue - } - mkdirSync(dirname(to), { recursive: true }) - if (statSync(from).isFile()) { - copyFileSync(from, to) - continue - } - try { - // dereference: sources may be symlinks (e.g. preview/static → shared/static) - cpSync(from, to, { - recursive: true, - dereference: true, - filter: (src) => !src.includes('/.vscode') && !src.includes('\\.vscode'), - }) - } catch (error) { - // In turbo dev, @tabler/core can clean dist while we copy it. - // If fallback is allowed and destination exists, keep current assets. - if (allowDestinationFallback && error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT' && existsSync(to)) { - logger.warn(`copy-assets: source changed during copy ${from} (using existing ${to})`) - continue - } - throw error - } - } - logger.info('public/ rebuilt from workspace assets') - } + for (const { from, to, label, required = true, requiredFile, allowDestinationFallback } of copies) { + if (!existsSync(from) || (requiredFile && !existsSync(requiredFile))) { + const message = `copy-assets: missing ${requiredFile ?? from}` + if (required) throw new Error(`${message} — build ${label} first`) + logger.warn(`${message} (skipped — build ${label} to get it)`) + continue + } + mkdirSync(dirname(to), { recursive: true }) + if (statSync(from).isFile()) { + copyFileSync(from, to) + continue + } + try { + // dereference: sources may be symlinks (e.g. preview/static → shared/static) + cpSync(from, to, { + recursive: true, + dereference: true, + filter: (src) => !src.includes('/.vscode') && !src.includes('\\.vscode'), + }) + } catch (error) { + // In turbo dev, @tabler/core can clean dist while we copy it. + // If fallback is allowed and destination exists, keep current assets. + if (allowDestinationFallback && error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT' && existsSync(to)) { + logger.warn(`copy-assets: source changed during copy ${from} (using existing ${to})`) + continue + } + throw error + } + } + logger.info('public/ rebuilt from workspace assets') + } - return { - name: 'copy-assets', - hooks: { - 'astro:config:setup': (options) => { - command = options.command - }, - 'astro:config:done': ({ logger }) => { - // `astro check`/`astro sync` (command 'sync') runs these hooks too, but - // only needs types — public/ is irrelevant there and CI's type-check - // job has no built workspace assets to copy. - if (command === 'sync') return - rebuildPublicDir(logger) - }, - 'astro:server:setup': ({ server, logger }) => { - if (command !== 'dev') return - // The copy above runs once at startup, so edits rebuilt into the source - // dirs during `pnpm run dev` (e.g. core/dist by core's watchers) would - // never reach the served public/ copy. Watch them, sync changed files - // into public/, and trigger a browser reload. - server.watcher.add([...syncDirs.map((dir) => dir.from), ...reloadDirs]) - let reloadTimer: ReturnType | undefined - const scheduleReload = (file: string) => { - // Source maps piggyback on their css/js file's reload. build-css.ts - // buffers all writes into one tight burst, so a short quiet window is - // enough to coalesce a whole rebuild into a single reload. - if (file.endsWith('.map')) return - clearTimeout(reloadTimer) - reloadTimer = setTimeout(() => { - server.hot.send({ type: 'full-reload' }) - logger.info(`reloaded after change in ${relative(repo, file)}`) - }, 250) - } - const sync = (file: string) => { - for (const { from, to } of syncDirs) { - if (!file.startsWith(from + sep)) continue - const dest = join(to, relative(from, file)) - mkdirSync(dirname(dest), { recursive: true }) - copyFileSync(file, dest) - scheduleReload(file) - return - } - if (reloadDirs.some((dir) => file.startsWith(dir + sep))) scheduleReload(file) - } - server.watcher.on('add', sync) - server.watcher.on('change', sync) - }, - }, - } + return { + name: 'copy-assets', + hooks: { + 'astro:config:setup': (options) => { + command = options.command + }, + 'astro:config:done': ({ logger }) => { + // `astro check`/`astro sync` (command 'sync') runs these hooks too, but + // only needs types — public/ is irrelevant there and CI's type-check + // job has no built workspace assets to copy. + if (command === 'sync') return + rebuildPublicDir(logger) + }, + 'astro:server:setup': ({ server, logger }) => { + if (command !== 'dev') return + // The copy above runs once at startup, so edits rebuilt into the source + // dirs during `pnpm run dev` (e.g. core/dist by core's watchers) would + // never reach the served public/ copy. Watch them, sync changed files + // into public/, and trigger a browser reload. + server.watcher.add([...syncDirs.map((dir) => dir.from), ...reloadDirs]) + let reloadTimer: ReturnType | undefined + const scheduleReload = (file: string) => { + // Source maps piggyback on their css/js file's reload. build-css.ts + // buffers all writes into one tight burst, so a short quiet window is + // enough to coalesce a whole rebuild into a single reload. + if (file.endsWith('.map')) return + clearTimeout(reloadTimer) + reloadTimer = setTimeout(() => { + server.hot.send({ type: 'full-reload' }) + logger.info(`reloaded after change in ${relative(repo, file)}`) + }, 250) + } + const sync = (file: string) => { + for (const { from, to } of syncDirs) { + if (!file.startsWith(from + sep)) continue + const dest = join(to, relative(from, file)) + mkdirSync(dirname(dest), { recursive: true }) + copyFileSync(file, dest) + scheduleReload(file) + return + } + if (reloadDirs.some((dir) => file.startsWith(dir + sep))) scheduleReload(file) + } + server.watcher.on('add', sync) + server.watcher.on('change', sync) + }, + }, + } } diff --git a/.build/modules.d.ts b/.build/modules.d.ts index a0510d628..072f0112b 100644 --- a/.build/modules.d.ts +++ b/.build/modules.d.ts @@ -1,19 +1,19 @@ // Minimal ambient typings for build-tool dependencies that ship no types. // Only the surface used by .build/build-css.ts is declared. declare module 'rtlcss' { - import type { Plugin } from 'postcss' - export default function rtlcss(config?: unknown): Plugin + import type { Plugin } from 'postcss' + export default function rtlcss(config?: unknown): Plugin } declare module 'clean-css' { - interface MinifyResult { - styles: string - errors: string[] - warnings: string[] - sourceMap: { toString(): string } - } - export default class CleanCSS { - constructor(options: Record) - minify(input: string[]): Promise> - } + interface MinifyResult { + styles: string + errors: string[] + warnings: string[] + sourceMap: { toString(): string } + } + export default class CleanCSS { + constructor(options: Record) + minify(input: string[]): Promise> + } } diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index b1585a031..0bb97a03e 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -6,7 +6,7 @@ import { fileURLToPath } from 'node:url' import { copyAssets } from '../.build/copy-assets' /** @param {string} p */ -const path = p => fileURLToPath(new URL(p, import.meta.url)) +const path = (p) => fileURLToPath(new URL(p, import.meta.url)) // https://astro.build/config export default defineConfig({ diff --git a/docs/components/DocsMenu.astro b/docs/components/DocsMenu.astro index b4f3bf89f..d9be618de 100644 --- a/docs/components/DocsMenu.astro +++ b/docs/components/DocsMenu.astro @@ -1,86 +1,74 @@ --- // Menu tree from shared/data/docs.json. -import Subheader from '@ui/Subheader.astro'; -import docs from '@data/docs.json'; -import { pathSlug as slug } from '@shared/lib/string-format'; +import Subheader from '@ui/Subheader.astro' +import docs from '@data/docs.json' +import { pathSlug as slug } from '@shared/lib/string-format' interface MenuLeaf { - title: string; - url: string; + title: string + url: string } interface MenuGroup extends MenuLeaf { - children?: MenuLeaf[]; + children?: MenuLeaf[] } interface MenuSection { - title: string; - children?: MenuGroup[]; + title: string + children?: MenuGroup[] } interface Props { - /** Page URL in the docs namespace (equivalent of page.url), e.g. "/ui/components/alert/". */ - url: string; + /** Page URL in the docs namespace (equivalent of page.url), e.g. "/ui/components/alert/". */ + url: string } -const { url } = Astro.props; -const menu = docs.menu as MenuSection[]; +const { url } = Astro.props +const menu = docs.menu as MenuSection[] --- diff --git a/docs/components/DocsNavbar.astro b/docs/components/DocsNavbar.astro index 416df57c2..3c941ad89 100644 --- a/docs/components/DocsNavbar.astro +++ b/docs/components/DocsNavbar.astro @@ -1,62 +1,62 @@ --- -import Icon from '@ui/Icon.astro'; -import DocsLogo from './DocsLogo.astro'; -import { site } from '@shared/lib/site'; +import Icon from '@ui/Icon.astro' +import DocsLogo from './DocsLogo.astro' +import { site } from '@shared/lib/site' // Eventually belongs in src/lib/site.ts (out of scope for this task). -const previewUrl = 'https://preview.tabler.io'; -const changelogUrl = 'https://tabler.io/changelog'; +const previewUrl = 'https://preview.tabler.io' +const changelogUrl = 'https://tabler.io/changelog' --- diff --git a/docs/components/DocsPagination.astro b/docs/components/DocsPagination.astro index 162915960..65d58e891 100644 --- a/docs/components/DocsPagination.astro +++ b/docs/components/DocsPagination.astro @@ -1,119 +1,113 @@ --- -import DocsCard from './DocsCard.astro'; -import docs from '@data/docs.json'; -import Icon from '@ui/Icon.astro'; -import { getDocsChildren } from '@shared/lib/docs-children'; -import type { DocsChildPage } from '@shared/lib/docs-children'; +import DocsCard from './DocsCard.astro' +import docs from '@data/docs.json' +import Icon from '@ui/Icon.astro' +import { getDocsChildren } from '@shared/lib/docs-children' +import type { DocsChildPage } from '@shared/lib/docs-children' interface MenuLeaf { - title: string; - url: string; + title: string + url: string } interface MenuNode { - title: string; - url?: string; - children?: MenuNode[]; + title: string + url?: string + children?: MenuNode[] } interface Props { - /** Page URL in the docs namespace (equivalent of page.url), e.g. "/ui/components/alert/". */ - url: string; + /** Page URL in the docs namespace (equivalent of page.url), e.g. "/ui/components/alert/". */ + url: string } -const { url } = Astro.props; +const { url } = Astro.props -const children = getDocsChildren(url); +const children = getDocsChildren(url) type Pagination = { - prev: MenuLeaf | null; - next: MenuLeaf | null; - found: boolean; -}; + prev: MenuLeaf | null + next: MenuLeaf | null + found: boolean +} function findPage(nodes: MenuNode[]): Pagination | null { - const index = nodes.findIndex((item) => item.url === url); + const index = nodes.findIndex((item) => item.url === url) - if (index !== -1) { - const previousNode = index > 0 ? nodes[index - 1] : null; - const nextNode = index < nodes.length - 1 ? nodes[index + 1] : null; + if (index !== -1) { + const previousNode = index > 0 ? nodes[index - 1] : null + const nextNode = index < nodes.length - 1 ? nodes[index + 1] : null - return { - prev: previousNode?.url ? { title: previousNode.title, url: previousNode.url } : null, - next: nextNode?.url ? { title: nextNode.title, url: nextNode.url } : null, - found: true, - }; - } + return { + prev: previousNode?.url ? { title: previousNode.title, url: previousNode.url } : null, + next: nextNode?.url ? { title: nextNode.title, url: nextNode.url } : null, + found: true, + } + } - for (const node of nodes) { - const result = node.children ? findPage(node.children) : null; - if (result) return result; - } + for (const node of nodes) { + const result = node.children ? findPage(node.children) : null + if (result) return result + } - return null; + return null } const { prev, next, found } = findPage(docs.menu as MenuNode[]) ?? { - prev: null, - next: null, - found: false, -}; + prev: null, + next: null, + found: false, +} --- - -{ - children.length > 0 && ( -
-
- {children.map((child: DocsChildPage) => ( - - ))} -
-
- ) +{ + children.length > 0 && ( +
+
+ {children.map((child: DocsChildPage) => ( + + ))} +
+
+ ) } { - children.length === 0 && found && ( - - ) + children.length === 0 && found && ( + + ) } diff --git a/docs/components/DocsToc.astro b/docs/components/DocsToc.astro index 62899f5d4..b9ce61e7b 100644 --- a/docs/components/DocsToc.astro +++ b/docs/components/DocsToc.astro @@ -3,44 +3,43 @@ // markdown content, excluding ... blocks). export interface TocItem { - /** 2 or 3 (h2/h3); h3 gets the ms-3 indent */ - level: number; - text: string; - /** anchor without '#', e.g. "default-markup" */ - id: string; + /** 2 or 3 (h2/h3); h3 gets the ms-3 indent */ + level: number + text: string + /** anchor without '#', e.g. "default-markup" */ + id: string } interface Props { - toc?: TocItem[]; + toc?: TocItem[] } -const { toc = [] } = Astro.props; +const { toc = [] } = Astro.props // shared/data/illustrations.json has 100 entries. -const illustrationsCount = 100; +const illustrationsCount = 100 --- - -{ - toc.length > 0 && ( - -

Table of Contents

- -
- ) +{ + toc.length > 0 && ( + +

Table of Contents

+ +
+ ) } -
- +
+ -

{illustrationsCount} sleek illustrations for your startup's visual identity.

-
+

{illustrationsCount} sleek illustrations for your startup's visual identity.

+
diff --git a/docs/components/Example.astro b/docs/components/Example.astro index a48fb2e56..8f4ff355c 100644 --- a/docs/components/Example.astro +++ b/docs/components/Example.astro @@ -1,97 +1,66 @@ --- // Example code from the `html` prop or the rendered slot; copy button uses data-clipboard-text. -import Icon from '@ui/Icon.astro'; -import { beautifyHtml, highlightCode, removeHref } from '@shared/lib/code-example'; +import Icon from '@ui/Icon.astro' +import { beautifyHtml, highlightCode, removeHref } from '@shared/lib/code-example' interface Props { - /** raw HTML of the example; when absent — the slot is rendered */ - html?: string; - /** code shown in the panel instead of html */ - code?: string; - raw?: boolean; - overflow?: string; - bg?: string; - class?: string; - height?: string; - column?: boolean; - centered?: boolean; - vertical?: boolean; - columnFullWidth?: boolean; - hideCode?: boolean; - codeOnly?: boolean; + /** raw HTML of the example; when absent — the slot is rendered */ + html?: string + /** code shown in the panel instead of html */ + code?: string + raw?: boolean + overflow?: string + bg?: string + class?: string + height?: string + column?: boolean + centered?: boolean + vertical?: boolean + columnFullWidth?: boolean + hideCode?: boolean + codeOnly?: boolean } -const { - html: htmlProp, - code, - raw, - overflow = 'auto', - bg, - class: className, - height, - column, - centered, - vertical, - columnFullWidth, - hideCode, - codeOnly, -} = Astro.props; +const { html: htmlProp, code, raw, overflow = 'auto', bg, class: className, height, column, centered, vertical, columnFullWidth, hideCode, codeOnly } = Astro.props // Strip empty lines from the example HTML. -let html = (htmlProp ?? (await Astro.slots.render('default'))).replace(/^\s*[\r\n]/gm, ''); +let html = (htmlProp ?? (await Astro.slots.render('default'))).replace(/^\s*[\r\n]/gm, '') // JSX reserializes the slot markup: empty SVG elements get explicit closing // tags, and apostrophes in text are escaped. We restore the source form so the // code in the panel looks like hand-written HTML. -html = html - .replace(/<(path|circle|line|polyline|polygon|rect|ellipse)([^>]*?)\s*><\/\1>/g, '<$1$2 />') - .replace(/'/g, "'"); +html = html.replace(/<(path|circle|line|polyline|polygon|rect|ellipse)([^>]*?)\s*><\/\1>/g, '<$1$2 />').replace(/'/g, "'") -const exampleClasses = [ - 'example fs-base border rounded my-5', - !raw && 'd-flex flex-wrap justify-content-center', - `overflow-${overflow}`, - 'position-relative', - bg ? `bg-${bg}` : 'bg-pattern-rectangles', - className, -] +const exampleClasses = ['example fs-base border rounded my-5', !raw && 'd-flex flex-wrap justify-content-center', `overflow-${overflow}`, 'position-relative', bg ? `bg-${bg}` : 'bg-pattern-rectangles', className] -const innerClasses = [ - 'p-6 w-full', - column && 'd-flex gap-3 flex-column', - !column && centered && `d-flex flex-fill flex-wrap gap-2 justify-content-center${vertical ? ' align-items-center flex-column' : ' justify-content-center'}`, - columnFullWidth && 'd-flex flex-fill flex-column gap-2', -] +const innerClasses = ['p-6 w-full', column && 'd-flex gap-3 flex-column', !column && centered && `d-flex flex-fill flex-wrap gap-2 justify-content-center${vertical ? ' align-items-center flex-column' : ' justify-content-center'}`, columnFullWidth && 'd-flex flex-fill flex-column gap-2'] -const highlighted = hideCode ? '' : await highlightCode(beautifyHtml(code ?? html), 'html'); +const highlighted = hideCode ? '' : await highlightCode(beautifyHtml(code ?? html), 'html') --- - -{ - !codeOnly && ( -
- { - raw ? ( - - ) : ( -
- -
- ) - } -
- ) +{ + !codeOnly && ( +
+ {raw ? ( + + ) : ( +
+ +
+ )} +
+ ) } { - !hideCode && ( -
- - - - - -
- ) + !hideCode && ( +
+ + + + + +
+ ) } diff --git a/docs/layouts/DocsMdxLayout.astro b/docs/layouts/DocsMdxLayout.astro index c68dc53b4..a457f8e12 100644 --- a/docs/layouts/DocsMdxLayout.astro +++ b/docs/layouts/DocsMdxLayout.astro @@ -16,7 +16,7 @@ interface MdxFrontmatter { 'summary'?: string 'description'?: string 'seoDescription'?: string - 'added-in'?: string; + 'added-in'?: string 'docs-libs'?: string[] 'hide-pagination'?: boolean } diff --git a/docs/pages/ui/base/markdown/index.astro b/docs/pages/ui/base/markdown/index.astro index 0aab22c30..f49ed6999 100644 --- a/docs/pages/ui/base/markdown/index.astro +++ b/docs/pages/ui/base/markdown/index.astro @@ -1,5 +1,5 @@ --- -import RedirectLayout from '@shared/layouts/RedirectLayout.astro'; +import RedirectLayout from '@shared/layouts/RedirectLayout.astro' --- diff --git a/preview/astro.config.mjs b/preview/astro.config.mjs index b694885b2..3657c45f7 100644 --- a/preview/astro.config.mjs +++ b/preview/astro.config.mjs @@ -1,172 +1,172 @@ // @ts-check -import { defineConfig } from 'astro/config'; -import mdx from '@astrojs/mdx'; -import { satteri } from '@astrojs/markdown-satteri'; -import beautify from 'js-beautify'; -import { execFileSync } from 'node:child_process'; -import { globSync, readFileSync, writeFileSync } from 'node:fs'; -import { devNull } from 'node:os'; -import { fileURLToPath } from 'node:url'; -import { copyAssets } from '../.build/copy-assets'; +import { defineConfig } from 'astro/config' +import mdx from '@astrojs/mdx' +import { satteri } from '@astrojs/markdown-satteri' +import beautify from 'js-beautify' +import { execFileSync } from 'node:child_process' +import { globSync, readFileSync, writeFileSync } from 'node:fs' +import { devNull } from 'node:os' +import { fileURLToPath } from 'node:url' +import { copyAssets } from '../.build/copy-assets' /** @param {string} p */ -const path = (p) => fileURLToPath(new URL(p, import.meta.url)); +const path = (p) => fileURLToPath(new URL(p, import.meta.url)) /** * After build, format page HTML with prettier (users copy it 1:1). * @returns {import('astro').AstroIntegration} */ function prettifyHtml() { - return { - name: 'prettify-html', - hooks: { - 'astro:build:done': async ({ dir, logger }) => { - const outDir = fileURLToPath(dir); - // dist/preview/ and dist/dist/ are copy-assets.mjs's copies of public/{preview,dist} - // (demo css/js and @tabler/core's dist, including vendored libs) — not pages, and - // some vendored libs ship their own malformed docs/*.html that trips the parser below. - /** @param {string} file */ - const isVendorCopy = (file) => file.includes(`${outDir}preview/`) || file.includes(`${outDir}dist/`); - // Astro appends "overflow-x: auto" to the shiki
 style — the
-				// Eleventy pipeline doesn't have it and HTML is the product: restore 1:1.
-				// node:fs is imported statically — a dynamic import() here would go
-				// through Vite's module runner, which is already closed by the time
-				// the astro:build:done hook runs (the config is bundled by vite-node
-				// because it imports a .ts module).
-				for (const file of globSync(`${outDir}**/*.html`, { exclude: isVendorCopy })) {
-					const content = readFileSync(file, 'utf8');
-					const cleaned = content.replaceAll('; overflow-x: auto;', '');
-					if (cleaned !== content) writeFileSync(file, cleaned);
-				}
-				execFileSync(
-					'npx',
-					[
-						'prettier',
-						'--write',
-						'--parser',
-						'html',
-						// Prettier's default ignore-path is [.gitignore, .prettierignore], and both
-						// list "dist" (needed elsewhere so normal lint/format passes skip build
-						// output) — that silently no-ops this pass on the very directory it targets.
-						// Point at devNull to opt this one deliberate pass out of those ignores.
-						'--ignore-path',
-						devNull,
-						`${outDir}**/*.html`,
-						`!${outDir}preview/**`,
-						`!${outDir}dist/**`,
-					],
-					{ stdio: 'inherit' },
-				);
-				logger.info('HTML formatted with prettier');
-			},
-		},
-	};
+  return {
+    name: 'prettify-html',
+    hooks: {
+      'astro:build:done': async ({ dir, logger }) => {
+        const outDir = fileURLToPath(dir)
+        // dist/preview/ and dist/dist/ are copy-assets.mjs's copies of public/{preview,dist}
+        // (demo css/js and @tabler/core's dist, including vendored libs) — not pages, and
+        // some vendored libs ship their own malformed docs/*.html that trips the parser below.
+        /** @param {string} file */
+        const isVendorCopy = (file) => file.includes(`${outDir}preview/`) || file.includes(`${outDir}dist/`)
+        // Astro appends "overflow-x: auto" to the shiki 
 style — the
+        // Eleventy pipeline doesn't have it and HTML is the product: restore 1:1.
+        // node:fs is imported statically — a dynamic import() here would go
+        // through Vite's module runner, which is already closed by the time
+        // the astro:build:done hook runs (the config is bundled by vite-node
+        // because it imports a .ts module).
+        for (const file of globSync(`${outDir}**/*.html`, { exclude: isVendorCopy })) {
+          const content = readFileSync(file, 'utf8')
+          const cleaned = content.replaceAll('; overflow-x: auto;', '')
+          if (cleaned !== content) writeFileSync(file, cleaned)
+        }
+        execFileSync(
+          'npx',
+          [
+            'prettier',
+            '--write',
+            '--parser',
+            'html',
+            // Prettier's default ignore-path is [.gitignore, .prettierignore], and both
+            // list "dist" (needed elsewhere so normal lint/format passes skip build
+            // output) — that silently no-ops this pass on the very directory it targets.
+            // Point at devNull to opt this one deliberate pass out of those ignores.
+            '--ignore-path',
+            devNull,
+            `${outDir}**/*.html`,
+            `!${outDir}preview/**`,
+            `!${outDir}dist/**`,
+          ],
+          { stdio: 'inherit' },
+        )
+        logger.info('HTML formatted with prettier')
+      },
+    },
+  }
 }
 
 // https://astro.build/config
 export default defineConfig({
-	// pages live at the package root (./pages) — all components/lib/data are
-	// shared (see the @shared alias)
-	srcDir: '.',
-	server: {
-		port: 3000,
-		// bind on all interfaces so the dev server is reachable from Docker
-		// port mappings and other devices on the local network
-		host: true,
-	},
-	vite: {
-		resolve: {
-			alias: {
-				// Demo data in shared/data (single source of truth).
-				'@data': fileURLToPath(new URL('../shared/data', import.meta.url)),
-				// Components/lib shared with docs.
-				'@shared': fileURLToPath(new URL('../shared', import.meta.url)),
-				'@ui': fileURLToPath(new URL('../shared/ui', import.meta.url)),
-				'@components': fileURLToPath(new URL('../shared/components', import.meta.url)),
-				// Used by @shared/lib/docs-children's glob.
-				'@pages': fileURLToPath(new URL('./pages', import.meta.url)),
-			},
-		},
-	},
-	build: {
-		// Emit sign-in.html instead of sign-in/index.html (HTML is the product).
-		format: 'file',
-	},
-	// Keep readable HTML; prettier formats after the build.
-	compressHTML: false,
-	integrations: [
-		copyAssets({
-			repo: path('..'),
-			publicDir: path('./public'),
-			copies: [
-				{
-					// @tabler/core dist (css/js/fonts/img/libs) — same as the Eleventy passthrough.
-					// Fallback keeps current assets if core rebuilds dist mid-copy in turbo dev.
-					from: path('./node_modules/@tabler/core/dist'),
-					to: path('./public/dist'),
-					label: '@tabler/core',
-					allowDestinationFallback: true,
-				},
-				{
-					// demo css/js built by this package's sass/vite pipeline. Source is
-					// tmp-assets/ (NOT dist/) on purpose — dist/ is Astro's own build output,
-					// and copying from a path Astro also writes to caused unbounded growth
-					// across repeated builds. See preview/.build/vite.config.mts.
-					from: path('./tmp-assets'),
-					to: path('./public/preview'),
-					label: '@tabler/preview',
-				},
-				{
-					// docs.css built by the @tabler/docs sass pipeline (used by docs pages)
-					from: path('../docs/dist/css'),
-					to: path('./public/css'),
-					label: '@tabler/docs',
-					required: false,
-				},
-				{
-					// static assets (photos, avatars, tracks, brand svgs...). The real source,
-					// because preview/static is a symlink that may not survive deployment packaging.
-					from: path('../shared/static'),
-					to: path('./public/static'),
-					label: 'shared assets',
-				},
-				// favicons (source assets of @tabler/preview)
-				{ from: path('./assets/favicon.ico'), to: path('./public/favicon.ico'), label: '@tabler/preview' },
-				{ from: path('./assets/favicon-dev.ico'), to: path('./public/favicon-dev.ico'), label: '@tabler/preview' },
-			],
-			// Watch the real core/dist, not the node_modules symlink the startup copy
-			// reads from — same directory.
-			syncDirs: [
-				{ from: path('../core/dist'), to: path('./public/dist') },
-				{ from: path('../shared/static'), to: path('./public/static') },
-			],
-			// watch-css/watch-js write straight into public/preview — the file is already
-			// in place, but Astro does not reload the browser on public/ changes.
-			reloadDirs: [path('./public/preview')],
-		}),
-		mdx(),
-		prettifyHtml(),
-	],
-	markdown: {
-		// No typographic quote rewriting.
-		processor: satteri({ features: { smartPunctuation: false } }),
-		shikiConfig: {
-			theme: 'github-dark',
-			transformers: [
-				{
-					// Beautify html fences before highlighting.
-					preprocess(code) {
-						if (this.options.lang === 'html') {
-							return beautify.html(code, { indent_size: 2, wrap_line_length: 80 });
-						}
-					},
-					// Keep shiki classes only (drop Astro's astro-code / data-language).
-					pre(node) {
-						node.properties.class = 'shiki github-dark';
-						delete node.properties.dataLanguage;
-					},
-				},
-			],
-		},
-	},
-});
+  // pages live at the package root (./pages) — all components/lib/data are
+  // shared (see the @shared alias)
+  srcDir: '.',
+  server: {
+    port: 3000,
+    // bind on all interfaces so the dev server is reachable from Docker
+    // port mappings and other devices on the local network
+    host: true,
+  },
+  vite: {
+    resolve: {
+      alias: {
+        // Demo data in shared/data (single source of truth).
+        '@data': fileURLToPath(new URL('../shared/data', import.meta.url)),
+        // Components/lib shared with docs.
+        '@shared': fileURLToPath(new URL('../shared', import.meta.url)),
+        '@ui': fileURLToPath(new URL('../shared/ui', import.meta.url)),
+        '@components': fileURLToPath(new URL('../shared/components', import.meta.url)),
+        // Used by @shared/lib/docs-children's glob.
+        '@pages': fileURLToPath(new URL('./pages', import.meta.url)),
+      },
+    },
+  },
+  build: {
+    // Emit sign-in.html instead of sign-in/index.html (HTML is the product).
+    format: 'file',
+  },
+  // Keep readable HTML; prettier formats after the build.
+  compressHTML: false,
+  integrations: [
+    copyAssets({
+      repo: path('..'),
+      publicDir: path('./public'),
+      copies: [
+        {
+          // @tabler/core dist (css/js/fonts/img/libs) — same as the Eleventy passthrough.
+          // Fallback keeps current assets if core rebuilds dist mid-copy in turbo dev.
+          from: path('./node_modules/@tabler/core/dist'),
+          to: path('./public/dist'),
+          label: '@tabler/core',
+          allowDestinationFallback: true,
+        },
+        {
+          // demo css/js built by this package's sass/vite pipeline. Source is
+          // tmp-assets/ (NOT dist/) on purpose — dist/ is Astro's own build output,
+          // and copying from a path Astro also writes to caused unbounded growth
+          // across repeated builds. See preview/.build/vite.config.mts.
+          from: path('./tmp-assets'),
+          to: path('./public/preview'),
+          label: '@tabler/preview',
+        },
+        {
+          // docs.css built by the @tabler/docs sass pipeline (used by docs pages)
+          from: path('../docs/dist/css'),
+          to: path('./public/css'),
+          label: '@tabler/docs',
+          required: false,
+        },
+        {
+          // static assets (photos, avatars, tracks, brand svgs...). The real source,
+          // because preview/static is a symlink that may not survive deployment packaging.
+          from: path('../shared/static'),
+          to: path('./public/static'),
+          label: 'shared assets',
+        },
+        // favicons (source assets of @tabler/preview)
+        { from: path('./assets/favicon.ico'), to: path('./public/favicon.ico'), label: '@tabler/preview' },
+        { from: path('./assets/favicon-dev.ico'), to: path('./public/favicon-dev.ico'), label: '@tabler/preview' },
+      ],
+      // Watch the real core/dist, not the node_modules symlink the startup copy
+      // reads from — same directory.
+      syncDirs: [
+        { from: path('../core/dist'), to: path('./public/dist') },
+        { from: path('../shared/static'), to: path('./public/static') },
+      ],
+      // watch-css/watch-js write straight into public/preview — the file is already
+      // in place, but Astro does not reload the browser on public/ changes.
+      reloadDirs: [path('./public/preview')],
+    }),
+    mdx(),
+    prettifyHtml(),
+  ],
+  markdown: {
+    // No typographic quote rewriting.
+    processor: satteri({ features: { smartPunctuation: false } }),
+    shikiConfig: {
+      theme: 'github-dark',
+      transformers: [
+        {
+          // Beautify html fences before highlighting.
+          preprocess(code) {
+            if (this.options.lang === 'html') {
+              return beautify.html(code, { indent_size: 2, wrap_line_length: 80 })
+            }
+          },
+          // Keep shiki classes only (drop Astro's astro-code / data-language).
+          pre(node) {
+            node.properties.class = 'shiki github-dark'
+            delete node.properties.dataLanguage
+          },
+        },
+      ],
+    },
+  },
+})
diff --git a/preview/pages/2-step-verification-code.astro b/preview/pages/2-step-verification-code.astro
index 735484d8a..e0dc007d2 100644
--- a/preview/pages/2-step-verification-code.astro
+++ b/preview/pages/2-step-verification-code.astro
@@ -1,93 +1,82 @@
 ---
-import FormFooter from '@ui/FormFooter.astro';
-import SingleLayout from '@shared/layouts/SingleLayout.astro';
-import Button from '@ui/Button.astro';
-import ButtonList from '@ui/ButtonList.astro';
-import CaptureScript from '@shared/components/CaptureScript.astro';
+import FormFooter from '@ui/FormFooter.astro'
+import SingleLayout from '@shared/layouts/SingleLayout.astro'
+import Button from '@ui/Button.astro'
+import ButtonList from '@ui/ButtonList.astro'
+import CaptureScript from '@shared/components/CaptureScript.astro'
 ---
 
 
-	
-
-

Authenticate Your Account

+ +
+

Authenticate Your Account

-

- Please confirm your account by entering the authorization code sent to +1 856-672-8552. -

+

+ Please confirm your account by entering the authorization code sent to +1 856-672-8552. +

-
-
- { - Array.from({ length: 2 }).map(() => ( -
-
- {Array.from({ length: 3 }).map(() => ( -
- -
- ))} -
-
- )) - } -
-
+
+
+ { + Array.from({ length: 2 }).map(() => ( +
+
+ {Array.from({ length: 3 }).map(() => ( +
+ +
+ ))} +
+
+ )) + } +
+
-
- -
+
+ +
- - -
- + + +
+ - - - - - + input.addEventListener('keydown', (e) => { + const target = e.target + // If the input field is empty and Backspace is pressed, and there is a previous input field, focus it + if (target.value.length === 0 && e.key === 'Backspace' && i > 0) { + inputs[i - 1].focus() + } + }) + }) + + + -
- It may take a minute to receive your code. Haven't received it? Resend a new code. -
+
+ It may take a minute to receive your code. Haven't received it? Resend a new code. +
diff --git a/preview/pages/all-elements.astro b/preview/pages/all-elements.astro index ecbb8349e..806216b5b 100644 --- a/preview/pages/all-elements.astro +++ b/preview/pages/all-elements.astro @@ -1,870 +1,832 @@ --- - // Code-block content lives in the frontmatter so Astro does not try to evaluate // its `${name}` / `{ }` as expressions; injected verbatim via set:html. -import ButtonGroup from '@ui/ButtonGroup.astro'; -import DefaultLayout from '@shared/layouts/DefaultLayout.astro'; -import Button from '@ui/Button.astro'; -import ButtonList from '@ui/ButtonList.astro'; -import Card from '@ui/Card.astro'; -import CardHeader from '@ui/CardHeader.astro'; -import CardBody from '@ui/CardBody.astro'; -import CardFooter from '@ui/CardFooter.astro'; -import Alert from '@ui/Alert.astro'; -import Badge from '@ui/Badge.astro'; -import Progress from '@ui/Progress.astro'; -import Select from '@ui/Select.astro'; -import Check from '@ui/form/Check.astro'; -import FormGroup from '@ui/FormGroup.astro'; -import Nav from '@ui/Nav.astro'; -import Breadcrumb from '@ui/Breadcrumb.astro'; -import Pagination from '@ui/Pagination.astro'; -import Avatar from '@ui/Avatar.astro'; -import Icon from '@ui/Icon.astro'; -import Dropdown from '@ui/Dropdown.astro'; -import Accordion from '@ui/Accordion.astro'; -import Spinner from '@ui/Spinner.astro'; -import Rating from '@ui/Rating.astro'; -import Steps from '@ui/Steps.astro'; -import StatusDot from '@ui/StatusDot.astro'; -import Toast from '@ui/Toast.astro'; -import InputIcon from '@ui/form/InputIcon.astro'; -import InputGroup from '@ui/InputGroup.astro'; -import Range from '@ui/Range.astro'; -import Tag from '@ui/Tag.astro'; -import Ribbon from '@ui/Ribbon.astro'; -import Flag from '@ui/Flag.astro'; -import Payment from '@ui/Payment.astro'; -import Timeline from '@ui/Timeline.astro'; -import Empty from '@ui/Empty.astro'; -import NavSegmented from '@ui/NavSegmented.astro'; -import ListGroup from '@ui/ListGroup.astro'; -import ListGroupItem from '@ui/ListGroupItem.astro'; +import ButtonGroup from '@ui/ButtonGroup.astro' +import DefaultLayout from '@shared/layouts/DefaultLayout.astro' +import Button from '@ui/Button.astro' +import ButtonList from '@ui/ButtonList.astro' +import Card from '@ui/Card.astro' +import CardHeader from '@ui/CardHeader.astro' +import CardBody from '@ui/CardBody.astro' +import CardFooter from '@ui/CardFooter.astro' +import Alert from '@ui/Alert.astro' +import Badge from '@ui/Badge.astro' +import Progress from '@ui/Progress.astro' +import Select from '@ui/Select.astro' +import Check from '@ui/form/Check.astro' +import FormGroup from '@ui/FormGroup.astro' +import Nav from '@ui/Nav.astro' +import Breadcrumb from '@ui/Breadcrumb.astro' +import Pagination from '@ui/Pagination.astro' +import Avatar from '@ui/Avatar.astro' +import Icon from '@ui/Icon.astro' +import Dropdown from '@ui/Dropdown.astro' +import Accordion from '@ui/Accordion.astro' +import Spinner from '@ui/Spinner.astro' +import Rating from '@ui/Rating.astro' +import Steps from '@ui/Steps.astro' +import StatusDot from '@ui/StatusDot.astro' +import Toast from '@ui/Toast.astro' +import InputIcon from '@ui/form/InputIcon.astro' +import InputGroup from '@ui/InputGroup.astro' +import Range from '@ui/Range.astro' +import Tag from '@ui/Tag.astro' +import Ribbon from '@ui/Ribbon.astro' +import Flag from '@ui/Flag.astro' +import Payment from '@ui/Payment.astro' +import Timeline from '@ui/Timeline.astro' +import Empty from '@ui/Empty.astro' +import NavSegmented from '@ui/NavSegmented.astro' +import ListGroup from '@ui/ListGroup.astro' +import ListGroupItem from '@ui/ListGroupItem.astro' const codeExample = `// JavaScript example function greetUser(name) { console.log(\`Hello, \${name}!\`); return true; -}`; +}` --- - -
- -
- - - -
-
-

Heading 1

-

Heading 2

-

Heading 3

-

Heading 4

-
Heading 5
-
Heading 6
-
-
-

This is a lead paragraph with larger text.

-

This is a regular paragraph with bold text, italic text, and - underlined text.

-

This is small muted text.

-

Primary text color

-

Success text color

-

Danger text color

-
-
-
-
-
+ +
+ +
+ + + +
+
+

Heading 1

+

Heading 2

+

Heading 3

+

Heading 4

+
Heading 5
+
Heading 6
+
+
+

This is a lead paragraph with larger text.

+

+ This is a regular paragraph with bold text, italic text, and + underlined text. +

+

This is small muted text.

+

Primary text color

+

Success text color

+

Danger text color

+
+
+
+
+
- -
- - - -
-
-

Standard Buttons

- -
-
-

Button Sizes

- -
+
+

Button Sizes

+ +
-
-
-
-
+

Icon Buttons

+ +
+
+ + + - -
- - - -

This is a simple card with header and body content.

-
-
-
+ +
+ + + +

This is a simple card with header and body content.

+
+
+
-
- - - -

This card includes a footer section.

-
- -
- Cancel - Save -
-
-
-
+
+ + + +

This card includes a footer section.

+
+ +
+ Cancel + Save +
+
+
+
- -
- - - - - - - - - -
+ +
+ + + + + + + + + +
- -
- - - -
-
- - - - - - -
-
- - -
-
-
-
-
+ +
+ + + +
+
+ + + + + + +
+
+ + +
+
+
+
+
- -
- - - -
-
- -
-
- -
-
- -
-
- -
-
-
-
-
+ +
+ + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
- -
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
+ +
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
- -
- - - -
-
-

Tabs

-
-
+ +
+ + + +
+
+

Tabs

+
+
-

Pills Navigation

-
-
+

Pills Navigation

+
+
-

Breadcrumb

- -
-
-

Pagination

- {/* activeItem="2" is a string — strict equality never matches integer index, so no page is active */} - +

Breadcrumb

+ +
+
+

Pagination

+ {/* activeItem="2" is a string — strict equality never matches integer index, so no page is active */} + -

Pagination with Text

- -
-
-
-
-
+

Pagination with Text

+ +
+
+
+
+
- -
- - - -

Unordered List

-
    -
  • First item
  • -
  • Second item
  • -
  • Third item
  • -
+ +
+ + + +

Unordered List

+
    +
  • First item
  • +
  • Second item
  • +
  • Third item
  • +
-

Ordered List

-
    -
  1. First item
  2. -
  3. Second item
  4. -
  5. Third item
  6. -
-
-
-
+

Ordered List

+
    +
  1. First item
  2. +
  3. Second item
  4. +
  5. Third item
  6. +
+
+
+
- -
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - -
NameEmailStatus
John Doejohn@example.com
Jane Smithjane@example.com
Bob Johnsonbob@example.com
-
-
-
-
+ +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
NameEmailStatus
John Doejohn@example.com
Jane Smithjane@example.com
Bob Johnsonbob@example.com
+
+
+
+
- -
- - - -
-
- - - - -
-
- - - -
-
- - - -
-
-
-
-
+ +
+ + + +
+
+ + + + +
+
+ + + +
+
+ + + +
+
+
+
+
- -
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - -
-
-
-
-
+ +
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+
+
+
+
- -
- - - - - - - - - -
+ +
+ + + + + + + + + +
- -
- - - -
-
-

Accordion

-
- -
-
-
-

Spinners

-
-
- - - - -
-
+ +
+ + + +
+
+

Accordion

+
+ +
+
+
+

Spinners

+
+
+ + + + +
+
-

Rating

-
- -
+

Rating

+
+ +
-

Steps

-
- -
-
- -
-
-
-
-
-
+

Steps

+
+ +
+
+ +
+
+
+
+
+
- -
- - - -

Status Dots

-
- - - - -
+ +
+ + + +

Status Dots

+
+ + + + +
-

Toast Notifications

-
- -
-
-
-
+

Toast Notifications

+
+ +
+
+
+
- -
- - - -

Input with Icons

-
- -
-
- -
+ +
+ + + +

Input with Icons

+
+ +
+
+ +
-

Input Groups

-
- -
-
- -
+

Input Groups

+
+ +
+
+ +
-

Range Slider

-
- -
-
-
-
+

Range Slider

+
+ +
+
+
+
- -
- - - -

Tags

-
- - - -
+ +
+ + + +

Tags

+
+ + + +
-

Ribbons

-
- -
-
- -
-
-
-
+

Ribbons

+
+ +
+
+ +
+
+
+
- -
- - - -

Flags

-
-
- - - - - - - - - - -
-
+ +
+ + + +

Flags

+
+
+ + + + + + + + + + +
+
-

Payment Icons

-
-
- - - - - - - - - - - - - - - - -
-
-
-
-
+

Payment Icons

+
+
+ + + + + + + + + + + + + + + + +
+
+
+
+
- -
- - - - - - -
+ +
+ + + + + + +
- -
- - - - - - -
+ +
+ + + + + + +
- -
- - - - - - - + +
+ + + + + + + - - + + - - - - -
+ + +
+
+
- -
- - - -

Basic Button Group

- - - - - + +
+ + + +

Basic Button Group

+ + + + + -

Button Toolbar

- +

Button Toolbar

+ -

Vertical Button Group

- - - - - -
-
-
+

Vertical Button Group

+ + + + + +
+
+
- -
- - - -

Basic Segmented

-
- -
+ +
+ + + +

Basic Segmented

+
+ +
-

With Icons

-
- -
+

With Icons

+
+ +
-

With Emojis

-
- -
+

With Emojis

+
+ +
-

With Icons and Text

-
- -
-
-
-
+

With Icons and Text

+
+ +
+
+
+
- -
- - - -
-
-

Basic Collapse

- -
-
- This is collapsed content that can be toggled. It's hidden by default and shown when the button is clicked. -
-
-
-
-

Multiple Targets

- - - - -
-
- First collapsible content. -
-
-
-
- Second collapsible content. -
-
-
-
-
-
-
+ +
+ + + +
+
+

Basic Collapse

+ +
+
This is collapsed content that can be toggled. It's hidden by default and shown when the button is clicked.
+
+
+
+

Multiple Targets

+ + + + +
+
First collapsible content.
+
+
+
Second collapsible content.
+
+
+
+
+
+
- -
- - - -
-
-

Tooltips

- - - - - - -
-
-

Popovers

- - - - -
-
-
-
-
+ +
+ + + +
+
+

Tooltips

+ + + + + + +
+
+

Popovers

+ + + + +
+
+
+
+
- -
- - - - - - - Home - - - - Active item - - - - Settings - - - - Profile - - - - Disabled item - - - - -
+ +
+ + + + + + + Home + + + + Active item + + + + Settings + + + + Profile + + + + Disabled item + + + + +
- -
- - - -

Blockquote

-
-

This is a blockquote example with some sample text to demonstrate the styling.

-
- Someone famous in Source Title -
-
+ +
+ + + +

Blockquote

+
+

This is a blockquote example with some sample text to demonstrate the styling.

+
+ Someone famous in Source Title +
+
-

Code Block

-
+

Code Block

+
-

Inline Elements

-

This paragraph contains inline code, Ctrl + S keyboard shortcut, and highlighted text.

-
-
-
-
+

Inline Elements

+

This paragraph contains inline code, Ctrl + S keyboard shortcut, and highlighted text.

+ + + +
diff --git a/preview/pages/auth-lock.astro b/preview/pages/auth-lock.astro index c7b0d71db..e78f12202 100644 --- a/preview/pages/auth-lock.astro +++ b/preview/pages/auth-lock.astro @@ -1,9 +1,9 @@ --- // Title is "Forgot password" (matches auth-lock card, not page name). -import SingleLayout from '@shared/layouts/SingleLayout.astro'; -import AuthLockCard from '@shared/components/cards/AuthLockCard.astro'; +import SingleLayout from '@shared/layouts/SingleLayout.astro' +import AuthLockCard from '@shared/components/cards/AuthLockCard.astro' --- - + diff --git a/preview/pages/avatars.astro b/preview/pages/avatars.astro index 345e2c6e1..6cda9e86e 100644 --- a/preview/pages/avatars.astro +++ b/preview/pages/avatars.astro @@ -1,166 +1,166 @@ --- -import DefaultLayout from '@shared/layouts/DefaultLayout.astro'; -import Avatar from '@ui/Avatar.astro'; -import AvatarUpload from '@ui/AvatarUpload.astro'; -import AvatarList from '@ui/AvatarList.astro'; -import Card from '@ui/Card.astro'; -import CardBody from '@ui/CardBody.astro'; -import CardTitle from '@ui/CardTitle.astro'; -import people from '@data/people.json'; -import { site } from '@shared/lib/site'; -import { firstLetters } from '@shared/lib/string-format'; +import DefaultLayout from '@shared/layouts/DefaultLayout.astro' +import Avatar from '@ui/Avatar.astro' +import AvatarUpload from '@ui/AvatarUpload.astro' +import AvatarList from '@ui/AvatarList.astro' +import Card from '@ui/Card.astro' +import CardBody from '@ui/CardBody.astro' +import CardTitle from '@ui/CardTitle.astro' +import people from '@data/people.json' +import { site } from '@shared/lib/site' +import { firstLetters } from '@shared/lib/string-format' -const iconIcons = ['user', 'settings', 'car', 'balloon', 'users', 'users-group', 'apps', 'ghost']; +const iconIcons = ['user', 'settings', 'car', 'balloon', 'users', 'users-group', 'apps', 'ghost'] // themeColors keys (blue..cyan). -const colors = site.themeColors; +const colors = site.themeColors -const people8 = people.slice(0, 8); -const people5 = people.slice(0, 5); -const sizes = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl']; -const listSizes = ['xxs', 'xs', 'sm', 'md', 'lg']; -const uploadSizes = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl']; -const statusColors = ['red', 'green', 'blue', 'yellow', 'secondary']; -const brands = ['netflix', 'amazon', 'messenger', 'figma', 'twitch']; +const people8 = people.slice(0, 8) +const people5 = people.slice(0, 5) +const sizes = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl'] +const listSizes = ['xxs', 'xs', 'sm', 'md', 'lg'] +const uploadSizes = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl'] +const statusColors = ['red', 'green', 'blue', 'yellow', 'secondary'] +const brands = ['netflix', 'amazon', 'messenger', 'figma', 'twitch'] --- -
-
- - - Default avatar - - - -
-
- - - Avatar with icon +
+
+ + + Default avatar + + + +
+
+ + + Avatar with icon -
- {iconIcons.map((icon) => )} -
-
-
-
-
- - - Avatar with icon +
+ {iconIcons.map((icon) => )} +
+
+
+
+
+ + + Avatar with icon -
- {colors.map((color) => )} -
-
-
-
-
- - - Simple avatar -
- {people8.map((person) => )} -
-
-
-
-
- - - Avatar placeholder -
- {people8.map((person) => )} -
-
-
-
-
- - - Avatar shapes -
- - - -
-
-
-
-
- - - Avatar sizes -
-
-
- {sizes.map((size) => )} -
-
-
-
- {sizes.map((size) => )} -
-
-
-
-
-
-
- - - Avatar lists -
- { - listSizes.map((size) => ( - <> -
- - {people5.map((person) => ( - - ))} - - -
-
- - {people5.map((person) => ( - - ))} - - -
- - )) - } -
-
-
-
-
- - - Avatar placeholder - {uploadSizes.map((size) => )} - - -
-
- - - Avatar statuses - {statusColors.map((color, i) => )} - - -
-
- - - Avatar brands - {brands.map((brand, i) => )} - - -
-
+
+ {colors.map((color) => )} +
+
+
+
+
+ + + Simple avatar +
+ {people8.map((person) => )} +
+
+
+
+
+ + + Avatar placeholder +
+ {people8.map((person) => )} +
+
+
+
+
+ + + Avatar shapes +
+ + + +
+
+
+
+
+ + + Avatar sizes +
+
+
+ {sizes.map((size) => )} +
+
+
+
+ {sizes.map((size) => )} +
+
+
+
+
+
+
+ + + Avatar lists +
+ { + listSizes.map((size) => ( + <> +
+ + {people5.map((person) => ( + + ))} + + +
+
+ + {people5.map((person) => ( + + ))} + + +
+ + )) + } +
+
+
+
+
+ + + Avatar placeholder + {uploadSizes.map((size) => )} + + +
+
+ + + Avatar statuses + {statusColors.map((color, i) => )} + + +
+
+ + + Avatar brands + {brands.map((brand, i) => )} + + +
+
diff --git a/preview/pages/badges.astro b/preview/pages/badges.astro index 7f41d180b..16343f9fd 100644 --- a/preview/pages/badges.astro +++ b/preview/pages/badges.astro @@ -1,206 +1,205 @@ --- - // colors = ['default'] + site.colors keys + ['dark', 'light'] -import BadgesList from '@ui/BadgesList.astro'; -import DefaultLayout from '@shared/layouts/DefaultLayout.astro'; -import Icon from '@ui/Icon.astro'; -import ButtonList from '@ui/ButtonList.astro'; -import Card from '@ui/Card.astro'; -import CardBody from '@ui/CardBody.astro'; -import CardTitle from '@ui/CardTitle.astro'; -import DropdownMenu from '@ui/DropdownMenu.astro'; -import site from '@data/site.json'; -import { ucFirst } from '@shared/lib/string-format'; +import BadgesList from '@ui/BadgesList.astro' +import DefaultLayout from '@shared/layouts/DefaultLayout.astro' +import Icon from '@ui/Icon.astro' +import ButtonList from '@ui/ButtonList.astro' +import Card from '@ui/Card.astro' +import CardBody from '@ui/CardBody.astro' +import CardTitle from '@ui/CardTitle.astro' +import DropdownMenu from '@ui/DropdownMenu.astro' +import site from '@data/site.json' +import { ucFirst } from '@shared/lib/string-format' -const colors = ['default', ...Object.keys(site.colors), 'dark', 'light']; -const sizes = ['sm', 'md', 'lg']; +const colors = ['default', ...Object.keys(site.colors), 'dark', 'light'] +const sizes = ['sm', 'md', 'lg'] --- -
-
-
-
- - -

Example heading New

-

Example heading New

-

Example heading New

-

Example heading New

-
Example heading New
-
Example heading New
-
-
-
-
- - - Badge sizes +
+
+
+
+ + +

Example heading New

+

Example heading New

+

Example heading New

+

Example heading New

+
Example heading New
+
Example heading New
+
+
+
+
+ + + Badge sizes -
- { - sizes.map((size) => ( - - Default - - Left icon - - - Right icon - - - - - - )) - } -
-
-
-
-
- - - Positioned badges +
+ { + sizes.map((size) => ( + + Default + + Left icon + + + Right icon + + + + + + + )) + } +
+
+
+
+
+ + + Positioned badges - - + + - + - + - + - + - - - - -
-
-
-
-
-
- - - Basic badges - - {colors.map((color) => {ucFirst(color)})} - - - -
-
- - - Light badges - - {colors.map((color) => {ucFirst(color)})} - - - -
-
- - - Outline badges - - {colors.map((color) => {ucFirst(color)})} - - - -
-
- - - Badges with icons - - { - colors.map((color) => ( - - {' '} - {ucFirst(color)}{' '} - - )) - } - - - -
-
-
-
-
-
-
- - - - { - colors.map((color, index) => ( - - )) - } - - - -
-
- - - - { - colors.map((color, index) => ( - - )) - } - - - -
-
- - - - { - colors.map((color) => ( - - )) - } - - - -
-
-
-
+ + +
+
+
+
+
+
+
+
+ + + Basic badges + + {colors.map((color) => {ucFirst(color)})} + + + +
+
+ + + Light badges + + {colors.map((color) => {ucFirst(color)})} + + + +
+
+ + + Outline badges + + {colors.map((color) => {ucFirst(color)})} + + + +
+
+ + + Badges with icons + + { + colors.map((color) => ( + + {' '} + {ucFirst(color)}{' '} + + )) + } + + + +
+
+
+
+
+
+
+ + + + { + colors.map((color, index) => ( + + )) + } + + + +
+
+ + + + { + colors.map((color, index) => ( + + )) + } + + + +
+
+ + + + { + colors.map((color) => ( + + )) + } + + + +
+
+
+
diff --git a/preview/pages/blank.astro b/preview/pages/blank.astro index 0324fb35b..bb5b4c25d 100644 --- a/preview/pages/blank.astro +++ b/preview/pages/blank.astro @@ -1,9 +1,9 @@ --- // front matter keys unused — empty page header comes from missing `page-header` key. -import DefaultLayout from '@shared/layouts/DefaultLayout.astro'; -import Empty from '@ui/Empty.astro'; +import DefaultLayout from '@shared/layouts/DefaultLayout.astro' +import Empty from '@ui/Empty.astro' --- - + diff --git a/preview/pages/buttons.astro b/preview/pages/buttons.astro index 530db5243..d90f934d7 100644 --- a/preview/pages/buttons.astro +++ b/preview/pages/buttons.astro @@ -1,191 +1,201 @@ --- -import DefaultLayout from '@shared/layouts/DefaultLayout.astro'; -import Icon from '@ui/Icon.astro'; -import Button from '@ui/Button.astro'; -import ButtonList from '@ui/ButtonList.astro'; -import Card from '@ui/Card.astro'; -import CardHeader from '@ui/CardHeader.astro'; -import CardBody from '@ui/CardBody.astro'; -import site from '@data/site.json'; +import DefaultLayout from '@shared/layouts/DefaultLayout.astro' +import Icon from '@ui/Icon.astro' +import Button from '@ui/Button.astro' +import ButtonList from '@ui/ButtonList.astro' +import Card from '@ui/Card.astro' +import CardHeader from '@ui/CardHeader.astro' +import CardBody from '@ui/CardBody.astro' +import site from '@data/site.json' -type ColorEntry = [string, { title: string; icon?: string }]; +type ColorEntry = [string, { title: string; icon?: string }] // site.json objects iterated as [key, value] pairs. // themeColors/colors entries have no icon — Icon renders nothing there. -const themeColors = Object.entries(site.themeColors) as ColorEntry[]; -const colors = Object.entries(site.colors) as ColorEntry[]; -const socialColors = Object.entries(site.socialColors) as ColorEntry[]; +const themeColors = Object.entries(site.themeColors) as ColorEntry[] +const colors = Object.entries(site.colors) as ColorEntry[] +const socialColors = Object.entries(site.socialColors) as ColorEntry[] -const actions = ['edit', 'copy', 'settings', 'clipboard', 'x']; -const sizes = ['sm', 'md', 'lg', 'xl']; +const actions = ['edit', 'copy', 'settings', 'clipboard', 'x'] +const sizes = ['sm', 'md', 'lg', 'xl'] --- -
-
- - - - - { - themeColors.map(([name, color]) => ( - {color.icon && } {color.title} - )) - } - - - -
-
- - - - - { - themeColors.map(([name, color]) => ( - {color.icon && } {color.title} - )) - } - - - -
-
- - - - - { - themeColors.map(([name, color]) => ( - {color.icon && } {color.title} - )) - } - - - -
-
- - - - - { - themeColors.map(([name, color]) => ( - {color.icon && } {color.title} - )) - } - - - -
-
- - - - - { - themeColors.map(([name, color]) => ( - {color.icon && } {color.title} - )) - } - - - -
-
- - - - - { - colors.map(([name, color]) => ( - {color.icon && } {color.title} - )) - } - - - -
-
- - - - - { - socialColors.map(([name, app]) => ( - {app.icon && } - )) - } - - - -
-
- - - - - { - socialColors.map(([name, app]) => ( - {app.icon && } {app.title} - )) - } - - - -
-
- - - -
- { - actions.map((action) => ( - - - - )) - } -
-
-
-
- -
- - - -
- { - sizes.map((size) => ( - -
-
-
-
-
+
+
+ + + + + { + themeColors.map(([name, color]) => ( + + {color.icon && } {color.title} + + )) + } + + + +
+
+ + + + + { + themeColors.map(([name, color]) => ( + + {color.icon && } {color.title} + + )) + } + + + +
+
+ + + + + { + themeColors.map(([name, color]) => ( + + {color.icon && } {color.title} + + )) + } + + + +
+
+ + + + + { + themeColors.map(([name, color]) => ( + + {color.icon && } {color.title} + + )) + } + + + +
+
+ + + + + { + themeColors.map(([name, color]) => ( + + {color.icon && } {color.title} + + )) + } + + + +
+
+ + + + + { + colors.map(([name, color]) => ( + + {color.icon && } {color.title} + + )) + } + + + +
+
+ + + + + {socialColors.map(([name, app]) => {app.icon && })} + + + +
+
+ + + + + { + socialColors.map(([name, app]) => ( + + {app.icon && } {app.title} + + )) + } + + + +
+
+ + + +
+ { + actions.map((action) => ( + + + + )) + } +
+
+
+
+ +
+ + + +
+ { + sizes.map((size) => ( + +
+
+
+
+
diff --git a/preview/pages/changelog.astro b/preview/pages/changelog.astro index ab2d22452..45fa71d10 100644 --- a/preview/pages/changelog.astro +++ b/preview/pages/changelog.astro @@ -1,11 +1,11 @@ --- -import ProseLayout from '@shared/layouts/ProseLayout.astro'; -import { renderMarkdown } from '@shared/lib/render-markdown'; -import changelog from '../../core/CHANGELOG.md?raw'; +import ProseLayout from '@shared/layouts/ProseLayout.astro' +import { renderMarkdown } from '@shared/lib/render-markdown' +import changelog from '../../core/CHANGELOG.md?raw' -const changelogHtml = renderMarkdown(changelog); +const changelogHtml = renderMarkdown(changelog) --- - + diff --git a/preview/pages/colorpicker.astro b/preview/pages/colorpicker.astro index dbc9e0e17..762b4f93d 100644 --- a/preview/pages/colorpicker.astro +++ b/preview/pages/colorpicker.astro @@ -1,34 +1,29 @@ --- -import DefaultLayout from '@shared/layouts/DefaultLayout.astro'; -import Card from '@ui/Card.astro'; -import CardBody from '@ui/CardBody.astro'; -import CardTitle from '@ui/CardTitle.astro'; -import Colorpicker from '@ui/Colorpicker.astro'; -import site from '@data/site.json'; +import DefaultLayout from '@shared/layouts/DefaultLayout.astro' +import Card from '@ui/Card.astro' +import CardBody from '@ui/CardBody.astro' +import CardTitle from '@ui/CardTitle.astro' +import Colorpicker from '@ui/Colorpicker.astro' +import site from '@data/site.json' -const colors = Object.values(site.colors) as { hex: string }[]; +const colors = Object.values(site.colors) as { hex: string }[] --- - - - - Basic -
- { - colors.map((color, index) => ( -
-
- -
-
- )) - } -
-
-
+ + + + Basic +
+ { + colors.map((color, index) => ( +
+
+ +
+
+ )) + } +
+
+
diff --git a/preview/pages/colors.astro b/preview/pages/colors.astro index 5ee77374d..378e29578 100644 --- a/preview/pages/colors.astro +++ b/preview/pages/colors.astro @@ -1,247 +1,236 @@ --- -import DefaultLayout from '@shared/layouts/DefaultLayout.astro'; -import CaptureScript from '@shared/components/CaptureScript.astro'; -import FormGroup from '@ui/FormGroup.astro'; -import Card from '@ui/Card.astro'; -import CardBody from '@ui/CardBody.astro'; -import Avatar from '@ui/Avatar.astro'; -import CardTitle from '@ui/CardTitle.astro'; -import site from '@data/site.json'; +import DefaultLayout from '@shared/layouts/DefaultLayout.astro' +import CaptureScript from '@shared/components/CaptureScript.astro' +import FormGroup from '@ui/FormGroup.astro' +import Card from '@ui/Card.astro' +import CardBody from '@ui/CardBody.astro' +import Avatar from '@ui/Avatar.astro' +import CardTitle from '@ui/CardTitle.astro' +import site from '@data/site.json' -type ColorEntry = [string, { title: string; hex: string; abbr?: string; icon?: string }]; +type ColorEntry = [string, { title: string; hex: string; abbr?: string; icon?: string }] -const colors = Object.entries(site.colors) as ColorEntry[]; -const lightColors = Object.entries(site.lightColors) as ColorEntry[]; -const grayColors = Object.entries(site.grayColors) as ColorEntry[]; -const socialColors = Object.entries(site.socialColors) as ColorEntry[]; +const colors = Object.entries(site.colors) as ColorEntry[] +const lightColors = Object.entries(site.lightColors) as ColorEntry[] +const grayColors = Object.entries(site.grayColors) as ColorEntry[] +const socialColors = Object.entries(site.socialColors) as ColorEntry[] -const gradientColors = [...Object.keys(site.colors), 'inverted', 'white', 'transparent']; +const gradientColors = [...Object.keys(site.colors), 'inverted', 'white', 'transparent'] --- -
-
- - -
- { - colors.map(([name, color]) => ( -
-
-
- -
-
- {color.title} -
- {color.hex} -
-
-
- )) - } -
-
-
-
-
- - -
- { - lightColors.map(([name, color]) => ( -
-
-
- -
-
- {color.title} -
- {color.hex} -
-
-
- )) - } -
-
-
-
-
- - -
- { - grayColors.map(([name, color]) => ( -
-
-
- -
-
- {color.title} -
- {color.hex} -
-
-
- )) - } -
-
-
-
-
- - -
- { - socialColors.map(([name, color]) => ( -
-
-
- -
-
- {color.title} -
- {color.hex} -
-
-
- )) - } -
-
-
-
-
-
-
- - - Gradient -
-
-
- - - - - - -
-
- - - - - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
- - -
-
-
- { - gradientColors.map((color) => ( -
- )) - } -
-
-
-
- { - gradientColors.map((color) => ( -
- )) - } -
-
-
- - -
-
-
-
+
+
+ + +
+ { + colors.map(([name, color]) => ( +
+
+
+ +
+
+ {color.title} +
+ {color.hex} +
+
+
+ )) + } +
+
+
+
+
+ + +
+ { + lightColors.map(([name, color]) => ( +
+
+
+ +
+
+ {color.title} +
+ {color.hex} +
+
+
+ )) + } +
+
+
+
+
+ + +
+ { + grayColors.map(([name, color]) => ( +
+
+
+ +
+
+ {color.title} +
+ {color.hex} +
+
+
+ )) + } +
+
+
+
+
+ + +
+ { + socialColors.map(([name, color]) => ( +
+
+
+ +
+
+ {color.title} +
+ {color.hex} +
+
+
+ )) + } +
+
+
+
+
+
+
+ + + Gradient +
+
+
+ + + + + + +
+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+ {gradientColors.map((color) =>
)} +
+
+
+
+ {gradientColors.map((color) =>
)} +
+
+
+ + +
+
+
+
- - - - - + updateGradient() + }) + + + diff --git a/preview/pages/dashboard-crypto.astro b/preview/pages/dashboard-crypto.astro index 25ea481c7..a322deab1 100644 --- a/preview/pages/dashboard-crypto.astro +++ b/preview/pages/dashboard-crypto.astro @@ -1,337 +1,332 @@ --- -import CardActions from '@ui/CardActions.astro'; -import DefaultLayout from '@shared/layouts/DefaultLayout.astro'; -import Avatar from '@ui/Avatar.astro'; -import Trending from '@ui/Trending.astro'; -import CardDropdown from '@ui/CardDropdown.astro'; -import Card from '@ui/Card.astro'; -import CardHeader from '@ui/CardHeader.astro'; -import CardBody from '@ui/CardBody.astro'; -import NavSegmented from '@ui/NavSegmented.astro'; -import Chart from '@ui/Chart.astro'; -import SwitchIcon from '@ui/SwitchIcon.astro'; -import cryptoCurrencies from '@data/crypto-currencies.json'; -import cryptoMarkets from '@data/crypto-markets.json'; -import cryptoOrders from '@data/crypto-orders.json'; -import { parseCurrency, roundTo } from '@shared/lib/string-format'; +import CardActions from '@ui/CardActions.astro' +import DefaultLayout from '@shared/layouts/DefaultLayout.astro' +import Avatar from '@ui/Avatar.astro' +import Trending from '@ui/Trending.astro' +import CardDropdown from '@ui/CardDropdown.astro' +import Card from '@ui/Card.astro' +import CardHeader from '@ui/CardHeader.astro' +import CardBody from '@ui/CardBody.astro' +import NavSegmented from '@ui/NavSegmented.astro' +import Chart from '@ui/Chart.astro' +import SwitchIcon from '@ui/SwitchIcon.astro' +import cryptoCurrencies from '@data/crypto-currencies.json' +import cryptoMarkets from '@data/crypto-markets.json' +import cryptoOrders from '@data/crypto-orders.json' +import { parseCurrency, roundTo } from '@shared/lib/string-format' interface Currency { - symbol: string; - price: string; - p24h: number; - 'volume-24h': string; + 'symbol': string + 'price': string + 'p24h': number + 'volume-24h': string } -const currencies = cryptoCurrencies as Currency[]; -const find = (symbol: string) => currencies.find((c) => c.symbol === symbol)!; -const btc = find('BTC'); -const ltc = find('LTC'); -const eth = find('ETH'); -const xmr = find('XMR'); +const currencies = cryptoCurrencies as Currency[] +const find = (symbol: string) => currencies.find((c) => c.symbol === symbol)! +const btc = find('BTC') +const ltc = find('LTC') +const eth = find('ETH') +const xmr = find('XMR') -const btcBalance = 2.3; -const btcPriceNum = parseCurrency(btc.price); -const totalUsd = Math.round(btcPriceNum * btcBalance).toLocaleString('en-US'); +const btcBalance = 2.3 +const btcPriceNum = parseCurrency(btc.price) +const totalUsd = Math.round(btcPriceNum * btcBalance).toLocaleString('en-US') // divided_by then round to 8 decimals (trailing zeros dropped in output) -const ltcBtc = roundTo(parseCurrency(ltc.price) / btcPriceNum); -const ethBtc = roundTo(parseCurrency(eth.price) / btcPriceNum); -const xmrBtc = roundTo(parseCurrency(xmr.price) / btcPriceNum); +const ltcBtc = roundTo(parseCurrency(ltc.price) / btcPriceNum) +const ethBtc = roundTo(parseCurrency(eth.price) / btcPriceNum) +const xmrBtc = roundTo(parseCurrency(xmr.price) / btcPriceNum) -const markets = (cryptoMarkets as { coin: string; price: string; volume: string; change: string }[]).slice(0, 10); -const orders = cryptoOrders as { sell_orders: { price: string; btc: string; sum: string }[]; buy_orders: { price: string; btc: string; sum: string }[] }; -const operationCurrencies = currencies.slice(0, 20); +const markets = (cryptoMarkets as { coin: string; price: string; volume: string; change: string }[]).slice(0, 10) +const orders = cryptoOrders as { sell_orders: { price: string; btc: string; sum: string }[]; buy_orders: { price: string; btc: string; sum: string }[] } +const operationCurrencies = currencies.slice(0, 20) --- - -
-
-
-
- - -
-
-
Total balance
-
-
- -
-
-
- ${totalUsd} - {btcBalance} {btc.symbol} -
-
- - Since last week -
-
-
-
+ +
+
+
+
+ + +
+
+
Total balance
+
+
+ +
+
+
+ ${totalUsd} + {btcBalance} {btc.symbol} +
+
+ + Since last week +
+
+
+
-
- - -
-
-
USD/BTC
-
-
- -
-
-
- {btc.price} - {btc.price} -
-
- Volume: {btc['volume-24h']} -
-
-
-
+
+ + +
+
+
USD/BTC
+
+
+ +
+
+
+ {btc.price} + {btc.price} +
+
+ Volume: {btc['volume-24h']} +
+
+
+
-
- - -
-
-
LTC/BTC
-
-
- -
-
-
- {ltcBtc} - {ltc.price} -
-
- Volume: {ltc['volume-24h'].replace('$', '')} -
-
-
-
+
+ + +
+
+
LTC/BTC
+
+
+ +
+
+
+ {ltcBtc} + {ltc.price} +
+
+ Volume: {ltc['volume-24h'].replace('$', '')} +
+
+
+
-
- - -
-
-
ETH/BTC
-
-
- -
-
-
- {ethBtc} - {eth.price} -
-
- Volume: {eth['volume-24h'].replace('$', '')} -
-
-
-
+
+ + +
+
+
ETH/BTC
+
+
+ +
+
+
+ {ethBtc} + {eth.price} +
+
+ Volume: {eth['volume-24h'].replace('$', '')} +
+
+
+
-
- - -
-
-
XMR/BTC
-
-
- -
-
-
- {xmrBtc} - {xmr.price} -
-
- Volume: {xmr['volume-24h'].replace('$', '')} -
-
-
-
-
-
-
-
-
- - -
Markets
- - - -
- - - - - - - - - - - - { - markets.map((market) => ( - - - - - - - - )) - } - -
- - CoinPriceVolumeChange
- - {market.coin}{market.price}{market.volume} - -
-
-
+
+ + +
+
+
XMR/BTC
+
+
+ +
+
+
+ {xmrBtc} + {xmr.price} +
+
+ Volume: {xmr['volume-24h'].replace('$', '')} +
+
+
+
+
+
+
+
+
+ + +
Markets
+ + + +
+ + + + + + + + + + + + { + markets.map((market) => ( + + + + + + + + )) + } + +
+ + CoinPriceVolumeChange
+ + {market.coin}{market.price}{market.volume} + +
+
+
-
- - -
LTC/BTC
- - - -
- - - -
-
-
-
-
-
-
- - -
Operations
- - - -
+
+ + +
LTC/BTC
+ + + +
+ + + +
+
+
+
+
+
+
+ + +
Operations
+ + + +
- -

Place new order:

+ +

Place new order:

-
- - - -
+
+ + + +
-
- - - -
+
+ + + +
-
- - - -
+
+ + + +
-
- -
+
+ +
-

The final amount could change depending on current market conditions.

-
-
-
+

The final amount could change depending on current market conditions.

+ + +
-
- - -
Sell Orders
- - - -
- - - - - - - - - - { - orders.sell_orders.map((order) => ( - - - - - - )) - } - -
PriceBTCSum(BTC)
{order.price}{order.btc}{order.sum}
-
-
+
+ + +
Sell Orders
+ + + +
+ + + + + + + + + + { + orders.sell_orders.map((order) => ( + + + + + + )) + } + +
PriceBTCSum(BTC)
{order.price}{order.btc}{order.sum}
+
+
-
- - -
Buy Orders
- - - -
- - - - - - - - - - { - orders.buy_orders.map((order) => ( - - - - - - )) - } - -
PriceBTCSum(BTC)
{order.price}{order.btc}{order.sum}
-
-
-
-
-
+
+ + +
Buy Orders
+ + + +
+ + + + + + + + + + { + orders.buy_orders.map((order) => ( + + + + + + )) + } + +
PriceBTCSum(BTC)
{order.price}{order.btc}{order.sum}
+
+
+
+
+
diff --git a/preview/pages/email-inbox.astro b/preview/pages/email-inbox.astro index 59b02a1ec..2a7b19154 100644 --- a/preview/pages/email-inbox.astro +++ b/preview/pages/email-inbox.astro @@ -1,208 +1,215 @@ --- +import Subheader from '@ui/Subheader.astro' +import ButtonGroup from '@ui/ButtonGroup.astro' +import DefaultLayout from '@shared/layouts/DefaultLayout.astro' +import Button from '@ui/Button.astro' +import Icon from '@ui/Icon.astro' +import Card from '@ui/Card.astro' +import Offcanvas from '@ui/Offcanvas.astro' +import Progress from '@ui/Progress.astro' +import Modal from '@shared/components/modals/Modal.astro' +import CaptureModal from '@shared/components/CaptureModal.astro' +import NewEmailModalContent from '@shared/components/modals/NewEmailModalContent.astro' +import mails from '@data/mails.json' +--- -import Subheader from '@ui/Subheader.astro'; -import ButtonGroup from '@ui/ButtonGroup.astro'; -import DefaultLayout from '@shared/layouts/DefaultLayout.astro'; -import Button from '@ui/Button.astro'; -import Icon from '@ui/Icon.astro'; -import Card from '@ui/Card.astro'; -import Offcanvas from '@ui/Offcanvas.astro'; -import Progress from '@ui/Progress.astro'; -import Modal from '@shared/components/modals/Modal.astro'; -import CaptureModal from '@shared/components/CaptureModal.astro'; -import NewEmailModalContent from '@shared/components/modals/NewEmailModalContent.astro'; -import mails from '@data/mails.json'; ---- + + +
+ -
-
- - - - - - + + + + +
diff --git a/preview/pages/emails.astro b/preview/pages/emails.astro index d71f66e10..8be91c70c 100644 --- a/preview/pages/emails.astro +++ b/preview/pages/emails.astro @@ -1,107 +1,98 @@ --- // Masonry auto-inits from the data-masonry attribute (page-lib, no init script, // like cards-masonry.astro); fslightbox is a pure page-lib (no init script). -import DefaultLayout from '@shared/layouts/DefaultLayout.astro'; -import CardStamp from '@ui/CardStamp.astro'; -import Prose from '@ui/Prose.astro'; -import CaptureScript from '@shared/components/CaptureScript.astro'; -import { site } from '@shared/lib/site'; -import emails from '@data/emails.json'; +import DefaultLayout from '@shared/layouts/DefaultLayout.astro' +import CardStamp from '@ui/CardStamp.astro' +import Prose from '@ui/Prose.astro' +import CaptureScript from '@shared/components/CaptureScript.astro' +import { site } from '@shared/lib/site' +import emails from '@data/emails.json' -const emailEntries = Object.entries(emails); +const emailEntries = Object.entries(emails) --- - -
-
-
- -
-
-
-

Tabler Emails

- - {emailEntries.length} eye-catching, customizable and responsive email templates to improve your email communication. No coding skills needed. - - -
-
-
-
-
-
-
- {emailEntries.map(([key, email]) => ( -
- - {email.descriptionShort} - -
- ))} -
-
-
+ +
+
+
+ +
+
+
+

Tabler Emails

+ + {emailEntries.length} eye-catching, customizable and responsive email templates to improve your email communication. No coding skills needed. + + +
+
+
+
+
+
+
+ { + emailEntries.map(([key, email]) => ( +
+ + {email.descriptionShort} + +
+ )) + } +
+
+
-