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.
This commit is contained in:
codecalm
2026-08-03 01:26:19 +02:00
parent 5246f0b6fd
commit f27c70ad38
166 changed files with 9678 additions and 10612 deletions
+65 -65
View File
@@ -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 <scssDir> <outDir> [--rtl] [--minify]')
process.exit(1)
console.error('usage: tsx build-css.ts <scssDir> <outDir> [--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<void> {
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<void> {
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)
})
+115 -115
View File
@@ -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<typeof setTimeout> | 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<typeof setTimeout> | 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)
},
},
}
}
+12 -12
View File
@@ -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<string, unknown>)
minify(input: string[]): Promise<Record<string, MinifyResult>>
}
interface MinifyResult {
styles: string
errors: string[]
warnings: string[]
sourceMap: { toString(): string }
}
export default class CleanCSS {
constructor(options: Record<string, unknown>)
minify(input: string[]): Promise<Record<string, MinifyResult>>
}
}
+1 -1
View File
@@ -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({
+52 -64
View File
@@ -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[]
---
<!-- BEGIN DOCS MENU -->
<nav class="space-y space-y-5" id="menu">
{
menu.map((level1) => (
<div>
<Subheader class="mb-2">{level1.title}</Subheader>
{level1.children && level1.children.length > 0 && (
<nav class="nav nav-vertical">
{level1.children.map((level2) => {
const hasChildren = Boolean(level2.children && level2.children.length > 0);
// Expanded only for the group's own page or one of its direct children —
// a prefix check would also expand "Introduction" on /ui/getting-started/frameworks/* pages.
const expanded = url === level2.url || Boolean(level2.children?.some((child) => child.url === url));
return (
<div>
{/* href/data-bs-* only when the element has children */}
{hasChildren ? (
<a
class={`nav-link${expanded ? ' active' : ''}`}
href={level2.url}
data-bs-toggle="collapse"
data-bs-target={`#collapse-${slug(level2.url)}`}
aria-expanded={expanded ? 'true' : 'false'}
>
{level2.title} <span class="nav-link-toggle" />
</a>
) : (
<a class={`nav-link${expanded ? ' active' : ''}`}>{level2.title}</a>
)}
{
menu.map((level1) => (
<div>
<Subheader class="mb-2">{level1.title}</Subheader>
{level1.children && level1.children.length > 0 && (
<nav class="nav nav-vertical">
{level1.children.map((level2) => {
const hasChildren = Boolean(level2.children && level2.children.length > 0)
// Expanded only for the group's own page or one of its direct children —
// a prefix check would also expand "Introduction" on /ui/getting-started/frameworks/* pages.
const expanded = url === level2.url || Boolean(level2.children?.some((child) => child.url === url))
return (
<div>
{/* href/data-bs-* only when the element has children */}
{hasChildren ? (
<a class={`nav-link${expanded ? ' active' : ''}`} href={level2.url} data-bs-toggle="collapse" data-bs-target={`#collapse-${slug(level2.url)}`} aria-expanded={expanded ? 'true' : 'false'}>
{level2.title} <span class="nav-link-toggle" />
</a>
) : (
<a class={`nav-link${expanded ? ' active' : ''}`}>{level2.title}</a>
)}
{hasChildren && (
<nav
class={`nav nav-vertical collapse${expanded ? ' show' : ''}`}
id={`collapse-${slug(level2.url)}`}
>
{level2.children!.map((level3) => (
<div>
<a
class={`nav-link${url === level3.url ? ' active' : ''}`}
href={level3.url}
>
{level3.title}
</a>
</div>
))}
</nav>
)}
</div>
);
})}
</nav>
)}
</div>
))
}
{hasChildren && (
<nav class={`nav nav-vertical collapse${expanded ? ' show' : ''}`} id={`collapse-${slug(level2.url)}`}>
{level2.children!.map((level3) => (
<div>
<a class={`nav-link${url === level3.url ? ' active' : ''}`} href={level3.url}>
{level3.title}
</a>
</div>
))}
</nav>
)}
</div>
)
})}
</nav>
)}
</div>
))
}
</nav>
<!-- END DOCS MENU -->
+53 -53
View File
@@ -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'
---
<!-- BEGIN DOCS NAVBAR -->
<div class="navbar navbar-expand sticky-top">
<div class="container">
<div class="row flex-fill align-items-md-center">
<div class="col">
<div class="d-flex align-items-center gap-4">
{/* href="." — relative link for docs root */}
<a href="." class="navbar-brand navbar-brand-autodark gap-4">
<DocsLogo />
</a>
<div>
<span class="badge">v{site.version}</span>
</div>
</div>
</div>
<div class="d-none d-md-block col">
<div id="docsearch"></div>
</div>
<div class="col d-flex">
<ul class="navbar-nav ms-auto gap-2 align-items-center">
<div class="nav-item d-none d-md-block">
<a href={previewUrl} class="nav-link">Preview</a>
</div>
<div class="nav-item d-none d-md-block">
<a href={changelogUrl} class="nav-link">Changelog</a>
</div>
<li class="nav-item hide-theme-dark">
<a href="?theme=dark" class="btn btn-icon">
<Icon name="moon" />
</a>
</li>
<li class="nav-item hide-theme-light">
<a href="?theme=light" class="btn btn-icon">
<Icon name="sun" />
</a>
</li>
<li class="nav-item">
<a href={site.githubUrl} class="btn btn-icon" target="_blank">
<Icon name="brand-github" />
</a>
</li>
<li class="nav-item">
<a href={previewUrl} class="btn btn-primary" target="_blank">
<Icon name="eye" /> Preview
</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="row flex-fill align-items-md-center">
<div class="col">
<div class="d-flex align-items-center gap-4">
{/* href="." — relative link for docs root */}
<a href="." class="navbar-brand navbar-brand-autodark gap-4">
<DocsLogo />
</a>
<div>
<span class="badge">v{site.version}</span>
</div>
</div>
</div>
<div class="d-none d-md-block col">
<div id="docsearch"></div>
</div>
<div class="col d-flex">
<ul class="navbar-nav ms-auto gap-2 align-items-center">
<div class="nav-item d-none d-md-block">
<a href={previewUrl} class="nav-link">Preview</a>
</div>
<div class="nav-item d-none d-md-block">
<a href={changelogUrl} class="nav-link">Changelog</a>
</div>
<li class="nav-item hide-theme-dark">
<a href="?theme=dark" class="btn btn-icon">
<Icon name="moon" />
</a>
</li>
<li class="nav-item hide-theme-light">
<a href="?theme=light" class="btn btn-icon">
<Icon name="sun" />
</a>
</li>
<li class="nav-item">
<a href={site.githubUrl} class="btn btn-icon" target="_blank">
<Icon name="brand-github" />
</a>
</li>
<li class="nav-item">
<a href={previewUrl} class="btn btn-primary" target="_blank">
<Icon name="eye" /> Preview
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<!-- END DOCS NAVBAR -->
+83 -89
View File
@@ -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,
}
---
<!-- BEGIN DOCS PAGINATION -->
{
children.length > 0 && (
<div class="mt-6 pt-6">
<div class="row row-deck row-cards">
{children.map((child: DocsChildPage) => (
<DocsCard
href={child.url}
title={child.title}
description={child.description}
icon={child.icon}
/>
))}
</div>
</div>
)
<!-- BEGIN DOCS PAGINATION -->{
children.length > 0 && (
<div class="mt-6 pt-6">
<div class="row row-deck row-cards">
{children.map((child: DocsChildPage) => (
<DocsCard href={child.url} title={child.title} description={child.description} icon={child.icon} />
))}
</div>
</div>
)
}
{
children.length === 0 && found && (
<div class="mt-6 pt-6">
<ul class="pagination">
{prev && (
<li class="page-item page-prev">
<a class="page-link" href={prev.url}>
<div class="row align-items-center">
<div class="col-auto">
<Icon name="chevron-left" />
</div>
<div class="col">
<div class="page-item-subtitle">previous</div>
<div class="page-item-title">{prev.title}</div>
</div>
</div>
</a>
</li>
)}
{next && (
<li class="page-item page-next">
<a class="page-link" href={next.url}>
<div class="row align-items-center">
<div class="col">
<div class="page-item-subtitle">next</div>
<div class="page-item-title">{next.title}</div>
</div>
<div class="col-auto">
<Icon name="chevron-right" />
</div>
</div>
</a>
</li>
)}
</ul>
</div>
)
children.length === 0 && found && (
<div class="mt-6 pt-6">
<ul class="pagination">
{prev && (
<li class="page-item page-prev">
<a class="page-link" href={prev.url}>
<div class="row align-items-center">
<div class="col-auto">
<Icon name="chevron-left" />
</div>
<div class="col">
<div class="page-item-subtitle">previous</div>
<div class="page-item-title">{prev.title}</div>
</div>
</div>
</a>
</li>
)}
{next && (
<li class="page-item page-next">
<a class="page-link" href={next.url}>
<div class="row align-items-center">
<div class="col">
<div class="page-item-subtitle">next</div>
<div class="page-item-title">{next.title}</div>
</div>
<div class="col-auto">
<Icon name="chevron-right" />
</div>
</div>
</a>
</li>
)}
</ul>
</div>
)
}
<!-- END DOCS PAGINATION -->
+25 -26
View File
@@ -3,44 +3,43 @@
// markdown content, excluding <!--EXAMPLE-->...<!--/EXAMPLE--> 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
---
<!-- BEGIN DOCS TOC -->
{
toc.length > 0 && (
<Fragment>
<h3>Table of Contents</h3>
<div class="nav nav-vertical" id="toc">
{toc.map((item) => (
<a href={`#${item.id}`} class={`nav-link${item.level === 3 ? ' ms-3' : ''}`}>
{item.text}
</a>
))}
</div>
</Fragment>
)
<!-- BEGIN DOCS TOC -->{
toc.length > 0 && (
<Fragment>
<h3>Table of Contents</h3>
<div class="nav nav-vertical" id="toc">
{toc.map((item) => (
<a href={`#${item.id}`} class={`nav-link${item.level === 3 ? ' ms-3' : ''}`}>
{item.text}
</a>
))}
</div>
</Fragment>
)
}
<a href="https://r.tabler.io/illustrations" class="card card-sm mt-6 shadow-none" target="_blank">
<div class="card-body">
<img src="/img/banner-carbon.png" class="mb-3" alt="" />
<div class="card-body">
<img src="/img/banner-carbon.png" class="mb-3" alt="" />
<h4>{illustrationsCount} sleek illustrations for your startup's visual identity.</h4>
</div>
<h4>{illustrationsCount} sleek illustrations for your startup's visual identity.</h4>
</div>
</a>
<!-- END DOCS TOC -->
+44 -75
View File
@@ -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(/&#39;/g, "'");
html = html.replace(/<(path|circle|line|polyline|polygon|rect|ellipse)([^>]*?)\s*><\/\1>/g, '<$1$2 />').replace(/&#39;/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')
---
<!--EXAMPLE-->
{
!codeOnly && (
<div class:list={exampleClasses} style={height ? `height: ${height}` : undefined}>
{
raw ? (
<Fragment set:html={removeHref(html)} />
) : (
<div class:list={innerClasses} style={column ? 'max-width: 25rem;' : undefined}>
<Fragment set:html={removeHref(html)} />
</div>
)
}
</div>
)
<!--EXAMPLE-->{
!codeOnly && (
<div class:list={exampleClasses} style={height ? `height: ${height}` : undefined}>
{raw ? (
<Fragment set:html={removeHref(html)} />
) : (
<div class:list={innerClasses} style={column ? 'max-width: 25rem;' : undefined}>
<Fragment set:html={removeHref(html)} />
</div>
)}
</div>
)
}
{
!hideCode && (
<div class="position-relative">
<a class="btn btn-icon btn-dark position-absolute m-2 top-0 end-0 z-3" data-clipboard-text={html}>
<Icon name="clipboard" />
<Icon name="check" class="d-none" />
</a>
<Fragment set:html={highlighted} />
</div>
)
!hideCode && (
<div class="position-relative">
<a class="btn btn-icon btn-dark position-absolute m-2 top-0 end-0 z-3" data-clipboard-text={html}>
<Icon name="clipboard" />
<Icon name="check" class="d-none" />
</a>
<Fragment set:html={highlighted} />
</div>
)
}
<!--/EXAMPLE-->
+1 -1
View File
@@ -16,7 +16,7 @@ interface MdxFrontmatter {
'summary'?: string
'description'?: string
'seoDescription'?: string
'added-in'?: string;
'added-in'?: string
'docs-libs'?: string[]
'hide-pagination'?: boolean
}
+1 -1
View File
@@ -1,5 +1,5 @@
---
import RedirectLayout from '@shared/layouts/RedirectLayout.astro';
import RedirectLayout from '@shared/layouts/RedirectLayout.astro'
---
<RedirectLayout base="../../.." />
+159 -159
View File
@@ -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 <pre> 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 <pre> 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
},
},
],
},
},
})
+69 -80
View File
@@ -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'
---
<SingleLayout title="2-Step Verification">
<form class="card card-md" action="./" method="get" autocomplete="off" novalidate>
<div class="card-body">
<h2 class="card-title card-title-lg text-center mb-4">Authenticate Your Account</h2>
<form class="card card-md" action="./" method="get" autocomplete="off" novalidate>
<div class="card-body">
<h2 class="card-title card-title-lg text-center mb-4">Authenticate Your Account</h2>
<p class="my-4 text-center">
Please confirm your account by entering the authorization code sent to <strong
>+1 856-672-8552</strong
>.
</p>
<p class="my-4 text-center">
Please confirm your account by entering the authorization code sent to <strong>+1 856-672-8552</strong>.
</p>
<div class="my-5">
<div class="row g-4">
{
Array.from({ length: 2 }).map(() => (
<div class="col">
<div class="row g-2">
{Array.from({ length: 3 }).map(() => (
<div class="col">
<input
type="text"
class="form-control form-control-lg text-center px-3 py-3"
maxlength="1"
inputmode="numeric"
pattern="[0-9]*"
data-code-input
/>
</div>
))}
</div>
</div>
))
}
</div>
</div>
<div class="my-5">
<div class="row g-4">
{
Array.from({ length: 2 }).map(() => (
<div class="col">
<div class="row g-2">
{Array.from({ length: 3 }).map(() => (
<div class="col">
<input type="text" class="form-control form-control-lg text-center px-3 py-3" maxlength="1" inputmode="numeric" pattern="[0-9]*" data-code-input />
</div>
))}
</div>
</div>
))
}
</div>
</div>
<div class="my-4">
<label class="form-check">
<input type="checkbox" class="form-check-input" />
Dont't ask for codes again on this device
</label>
</div>
<div class="my-4">
<label class="form-check">
<input type="checkbox" class="form-check-input" />
Dont't ask for codes again on this device
</label>
</div>
<FormFooter>
<ButtonList class="flex-nowrap">
<Button text="Cancel" block href="2-step-verification.html" />
<Button text="Verify" block color="primary" />
</ButtonList>
</FormFooter>
</div>
</form>
<FormFooter>
<ButtonList class="flex-nowrap">
<Button text="Cancel" block href="2-step-verification.html" />
<Button text="Verify" block color="primary" />
</ButtonList>
</FormFooter>
</div>
</form>
<CaptureScript>
<!-- BEGIN CODE INPUT SCRIPT -->
<script is:inline>
const inputs = document.querySelectorAll('[data-code-input]');
<CaptureScript>
<!-- BEGIN CODE INPUT SCRIPT -->
<script is:inline>
const inputs = document.querySelectorAll('[data-code-input]')
// Attach an event listener to each input element
inputs.forEach((input, i) => {
input.addEventListener('input', (e) => {
const target = e.target;
// If the input field has a character, and there is a next input field, focus it
if (target.value.length === target.maxLength && i + 1 < inputs.length) {
inputs[i + 1].focus();
}
});
// Attach an event listener to each input element
inputs.forEach((input, i) => {
input.addEventListener('input', (e) => {
const target = e.target
// If the input field has a character, and there is a next input field, focus it
if (target.value.length === target.maxLength && i + 1 < inputs.length) {
inputs[i + 1].focus()
}
})
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();
}
});
});
</script>
<!-- END CODE INPUT SCRIPT -->
</CaptureScript>
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()
}
})
})
</script>
<!-- END CODE INPUT SCRIPT -->
</CaptureScript>
<div class="text-center text-secondary mt-3">
It may take a minute to receive your code. Haven't received it? <a href="./"
>Resend a new code.</a
>
</div>
<div class="text-center text-secondary mt-3">
It may take a minute to receive your code. Haven't received it? <a href="./">Resend a new code.</a>
</div>
</SingleLayout>
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -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'
---
<SingleLayout title="Forgot password">
<AuthLockCard />
<AuthLockCard />
</SingleLayout>
+155 -155
View File
@@ -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']
---
<DefaultLayout title="Avatars" pageMenu="base.avatars" pageHeader="Avatars">
<div class="row row-cards">
<div class="col-4">
<Card>
<CardBody>
<CardTitle as="h2">Default avatar</CardTitle>
<Avatar />
</CardBody>
</Card>
</div>
<div class="col-4">
<Card>
<CardBody>
<CardTitle as="h2">Avatar with icon</CardTitle>
<div class="row row-cards">
<div class="col-4">
<Card>
<CardBody>
<CardTitle as="h2">Default avatar</CardTitle>
<Avatar />
</CardBody>
</Card>
</div>
<div class="col-4">
<Card>
<CardBody>
<CardTitle as="h2">Avatar with icon</CardTitle>
<div class="avatar-list">
{iconIcons.map((icon) => <Avatar icon={icon} />)}
</div>
</CardBody>
</Card>
</div>
<div class="col-4">
<Card>
<CardBody>
<CardTitle as="h2">Avatar with icon</CardTitle>
<div class="avatar-list">
{iconIcons.map((icon) => <Avatar icon={icon} />)}
</div>
</CardBody>
</Card>
</div>
<div class="col-4">
<Card>
<CardBody>
<CardTitle as="h2">Avatar with icon</CardTitle>
<div class="avatar-list">
{colors.map((color) => <Avatar icon="user" color={color} />)}
</div>
</CardBody>
</Card>
</div>
<div class="col-4">
<Card>
<CardBody>
<CardTitle as="h2">Simple avatar</CardTitle>
<div class="avatar-list">
{people8.map((person) => <Avatar person={person} />)}
</div>
</CardBody>
</Card>
</div>
<div class="col-4">
<Card>
<CardBody>
<CardTitle as="h2">Avatar placeholder</CardTitle>
<div class="avatar-list">
{people8.map((person) => <Avatar placeholder={firstLetters(person.full_name)} />)}
</div>
</CardBody>
</Card>
</div>
<div class="col-4">
<Card>
<CardBody>
<CardTitle as="h2">Avatar shapes</CardTitle>
<div class="avatar-list">
<Avatar />
<Avatar class="rounded-circle" />
<Avatar class="rounded-0" />
</div>
</CardBody>
</Card>
</div>
<div class="col-6">
<Card>
<CardBody>
<CardTitle as="h2">Avatar sizes</CardTitle>
<div class="row">
<div class="col-6">
<div class="avatar-list">
{sizes.map((size) => <Avatar size={size} />)}
</div>
</div>
<div class="col-6">
<div class="avatar-list">
{sizes.map((size) => <Avatar placeholder="PK" size={size} />)}
</div>
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-6">
<Card>
<CardBody>
<CardTitle as="h2">Avatar lists</CardTitle>
<div class="row g-3">
{
listSizes.map((size) => (
<>
<div class="col-6">
<AvatarList stacked={true} size={size}>
{people5.map((person) => (
<Avatar person={person} />
))}
<Avatar icon="plus" link />
</AvatarList>
</div>
<div class="col-6">
<AvatarList stacked={true} size={size}>
{people5.map((person) => (
<Avatar person={person} class="rounded-circle" />
))}
<Avatar icon="plus" link class="rounded-circle" />
</AvatarList>
</div>
</>
))
}
</div>
</CardBody>
</Card>
</div>
<div class="col-4">
<Card>
<CardBody>
<CardTitle as="h2">Avatar placeholder</CardTitle>
{uploadSizes.map((size) => <AvatarUpload size={size} />)}
</CardBody>
</Card>
</div>
<div class="col-4">
<Card>
<CardBody>
<CardTitle as="h2">Avatar statuses</CardTitle>
{statusColors.map((color, i) => <Avatar personId={i + 1} class="rounded-circle" status={color} />)}
</CardBody>
</Card>
</div>
<div class="col-4">
<Card>
<CardBody>
<CardTitle as="h2">Avatar brands</CardTitle>
{brands.map((brand, i) => <Avatar personId={i + 1} brand={brand} />)}
</CardBody>
</Card>
</div>
</div>
<div class="avatar-list">
{colors.map((color) => <Avatar icon="user" color={color} />)}
</div>
</CardBody>
</Card>
</div>
<div class="col-4">
<Card>
<CardBody>
<CardTitle as="h2">Simple avatar</CardTitle>
<div class="avatar-list">
{people8.map((person) => <Avatar person={person} />)}
</div>
</CardBody>
</Card>
</div>
<div class="col-4">
<Card>
<CardBody>
<CardTitle as="h2">Avatar placeholder</CardTitle>
<div class="avatar-list">
{people8.map((person) => <Avatar placeholder={firstLetters(person.full_name)} />)}
</div>
</CardBody>
</Card>
</div>
<div class="col-4">
<Card>
<CardBody>
<CardTitle as="h2">Avatar shapes</CardTitle>
<div class="avatar-list">
<Avatar />
<Avatar class="rounded-circle" />
<Avatar class="rounded-0" />
</div>
</CardBody>
</Card>
</div>
<div class="col-6">
<Card>
<CardBody>
<CardTitle as="h2">Avatar sizes</CardTitle>
<div class="row">
<div class="col-6">
<div class="avatar-list">
{sizes.map((size) => <Avatar size={size} />)}
</div>
</div>
<div class="col-6">
<div class="avatar-list">
{sizes.map((size) => <Avatar placeholder="PK" size={size} />)}
</div>
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-6">
<Card>
<CardBody>
<CardTitle as="h2">Avatar lists</CardTitle>
<div class="row g-3">
{
listSizes.map((size) => (
<>
<div class="col-6">
<AvatarList stacked={true} size={size}>
{people5.map((person) => (
<Avatar person={person} />
))}
<Avatar icon="plus" link />
</AvatarList>
</div>
<div class="col-6">
<AvatarList stacked={true} size={size}>
{people5.map((person) => (
<Avatar person={person} class="rounded-circle" />
))}
<Avatar icon="plus" link class="rounded-circle" />
</AvatarList>
</div>
</>
))
}
</div>
</CardBody>
</Card>
</div>
<div class="col-4">
<Card>
<CardBody>
<CardTitle as="h2">Avatar placeholder</CardTitle>
{uploadSizes.map((size) => <AvatarUpload size={size} />)}
</CardBody>
</Card>
</div>
<div class="col-4">
<Card>
<CardBody>
<CardTitle as="h2">Avatar statuses</CardTitle>
{statusColors.map((color, i) => <Avatar personId={i + 1} class="rounded-circle" status={color} />)}
</CardBody>
</Card>
</div>
<div class="col-4">
<Card>
<CardBody>
<CardTitle as="h2">Avatar brands</CardTitle>
{brands.map((brand, i) => <Avatar personId={i + 1} brand={brand} />)}
</CardBody>
</Card>
</div>
</div>
</DefaultLayout>
+190 -191
View File
@@ -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']
---
<DefaultLayout title="Badges" pageHeader="Badges" pageMenu="base.badges">
<div class="row row-cards">
<div class="col-4">
<div class="row row-cards">
<div class="col-12">
<Card>
<CardBody>
<h1>Example heading <span class="badge">New</span></h1>
<h2>Example heading <span class="badge">New</span></h2>
<h3>Example heading <span class="badge">New</span></h3>
<h4>Example heading <span class="badge">New</span></h4>
<h5>Example heading <span class="badge">New</span></h5>
<h6>Example heading <span class="badge">New</span></h6>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
<CardTitle>Badge sizes</CardTitle>
<div class="row row-cards">
<div class="col-4">
<div class="row row-cards">
<div class="col-12">
<Card>
<CardBody>
<h1>Example heading <span class="badge">New</span></h1>
<h2>Example heading <span class="badge">New</span></h2>
<h3>Example heading <span class="badge">New</span></h3>
<h4>Example heading <span class="badge">New</span></h4>
<h5>Example heading <span class="badge">New</span></h5>
<h6>Example heading <span class="badge">New</span></h6>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
<CardTitle>Badge sizes</CardTitle>
<div class="space-y">
{
sizes.map((size) => (
<BadgesList>
<span class={`badge${size !== 'md' ? ` badge-${size}` : ''}`}>Default</span>
<span class={`badge${size !== 'md' ? ` badge-${size}` : ''}`}>
<Icon name="check" /> Left icon
</span>
<span class={`badge${size !== 'md' ? ` badge-${size}` : ''}`}>
Right icon<Icon name="arrow-right" />
</span>
<span class={`badge badge-icononly${size !== 'md' ? ` badge-${size}` : ''}`}>
<Icon name="star" type="filled" />
</span>
</BadgesList>
))
}
</div>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
<CardTitle>Positioned badges</CardTitle>
<div class="space-y">
{
sizes.map((size) => (
<BadgesList>
<span class={`badge${size !== 'md' ? ` badge-${size}` : ''}`}>Default</span>
<span class={`badge${size !== 'md' ? ` badge-${size}` : ''}`}>
<Icon name="check" /> Left icon
</span>
<span class={`badge${size !== 'md' ? ` badge-${size}` : ''}`}>
Right icon
<Icon name="arrow-right" />
</span>
<span class={`badge badge-icononly${size !== 'md' ? ` badge-${size}` : ''}`}>
<Icon name="star" type="filled" />
</span>
</BadgesList>
))
}
</div>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
<CardTitle>Positioned badges</CardTitle>
<ButtonList>
<button type="button" class="btn">Notifications <span class="badge text-bg-secondary ms-2">4</span></button>
<ButtonList>
<button type="button" class="btn">Notifications <span class="badge text-bg-secondary ms-2">4</span></button>
<button type="button" class="btn">
Inbox
<span class="badge bg-red badge-notification text-red-fg">
9+
<span class="visually-hidden">unread messages</span>
</span>
</button>
<button type="button" class="btn">
Inbox
<span class="badge bg-red badge-notification text-red-fg">
9+
<span class="visually-hidden">unread messages</span>
</span>
</button>
<button type="button" class="btn">
Profile
<span class="badge badge-dot bg-red badge-notification"></span>
</button>
<button type="button" class="btn">
Profile
<span class="badge badge-dot bg-red badge-notification"></span>
</button>
<button type="button" class="btn">
Settings
<span class="badge badge-dot bg-red badge-notification badge-blink"></span>
</button>
<button type="button" class="btn">
Settings
<span class="badge badge-dot bg-red badge-notification badge-blink"></span>
</button>
<button type="button" class="btn btn-icon">
<Icon name="bell" />
<span class="badge badge-dot bg-red badge-notification badge-blink"></span>
</button>
<button type="button" class="btn btn-icon">
<Icon name="bell" />
<span class="badge badge-dot bg-red badge-notification badge-blink"></span>
</button>
<button type="button" class="btn btn-icon btn-action">
<Icon name="bell" />
<span class="badge badge-dot bg-red badge-notification"></span>
</button>
</ButtonList>
</CardBody>
</Card>
</div>
</div>
</div>
<div class="col-8">
<div class="row row-cards">
<div class="col-12">
<Card>
<CardBody>
<CardTitle>Basic badges</CardTitle>
<BadgesList>
{colors.map((color) => <span class={`badge bg-${color} text-${color}-fg`}>{ucFirst(color)}</span>)}
</BadgesList>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
<CardTitle>Light badges</CardTitle>
<BadgesList>
{colors.map((color) => <span class={`badge bg-${color}-lt`}>{ucFirst(color)}</span>)}
</BadgesList>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
<CardTitle>Outline badges</CardTitle>
<BadgesList>
{colors.map((color) => <span class={`badge badge-outline text-${color}`}>{ucFirst(color)}</span>)}
</BadgesList>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
<CardTitle>Badges with icons</CardTitle>
<BadgesList>
{
colors.map((color) => (
<span class={`badge bg-${color} text-${color}-fg`}>
{' '}
<Icon name="star" type="filled" /> {ucFirst(color)}{' '}
</span>
))
}
</BadgesList>
</CardBody>
</Card>
</div>
</div>
</div>
<div class="col-sm-6 col-lg-3"><DropdownMenu show badge arrow /></div>
<div class="col-sm-6 col-lg-9">
<div class="row row-cards">
<div class="col-12">
<Card>
<CardBody>
<ButtonList>
{
colors.map((color, index) => (
<button class="btn">
{ucFirst(color)} badge <span class={`badge bg-${color} text-${color}-fg ms-2`}>{index + 1}</span>
</button>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
<ButtonList>
{
colors.map((color, index) => (
<button class="btn position-relative">
{ucFirst(color)} badge{' '}
<span class={`badge bg-${color} text-${color}-fg badge-notification badge-pill`}>{index + 1}</span>
</button>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
<ButtonList>
{
colors.map((color) => (
<button class="btn position-relative">
{ucFirst(color)} badge <span class={`badge bg-${color} badge-notification badge-blink`} />
</button>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
</div>
</div>
</div>
<button type="button" class="btn btn-icon btn-action">
<Icon name="bell" />
<span class="badge badge-dot bg-red badge-notification"></span>
</button>
</ButtonList>
</CardBody>
</Card>
</div>
</div>
</div>
<div class="col-8">
<div class="row row-cards">
<div class="col-12">
<Card>
<CardBody>
<CardTitle>Basic badges</CardTitle>
<BadgesList>
{colors.map((color) => <span class={`badge bg-${color} text-${color}-fg`}>{ucFirst(color)}</span>)}
</BadgesList>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
<CardTitle>Light badges</CardTitle>
<BadgesList>
{colors.map((color) => <span class={`badge bg-${color}-lt`}>{ucFirst(color)}</span>)}
</BadgesList>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
<CardTitle>Outline badges</CardTitle>
<BadgesList>
{colors.map((color) => <span class={`badge badge-outline text-${color}`}>{ucFirst(color)}</span>)}
</BadgesList>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
<CardTitle>Badges with icons</CardTitle>
<BadgesList>
{
colors.map((color) => (
<span class={`badge bg-${color} text-${color}-fg`}>
{' '}
<Icon name="star" type="filled" /> {ucFirst(color)}{' '}
</span>
))
}
</BadgesList>
</CardBody>
</Card>
</div>
</div>
</div>
<div class="col-sm-6 col-lg-3"><DropdownMenu show badge arrow /></div>
<div class="col-sm-6 col-lg-9">
<div class="row row-cards">
<div class="col-12">
<Card>
<CardBody>
<ButtonList>
{
colors.map((color, index) => (
<button class="btn">
{ucFirst(color)} badge <span class={`badge bg-${color} text-${color}-fg ms-2`}>{index + 1}</span>
</button>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
<ButtonList>
{
colors.map((color, index) => (
<button class="btn position-relative">
{ucFirst(color)} badge <span class={`badge bg-${color} text-${color}-fg badge-notification badge-pill`}>{index + 1}</span>
</button>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
<ButtonList>
{
colors.map((color) => (
<button class="btn position-relative">
{ucFirst(color)} badge <span class={`badge bg-${color} badge-notification badge-blink`} />
</button>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
</div>
</div>
</div>
</DefaultLayout>
+3 -3
View File
@@ -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'
---
<DefaultLayout title="Blank page" pageMenu="base.blank" containerCentered>
<Empty buttonText="Add your first client" buttonIcon="plus" illustration="computer-fix.svg" />
<Empty buttonText="Add your first client" buttonIcon="plus" illustration="computer-fix.svg" />
</DefaultLayout>
+191 -181
View File
@@ -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']
---
<DefaultLayout title="Buttons" pageHeader="Buttons" pageMenu="base.buttons">
<div class="row row-cards">
<div class="col-md-6">
<Card>
<CardHeader title="Standard Buttons" />
<CardBody>
<ButtonList>
{
themeColors.map(([name, color]) => (
<a class={`btn btn-${name}`}>{color.icon && <Icon name={color.icon} />} {color.title}</a>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Outline Buttons" />
<CardBody>
<ButtonList>
{
themeColors.map(([name, color]) => (
<a class={`btn btn-outline btn-${name}`}>{color.icon && <Icon name={color.icon} />} {color.title}</a>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Ghost Buttons" />
<CardBody>
<ButtonList>
{
themeColors.map(([name, color]) => (
<a class={`btn btn-ghost btn-${name}`}>{color.icon && <Icon name={color.icon} />} {color.title}</a>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Square Buttons" />
<CardBody>
<ButtonList>
{
themeColors.map(([name, color]) => (
<a class={`btn btn-square btn-${name}`}>{color.icon && <Icon name={color.icon} />} {color.title}</a>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Pill Buttons" />
<CardBody>
<ButtonList>
{
themeColors.map(([name, color]) => (
<a class={`btn btn-pill btn-${name}`}>{color.icon && <Icon name={color.icon} />} {color.title}</a>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Extra colors" />
<CardBody>
<ButtonList>
{
colors.map(([name, color]) => (
<a class={`btn btn-${name}`}>{color.icon && <Icon name={color.icon} />} {color.title}</a>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Icon buttons" />
<CardBody>
<ButtonList>
{
socialColors.map(([name, app]) => (
<a class={`btn btn-icon btn-${name}`}>{app.icon && <Icon name={app.icon} />}</a>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Social colors" />
<CardBody>
<ButtonList>
{
socialColors.map(([name, app]) => (
<a class={`btn btn-${name}`}>{app.icon && <Icon name={app.icon} />} {app.title}</a>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Action buttons" />
<CardBody>
<div class="btn-actions">
{
actions.map((action) => (
<a class="btn btn-action">
<Icon name={action} />
</a>
))
}
</div>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Buttons with icon" />
<CardBody>
<ButtonList>
<a class="btn btn-animate-icon">Save <Icon name="arrow-right" class="icon-end" /></a>
<a class="btn btn-animate-icon btn-animate-icon-rotate"><Icon name="plus" /> Add</a>
<a class="btn btn-animate-icon btn-animate-icon-shake"><Icon name="bell" /> Notifications</a>
<a class="btn btn-animate-icon btn-animate-icon-rotate"><Icon name="settings" /> Settings</a>
<a class="btn btn-animate-icon btn-animate-icon-pulse"><Icon name="heart" /> Love</a>
<a class="btn btn-animate-icon btn-animate-icon-rotate"><Icon name="x" /> Close</a>
<a class="btn btn-animate-icon btn-animate-icon-tada"><Icon name="check" /> Confirm</a>
<a class="btn btn-animate-icon">Next <Icon name="chevron-right" class="icon-end" /></a>
<a class="btn btn-animate-icon btn-animate-icon-move-start"><Icon name="chevron-left" /> Previous</a>
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Buttons size" />
<CardBody>
<div class="space-y">
{
sizes.map((size) => (
<ButtonList>
<Button size={size} text="Button" />
<Button size={size} icon="star" iconOnly />
<Button size={size} icon="star" text="Button" />
<Button size={size} iconEnd="star" text="Button" />
</ButtonList>
))
}
</div>
</CardBody>
</Card>
</div>
</div>
<div class="row row-cards">
<div class="col-md-6">
<Card>
<CardHeader title="Standard Buttons" />
<CardBody>
<ButtonList>
{
themeColors.map(([name, color]) => (
<a class={`btn btn-${name}`}>
{color.icon && <Icon name={color.icon} />} {color.title}
</a>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Outline Buttons" />
<CardBody>
<ButtonList>
{
themeColors.map(([name, color]) => (
<a class={`btn btn-outline btn-${name}`}>
{color.icon && <Icon name={color.icon} />} {color.title}
</a>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Ghost Buttons" />
<CardBody>
<ButtonList>
{
themeColors.map(([name, color]) => (
<a class={`btn btn-ghost btn-${name}`}>
{color.icon && <Icon name={color.icon} />} {color.title}
</a>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Square Buttons" />
<CardBody>
<ButtonList>
{
themeColors.map(([name, color]) => (
<a class={`btn btn-square btn-${name}`}>
{color.icon && <Icon name={color.icon} />} {color.title}
</a>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Pill Buttons" />
<CardBody>
<ButtonList>
{
themeColors.map(([name, color]) => (
<a class={`btn btn-pill btn-${name}`}>
{color.icon && <Icon name={color.icon} />} {color.title}
</a>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Extra colors" />
<CardBody>
<ButtonList>
{
colors.map(([name, color]) => (
<a class={`btn btn-${name}`}>
{color.icon && <Icon name={color.icon} />} {color.title}
</a>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Icon buttons" />
<CardBody>
<ButtonList>
{socialColors.map(([name, app]) => <a class={`btn btn-icon btn-${name}`}>{app.icon && <Icon name={app.icon} />}</a>)}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Social colors" />
<CardBody>
<ButtonList>
{
socialColors.map(([name, app]) => (
<a class={`btn btn-${name}`}>
{app.icon && <Icon name={app.icon} />} {app.title}
</a>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Action buttons" />
<CardBody>
<div class="btn-actions">
{
actions.map((action) => (
<a class="btn btn-action">
<Icon name={action} />
</a>
))
}
</div>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Buttons with icon" />
<CardBody>
<ButtonList>
<a class="btn btn-animate-icon">Save <Icon name="arrow-right" class="icon-end" /></a>
<a class="btn btn-animate-icon btn-animate-icon-rotate"><Icon name="plus" /> Add</a>
<a class="btn btn-animate-icon btn-animate-icon-shake"><Icon name="bell" /> Notifications</a>
<a class="btn btn-animate-icon btn-animate-icon-rotate"><Icon name="settings" /> Settings</a>
<a class="btn btn-animate-icon btn-animate-icon-pulse"><Icon name="heart" /> Love</a>
<a class="btn btn-animate-icon btn-animate-icon-rotate"><Icon name="x" /> Close</a>
<a class="btn btn-animate-icon btn-animate-icon-tada"><Icon name="check" /> Confirm</a>
<a class="btn btn-animate-icon">Next <Icon name="chevron-right" class="icon-end" /></a>
<a class="btn btn-animate-icon btn-animate-icon-move-start"><Icon name="chevron-left" /> Previous</a>
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card>
<CardHeader title="Buttons size" />
<CardBody>
<div class="space-y">
{
sizes.map((size) => (
<ButtonList>
<Button size={size} text="Button" />
<Button size={size} icon="star" iconOnly />
<Button size={size} icon="star" text="Button" />
<Button size={size} iconEnd="star" text="Button" />
</ButtonList>
))
}
</div>
</CardBody>
</Card>
</div>
</div>
</DefaultLayout>
+5 -5
View File
@@ -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)
---
<ProseLayout title="Changelog" pageMenu="changelog">
<Fragment set:html={changelogHtml} />
<Fragment set:html={changelogHtml} />
</ProseLayout>
+24 -29
View File
@@ -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 }[]
---
<DefaultLayout
title="Color picker"
pageHeader="Color picker"
pageMenu="plugins.colorpicker"
pageLibs={['coloris.js']}
>
<Card>
<CardBody>
<CardTitle>Basic</CardTitle>
<div class="row g-3">
{
colors.map((color, index) => (
<div class="col-2">
<div>
<Colorpicker value={color.hex} id={index + 1} format="hex" />
</div>
</div>
))
}
</div>
</CardBody>
</Card>
<DefaultLayout title="Color picker" pageHeader="Color picker" pageMenu="plugins.colorpicker" pageLibs={['coloris.js']}>
<Card>
<CardBody>
<CardTitle>Basic</CardTitle>
<div class="row g-3">
{
colors.map((color, index) => (
<div class="col-2">
<div>
<Colorpicker value={color.hex} id={index + 1} format="hex" />
</div>
</div>
))
}
</div>
</CardBody>
</Card>
</DefaultLayout>
+220 -231
View File
@@ -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']
---
<DefaultLayout title="Colors" pageHeader="Colors" pageMenu="base.colors">
<div class="row row-cards">
<div class="col-3">
<Card>
<CardBody>
<div class="row g-3">
{
colors.map(([name, color]) => (
<div class="col-12">
<div class="row align-items-center">
<div class="col-auto">
<Avatar square class={`bg-${name} text-${name}-fg`} placeholder={color.abbr} />
</div>
<div class="col">
{color.title}
<br />
<code>{color.hex}</code>
</div>
</div>
</div>
))
}
</div>
</CardBody>
</Card>
</div>
<div class="col-3">
<Card>
<CardBody>
<div class="row g-3">
{
lightColors.map(([name, color]) => (
<div class="col-12">
<div class="row align-items-center">
<div class="col-auto">
<Avatar square class={`bg-${name}-lt text-${name}-lt-fg`} placeholder={color.abbr} />
</div>
<div class="col">
{color.title}
<br />
<code>{color.hex}</code>
</div>
</div>
</div>
))
}
</div>
</CardBody>
</Card>
</div>
<div class="col-3">
<Card>
<CardBody>
<div class="row g-3">
{
grayColors.map(([name, color]) => (
<div class="col-12">
<div class="row align-items-center">
<div class="col-auto">
<Avatar square class={`bg-${name} text-${name}-fg`} placeholder={color.abbr} />
</div>
<div class="col">
{color.title}
<br />
<code>{color.hex}</code>
</div>
</div>
</div>
))
}
</div>
</CardBody>
</Card>
</div>
<div class="col-3">
<Card>
<CardBody>
<div class="row g-3">
{
socialColors.map(([name, color]) => (
<div class="col-12">
<div class="row align-items-center">
<div class="col-auto">
<Avatar square class={`bg-${name} text-${name}-fg`} icon={color.icon} />
</div>
<div class="col">
{color.title}
<br />
<code>{color.hex}</code>
</div>
</div>
</div>
))
}
</div>
</CardBody>
</Card>
</div>
<div class="col-12">
<div class="row">
<div class="col-6">
<Card>
<CardBody>
<CardTitle>Gradient</CardTitle>
<form action="">
<div class="row g-4">
<div class="col">
<FormGroup label="From" class="">
<select class="form-select" name="color-from">
{gradientColors.map((color) => <option value={color}>{color}</option>)}
</select>
</FormGroup>
<FormGroup label="To" class="mt-3">
<select class="form-select" name="color-to">
{
gradientColors.map((color) => (
<option value={color} selected={color === 'transparent'}>
{color}
</option>
))
}
</select>
</FormGroup>
</div>
<div class="col">
<FormGroup label="Via" class="">
<select class="form-select" name="color-via">
<option></option>
{gradientColors.map((color) => <option value={color}>{color}</option>)}
</select>
</FormGroup>
<FormGroup label="Direction" class="mt-3">
<select class="form-select" name="color-direction">
<option value="to-t">to top</option>
<option value="to-te">to top right</option>
<option value="to-r" selected>to right</option>
<option value="to-be">to bottom right</option>
<option value="to-b">to bottom</option>
<option value="to-bs">to bottom left</option>
<option value="to-s">to left</option>
<option value="to-ts">to top left</option>
</select>
</FormGroup>
</div>
<div class="col-12">
<div class="border rounded bg-pattern-transparent overflow-hidden">
<div
id="gradient-preview"
class="border rounded bg-gradient bg-gradient-from-primary bg-gradient-to-transparent"
>
<div class=" px-4 py-5"></div>
</div>
</div>
</div>
</div>
</form>
</CardBody>
</Card>
</div>
<div class="col-6">
<Card>
<CardBody>
<div class="row">
<div class="col">
<div class="space-y">
{
gradientColors.map((color) => (
<div class={`border rounded bg-gradient bg-gradient-from-${color} bg-gradient-to-transparent px-4 py-2`} />
))
}
</div>
</div>
<div class="col">
<div class="space-y">
{
gradientColors.map((color) => (
<div class={`border rounded bg-gradient bg-gradient-to-${color} bg-gradient-from-transparent px-4 py-2`} />
))
}
</div>
</div>
</div>
</CardBody>
</Card>
</div>
</div>
</div>
</div>
<div class="row row-cards">
<div class="col-3">
<Card>
<CardBody>
<div class="row g-3">
{
colors.map(([name, color]) => (
<div class="col-12">
<div class="row align-items-center">
<div class="col-auto">
<Avatar square class={`bg-${name} text-${name}-fg`} placeholder={color.abbr} />
</div>
<div class="col">
{color.title}
<br />
<code>{color.hex}</code>
</div>
</div>
</div>
))
}
</div>
</CardBody>
</Card>
</div>
<div class="col-3">
<Card>
<CardBody>
<div class="row g-3">
{
lightColors.map(([name, color]) => (
<div class="col-12">
<div class="row align-items-center">
<div class="col-auto">
<Avatar square class={`bg-${name}-lt text-${name}-lt-fg`} placeholder={color.abbr} />
</div>
<div class="col">
{color.title}
<br />
<code>{color.hex}</code>
</div>
</div>
</div>
))
}
</div>
</CardBody>
</Card>
</div>
<div class="col-3">
<Card>
<CardBody>
<div class="row g-3">
{
grayColors.map(([name, color]) => (
<div class="col-12">
<div class="row align-items-center">
<div class="col-auto">
<Avatar square class={`bg-${name} text-${name}-fg`} placeholder={color.abbr} />
</div>
<div class="col">
{color.title}
<br />
<code>{color.hex}</code>
</div>
</div>
</div>
))
}
</div>
</CardBody>
</Card>
</div>
<div class="col-3">
<Card>
<CardBody>
<div class="row g-3">
{
socialColors.map(([name, color]) => (
<div class="col-12">
<div class="row align-items-center">
<div class="col-auto">
<Avatar square class={`bg-${name} text-${name}-fg`} icon={color.icon} />
</div>
<div class="col">
{color.title}
<br />
<code>{color.hex}</code>
</div>
</div>
</div>
))
}
</div>
</CardBody>
</Card>
</div>
<div class="col-12">
<div class="row">
<div class="col-6">
<Card>
<CardBody>
<CardTitle>Gradient</CardTitle>
<form action="">
<div class="row g-4">
<div class="col">
<FormGroup label="From" class="">
<select class="form-select" name="color-from">
{gradientColors.map((color) => <option value={color}>{color}</option>)}
</select>
</FormGroup>
<FormGroup label="To" class="mt-3">
<select class="form-select" name="color-to">
{
gradientColors.map((color) => (
<option value={color} selected={color === 'transparent'}>
{color}
</option>
))
}
</select>
</FormGroup>
</div>
<div class="col">
<FormGroup label="Via" class="">
<select class="form-select" name="color-via">
<option></option>
{gradientColors.map((color) => <option value={color}>{color}</option>)}
</select>
</FormGroup>
<FormGroup label="Direction" class="mt-3">
<select class="form-select" name="color-direction">
<option value="to-t">to top</option>
<option value="to-te">to top right</option>
<option value="to-r" selected>to right</option>
<option value="to-be">to bottom right</option>
<option value="to-b">to bottom</option>
<option value="to-bs">to bottom left</option>
<option value="to-s">to left</option>
<option value="to-ts">to top left</option>
</select>
</FormGroup>
</div>
<div class="col-12">
<div class="border rounded bg-pattern-transparent overflow-hidden">
<div id="gradient-preview" class="border rounded bg-gradient bg-gradient-from-primary bg-gradient-to-transparent">
<div class="px-4 py-5"></div>
</div>
</div>
</div>
</div>
</form>
</CardBody>
</Card>
</div>
<div class="col-6">
<Card>
<CardBody>
<div class="row">
<div class="col">
<div class="space-y">
{gradientColors.map((color) => <div class={`border rounded bg-gradient bg-gradient-from-${color} bg-gradient-to-transparent px-4 py-2`} />)}
</div>
</div>
<div class="col">
<div class="space-y">
{gradientColors.map((color) => <div class={`border rounded bg-gradient bg-gradient-to-${color} bg-gradient-from-transparent px-4 py-2`} />)}
</div>
</div>
</div>
</CardBody>
</Card>
</div>
</div>
</div>
</div>
<CaptureScript>
<!-- BEGIN GRADIENT SCRIPT -->
<script is:inline>
document.addEventListener('DOMContentLoaded', function () {
var gradientPreview = document.getElementById('gradient-preview');
var colorFrom = document.querySelector('[name="color-from"]');
var colorTo = document.querySelector('[name="color-to"]');
var colorVia = document.querySelector('[name="color-via"]');
var colorDirection = document.querySelector('[name="color-direction"]');
<CaptureScript>
<!-- BEGIN GRADIENT SCRIPT -->
<script is:inline>
document.addEventListener('DOMContentLoaded', function () {
var gradientPreview = document.getElementById('gradient-preview')
var colorFrom = document.querySelector('[name="color-from"]')
var colorTo = document.querySelector('[name="color-to"]')
var colorVia = document.querySelector('[name="color-via"]')
var colorDirection = document.querySelector('[name="color-direction"]')
function updateGradient() {
var from = colorFrom.value;
var to = colorTo.value;
var via = colorVia.value;
var direction = colorDirection.value;
function updateGradient() {
var from = colorFrom.value
var to = colorTo.value
var via = colorVia.value
var direction = colorDirection.value
var gradient = 'bg-gradient bg-gradient-from-' + from + ' bg-gradient-to-' + to;
var gradient = 'bg-gradient bg-gradient-from-' + from + ' bg-gradient-to-' + to
if (via) {
gradient += ' bg-gradient-via-' + via;
}
if (via) {
gradient += ' bg-gradient-via-' + via
}
gradient += ' bg-gradient-' + direction;
gradient += ' bg-gradient-' + direction
gradientPreview.className = gradient;
}
gradientPreview.className = gradient
}
colorFrom.addEventListener('change', updateGradient);
colorTo.addEventListener('change', updateGradient);
colorVia.addEventListener('change', updateGradient);
colorDirection.addEventListener('change', updateGradient);
colorFrom.addEventListener('change', updateGradient)
colorTo.addEventListener('change', updateGradient)
colorVia.addEventListener('change', updateGradient)
colorDirection.addEventListener('change', updateGradient)
updateGradient();
});
</script>
<!-- END GRADIENT SCRIPT -->
</CaptureScript>
updateGradient()
})
</script>
<!-- END GRADIENT SCRIPT -->
</CaptureScript>
</DefaultLayout>
+308 -313
View File
@@ -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)
---
<DefaultLayout
title="Crypto Dashboard"
pageHeader="Crypto Dashboard"
pageMenu="dashboards.crypto"
pageLibs={['apexcharts']}
>
<div class="row row-cards">
<div class="col-12">
<div class="row row-cards">
<div class="col-12 col-md-6 col-xxl">
<Card>
<CardBody>
<div class="row">
<div class="col mt-0">
<h5 class="card-title">Total balance</h5>
</div>
<div class="col-auto">
<Avatar icon="currency-dollar" />
</div>
</div>
<div class="mb-1">
<span class="h3">${totalUsd}</span>
<span class="text-muted">{btcBalance} {btc.symbol}</span>
</div>
<div class="mb-0">
<Trending value={btc.p24h} />
<span class="text-muted">Since last week</span>
</div>
</CardBody>
</Card>
</div>
<DefaultLayout title="Crypto Dashboard" pageHeader="Crypto Dashboard" pageMenu="dashboards.crypto" pageLibs={['apexcharts']}>
<div class="row row-cards">
<div class="col-12">
<div class="row row-cards">
<div class="col-12 col-md-6 col-xxl">
<Card>
<CardBody>
<div class="row">
<div class="col mt-0">
<h5 class="card-title">Total balance</h5>
</div>
<div class="col-auto">
<Avatar icon="currency-dollar" />
</div>
</div>
<div class="mb-1">
<span class="h3">${totalUsd}</span>
<span class="text-muted">{btcBalance} {btc.symbol}</span>
</div>
<div class="mb-0">
<Trending value={btc.p24h} />
<span class="text-muted">Since last week</span>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-md-6 col-xxl">
<Card>
<CardBody>
<div class="row">
<div class="col mt-0">
<h5 class="card-title">USD/BTC</h5>
</div>
<div class="col-auto">
<Avatar icon="currency-bitcoin" />
</div>
</div>
<div class="mb-1">
<span class="h3">{btc.price}</span>
<span class="text-muted">{btc.price}</span>
</div>
<div class="mb-0">
<span class="text-muted">Volume: {btc['volume-24h']}</span>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-md-6 col-xxl">
<Card>
<CardBody>
<div class="row">
<div class="col mt-0">
<h5 class="card-title">USD/BTC</h5>
</div>
<div class="col-auto">
<Avatar icon="currency-bitcoin" />
</div>
</div>
<div class="mb-1">
<span class="h3">{btc.price}</span>
<span class="text-muted">{btc.price}</span>
</div>
<div class="mb-0">
<span class="text-muted">Volume: {btc['volume-24h']}</span>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-md-6 col-xxl">
<Card>
<CardBody>
<div class="row">
<div class="col mt-0">
<h5 class="card-title">LTC/BTC</h5>
</div>
<div class="col-auto">
<Avatar icon="currency-litecoin" />
</div>
</div>
<div class="mb-1">
<span class="h3">{ltcBtc}</span>
<span class="text-muted">{ltc.price}</span>
</div>
<div class="mb-0">
<span class="text-muted">Volume: {ltc['volume-24h'].replace('$', '')}</span>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-md-6 col-xxl">
<Card>
<CardBody>
<div class="row">
<div class="col mt-0">
<h5 class="card-title">LTC/BTC</h5>
</div>
<div class="col-auto">
<Avatar icon="currency-litecoin" />
</div>
</div>
<div class="mb-1">
<span class="h3">{ltcBtc}</span>
<span class="text-muted">{ltc.price}</span>
</div>
<div class="mb-0">
<span class="text-muted">Volume: {ltc['volume-24h'].replace('$', '')}</span>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-md-6 col-xxl">
<Card>
<CardBody>
<div class="row">
<div class="col mt-0">
<h5 class="card-title">ETH/BTC</h5>
</div>
<div class="col-auto">
<Avatar icon="currency-ethereum" />
</div>
</div>
<div class="mb-1">
<span class="h3">{ethBtc}</span>
<span class="text-muted">{eth.price}</span>
</div>
<div class="mb-0">
<span class="text-muted">Volume: {eth['volume-24h'].replace('$', '')}</span>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-md-6 col-xxl">
<Card>
<CardBody>
<div class="row">
<div class="col mt-0">
<h5 class="card-title">ETH/BTC</h5>
</div>
<div class="col-auto">
<Avatar icon="currency-ethereum" />
</div>
</div>
<div class="mb-1">
<span class="h3">{ethBtc}</span>
<span class="text-muted">{eth.price}</span>
</div>
<div class="mb-0">
<span class="text-muted">Volume: {eth['volume-24h'].replace('$', '')}</span>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-md-6 col-xxl">
<Card>
<CardBody>
<div class="row">
<div class="col mt-0">
<h5 class="card-title">XMR/BTC</h5>
</div>
<div class="col-auto">
<Avatar icon="currency-monero" />
</div>
</div>
<div class="mb-1">
<span class="h3">{xmrBtc}</span>
<span class="text-muted">{xmr.price}</span>
</div>
<div class="mb-0">
<span class="text-muted">Volume: {xmr['volume-24h'].replace('$', '')}</span>
</div>
</CardBody>
</Card>
</div>
</div>
</div>
<div class="col-12">
<div class="row row-cards">
<div class="col-12 col-lg-5">
<Card>
<CardHeader>
<h5 class="card-title mb-0">Markets</h5>
<CardActions>
<CardDropdown />
</CardActions>
</CardHeader>
<table class="table card-table table-striped">
<thead>
<tr>
<th>
<SwitchIcon icon="star" iconBColor="yellow" variant="slide-up" />
</th>
<th>Coin</th>
<th>Price</th>
<th class="d-none d-xl-table-cell">Volume</th>
<th class="d-none d-xl-table-cell text-end">Change</th>
</tr>
</thead>
<tbody>
{
markets.map((market) => (
<tr>
<td>
<SwitchIcon icon="star" iconBColor="yellow" variant="slide-up" />
</td>
<td>{market.coin}</td>
<td class="">{market.price}</td>
<td class="d-none d-xl-table-cell">{market.volume}</td>
<td class="d-none d-xl-table-cell text-end">
<Trending value={market.change as unknown as number} />
</td>
</tr>
))
}
</tbody>
</table>
</Card>
</div>
<div class="col-12 col-md-6 col-xxl">
<Card>
<CardBody>
<div class="row">
<div class="col mt-0">
<h5 class="card-title">XMR/BTC</h5>
</div>
<div class="col-auto">
<Avatar icon="currency-monero" />
</div>
</div>
<div class="mb-1">
<span class="h3">{xmrBtc}</span>
<span class="text-muted">{xmr.price}</span>
</div>
<div class="mb-0">
<span class="text-muted">Volume: {xmr['volume-24h'].replace('$', '')}</span>
</div>
</CardBody>
</Card>
</div>
</div>
</div>
<div class="col-12">
<div class="row row-cards">
<div class="col-12 col-lg-5">
<Card>
<CardHeader>
<h5 class="card-title mb-0">Markets</h5>
<CardActions>
<CardDropdown />
</CardActions>
</CardHeader>
<table class="table card-table table-striped">
<thead>
<tr>
<th>
<SwitchIcon icon="star" iconBColor="yellow" variant="slide-up" />
</th>
<th>Coin</th>
<th>Price</th>
<th class="d-none d-xl-table-cell">Volume</th>
<th class="d-none d-xl-table-cell text-end">Change</th>
</tr>
</thead>
<tbody>
{
markets.map((market) => (
<tr>
<td>
<SwitchIcon icon="star" iconBColor="yellow" variant="slide-up" />
</td>
<td>{market.coin}</td>
<td class="">{market.price}</td>
<td class="d-none d-xl-table-cell">{market.volume}</td>
<td class="d-none d-xl-table-cell text-end">
<Trending value={market.change as unknown as number} />
</td>
</tr>
))
}
</tbody>
</table>
</Card>
</div>
<div class="col-12 col-lg-7">
<Card>
<CardHeader>
<h5 class="card-title mb-0">LTC/BTC</h5>
<CardActions>
<NavSegmented items={['1m', '5m', '30m', '1h', '1d']} name="timeframe" size="sm" default={2} />
</CardActions>
</CardHeader>
<CardBody>
<Chart chartId="dashboard-crypto-candlestick" height={28} />
</CardBody>
</Card>
</div>
</div>
</div>
<div class="col-12">
<div class="row row-cards">
<div class="col-12 col-lg-12 col-xxl-3">
<Card>
<CardHeader>
<h5 class="card-title mb-0">Operations</h5>
<CardActions>
<NavSegmented items={['Buy', 'Sell', 'Send']} name="operations" size="sm" />
</CardActions>
</CardHeader>
<div class="col-12 col-lg-7">
<Card>
<CardHeader>
<h5 class="card-title mb-0">LTC/BTC</h5>
<CardActions>
<NavSegmented items={['1m', '5m', '30m', '1h', '1d']} name="timeframe" size="sm" default={2} />
</CardActions>
</CardHeader>
<CardBody>
<Chart chartId="dashboard-crypto-candlestick" height={28} />
</CardBody>
</Card>
</div>
</div>
</div>
<div class="col-12">
<div class="row row-cards">
<div class="col-12 col-lg-12 col-xxl-3">
<Card>
<CardHeader>
<h5 class="card-title mb-0">Operations</h5>
<CardActions>
<NavSegmented items={['Buy', 'Sell', 'Send']} name="operations" size="sm" />
</CardActions>
</CardHeader>
<CardBody>
<p>Place new order:</p>
<CardBody>
<p>Place new order:</p>
<div class="input-group mb-3">
<label class="input-group-text">Amount</label>
<select class="form-select">
{
operationCurrencies.map((currency, i) => (
<option value={currency.symbol} selected={i === 0}>
{currency.symbol}
</option>
))
}
</select>
<input type="text" class="form-control" value="0.25" />
</div>
<div class="input-group mb-3">
<label class="input-group-text">Amount</label>
<select class="form-select">
{
operationCurrencies.map((currency, i) => (
<option value={currency.symbol} selected={i === 0}>
{currency.symbol}
</option>
))
}
</select>
<input type="text" class="form-control" value="0.25" />
</div>
<div class="input-group mb-3">
<label class="input-group-text">Price</label>
<input type="text" class="form-control" readonly="" value="23,077.05" />
<label class="input-group-text">$</label>
</div>
<div class="input-group mb-3">
<label class="input-group-text">Price</label>
<input type="text" class="form-control" readonly="" value="23,077.05" />
<label class="input-group-text">$</label>
</div>
<div class="input-group mb-3">
<label class="input-group-text">Total</label>
<input type="text" class="form-control" readonly="" value="5,769.27" />
<label class="input-group-text">$</label>
</div>
<div class="input-group mb-3">
<label class="input-group-text">Total</label>
<input type="text" class="form-control" readonly="" value="5,769.27" />
<label class="input-group-text">$</label>
</div>
<div class="d-grid">
<button type="button" class="btn btn-primary mb-3">Process to wallet</button>
</div>
<div class="d-grid">
<button type="button" class="btn btn-primary mb-3">Process to wallet</button>
</div>
<p class="text-muted mb-0 small">The final amount could change depending on current market conditions.</p>
</CardBody>
</Card>
</div>
<p class="text-muted mb-0 small">The final amount could change depending on current market conditions.</p>
</CardBody>
</Card>
</div>
<div class="col-12 col-lg-6 col-xxl">
<Card>
<CardHeader>
<h5 class="card-title mb-0">Sell Orders</h5>
<CardActions>
<button class="btn btn-sm">View all</button>
</CardActions>
</CardHeader>
<table class="table card-table table-striped">
<thead>
<tr>
<th>Price</th>
<th class="d-none d-xl-table-cell">BTC</th>
<th>Sum(BTC)</th>
</tr>
</thead>
<tbody>
{
orders.sell_orders.map((order) => (
<tr>
<td>{order.price}</td>
<td class="d-none d-xl-table-cell">{order.btc}</td>
<td>{order.sum}</td>
</tr>
))
}
</tbody>
</table>
</Card>
</div>
<div class="col-12 col-lg-6 col-xxl">
<Card>
<CardHeader>
<h5 class="card-title mb-0">Sell Orders</h5>
<CardActions>
<button class="btn btn-sm">View all</button>
</CardActions>
</CardHeader>
<table class="table card-table table-striped">
<thead>
<tr>
<th>Price</th>
<th class="d-none d-xl-table-cell">BTC</th>
<th>Sum(BTC)</th>
</tr>
</thead>
<tbody>
{
orders.sell_orders.map((order) => (
<tr>
<td>{order.price}</td>
<td class="d-none d-xl-table-cell">{order.btc}</td>
<td>{order.sum}</td>
</tr>
))
}
</tbody>
</table>
</Card>
</div>
<div class="col-12 col-lg-6 col-xxl">
<Card>
<CardHeader>
<h5 class="card-title mb-0">Buy Orders</h5>
<CardActions>
<button class="btn btn-sm">View all</button>
</CardActions>
</CardHeader>
<table class="table card-table table-striped">
<thead>
<tr>
<th>Price</th>
<th class="d-none d-xl-table-cell">BTC</th>
<th>Sum(BTC)</th>
</tr>
</thead>
<tbody>
{
orders.buy_orders.map((order) => (
<tr>
<td>{order.price}</td>
<td class="d-none d-xl-table-cell">{order.btc}</td>
<td>{order.sum}</td>
</tr>
))
}
</tbody>
</table>
</Card>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6 col-xxl">
<Card>
<CardHeader>
<h5 class="card-title mb-0">Buy Orders</h5>
<CardActions>
<button class="btn btn-sm">View all</button>
</CardActions>
</CardHeader>
<table class="table card-table table-striped">
<thead>
<tr>
<th>Price</th>
<th class="d-none d-xl-table-cell">BTC</th>
<th>Sum(BTC)</th>
</tr>
</thead>
<tbody>
{
orders.buy_orders.map((order) => (
<tr>
<td>{order.price}</td>
<td class="d-none d-xl-table-cell">{order.btc}</td>
<td>{order.sum}</td>
</tr>
))
}
</tbody>
</table>
</Card>
</div>
</div>
</div>
</div>
</DefaultLayout>
+198 -191
View File
@@ -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';
---
<DefaultLayout title="Email inbox" pageHeader="Inbox" pageMenu="extra.email-inbox" pageLibs={['hugerte']}>
<Card>
<div class="row g-0">
<div class="col-xxl-3 email-border border-end">
<Offcanvas size="xxl" direction="start" class="h-100 file-offcanvas" tabindex="-1" id="emailSidebaroffcanvas">
<div class="card-body h-100">
<div>
<Button icon="pencil" text="Compose" color="primary" class="d-none d-sm-block" modalId="new-email" />
</div>
<DefaultLayout
title="Email inbox"
pageHeader="Inbox"
pageMenu="extra.email-inbox"
pageLibs={['hugerte']}
>
<Card>
<div class="row g-0">
<div class="col-xxl-3 email-border border-end">
<Offcanvas size="xxl" direction="start" class="h-100 file-offcanvas" tabindex="-1" id="emailSidebaroffcanvas">
<div class="card-body h-100">
<div>
<Button icon="pencil" text="Compose" color="primary" class="d-none d-sm-block" modalId="new-email" />
</div>
<div class="mt-3 nav nav-vertical">
<a href="#" class="nav-link text-danger fw-bold">
<Icon name="inbox" class="me-2" />
Inbox<span class="badge badge-danger ms-auto">{mails.length}</span>
</a>
<a href="#" class="nav-link"><Icon name="star" class="me-2" />Starred</a>
<a href="#" class="nav-link"><Icon name="clock" class="me-2" />Snoozed</a>
<a href="#" class="nav-link"><Icon name="file" class="me-2" />Draft<span class="badge badge-info ms-auto">32</span></a>
<a href="#" class="nav-link"><Icon name="mail-up" class="me-2" />Sent Mail</a>
<a href="#" class="nav-link"><Icon name="trash" class="me-2" />Trash</a>
<a href="#" class="nav-link"><Icon name="tag" class="me-2" />Important</a>
<a href="#" class="nav-link"><Icon name="alert-octagon" class="me-2" />Spam</a>
</div>
<div class="mt-3 nav nav-vertical">
<a href="#" class="nav-link text-danger fw-bold">
<Icon name="inbox" class="me-2" />
Inbox<span class="badge badge-danger ms-auto">{mails.length}</span>
</a>
<a href="#" class="nav-link"><Icon name="star" class="me-2" />Starred</a>
<a href="#" class="nav-link"><Icon name="clock" class="me-2" />Snoozed</a>
<a href="#" class="nav-link "><Icon name="file" class="me-2" />Draft<span class="badge badge-info ms-auto">32</span></a>
<a href="#" class="nav-link"><Icon name="mail-up" class="me-2" />Sent Mail</a>
<a href="#" class="nav-link"><Icon name="trash" class="me-2" />Trash</a>
<a href="#" class="nav-link"><Icon name="tag" class="me-2" />Important</a>
<a href="#" class="nav-link"><Icon name="alert-octagon" class="me-2" />Spam</a>
</div>
<div class="mt-4">
<Subheader as="h6">Labels</Subheader>
<div class="mt-2 nav nav-vertical">
<a href="#" class="nav-link">
<div class="badge bg-info me-2"></div> Updates
</a>
<a href="#" class="nav-link">
<div class="badge bg-warning me-2"></div> Friends
</a>
<a href="#" class="nav-link">
<div class="badge bg-success me-2"></div> Family
</a>
<a href="#" class="nav-link">
<div class="badge bg-primary me-2"></div> Social
</a>
<a href="#" class="nav-link">
<div class="badge bg-danger me-2"></div> Important
</a>
<a href="#" class="nav-link">
<div class="badge bg-purple me-2"></div> Promotions
</a>
</div>
</div>
<div class="mt-4">
<Subheader as="h6">Labels</Subheader>
<div class="mt-2 nav nav-vertical">
<a href="#" class="nav-link">
<div class="badge bg-info me-2"></div> Updates
</a>
<a href="#" class="nav-link">
<div class="badge bg-warning me-2"></div> Friends
</a>
<a href="#" class="nav-link">
<div class="badge bg-success me-2"></div> Family
</a>
<a href="#" class="nav-link">
<div class="badge bg-primary me-2"></div> Social
</a>
<a href="#" class="nav-link">
<div class="badge bg-danger me-2"></div> Important
</a>
<a href="#" class="nav-link">
<div class="badge bg-purple me-2"></div> Promotions
</a>
</div>
</div>
<div class="mt-5">
<Subheader as="h6">Storage</Subheader>
<Progress value={46} class="my-2" />
<div class="mt-5">
<Subheader as="h6">Storage</Subheader>
<Progress value={46} class="my-2" />
<p class="text-muted font-13 mb-0">7.02 GB (46%) of 15 GB used</p>
</div>
</div>
</Offcanvas>
</div>
<p class="text-muted font-13 mb-0">7.02 GB (46%) of 15 GB used</p>
</div>
<div class="col-xxl-9">
<div class="card-body">
<div class="d-flex flex-wrap align-items-center gap-2">
<div class="d-xxl-none d-inline-flex">
<button class="btn btn-icon" type="button" data-bs-toggle="offcanvas" data-bs-target="#emailSidebaroffcanvas" aria-controls="emailSidebaroffcanvas">
<Icon name="menu-2" />
</button>
</div>
<ButtonGroup>
<Button icon="archive" iconOnly />
<Button icon="alert-octagon" iconOnly />
<Button icon="trash" iconOnly />
</ButtonGroup>
</div>
</Offcanvas>
</div>
<ButtonGroup>
<button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<Icon name="folder" />
</button>
<div class="dropdown-menu">
<span class="dropdown-header">Move to</span>
<a class="dropdown-item" href="#">Social</a>
<a class="dropdown-item" href="#">Promotions</a>
<a class="dropdown-item" href="#">Updates</a>
<a class="dropdown-item" href="#">Forums</a>
</div>
</ButtonGroup>
<div class="col-xxl-9">
<div class="card-body">
<div class="d-flex flex-wrap align-items-center gap-2">
<div class="d-xxl-none d-inline-flex">
<button class="btn btn-icon" type="button" data-bs-toggle="offcanvas"
data-bs-target="#emailSidebaroffcanvas" aria-controls="emailSidebaroffcanvas">
<Icon name="menu-2" />
</button>
</div>
<ButtonGroup>
<button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<Icon name="tag" />
</button>
<div class="dropdown-menu">
<span class="dropdown-header">Label as:</span>
<a class="dropdown-item" href="#">Updates</a>
<a class="dropdown-item" href="#">Social</a>
<a class="dropdown-item" href="#">Promotions</a>
<a class="dropdown-item" href="#">Forums</a>
</div>
</ButtonGroup>
<ButtonGroup>
<Button icon="archive" iconOnly />
<Button icon="alert-octagon" iconOnly />
<Button icon="trash" iconOnly />
</ButtonGroup>
<ButtonGroup>
<button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<Icon name="dots" /> More
</button>
<div class="dropdown-menu">
<span class="dropdown-header">More Options :</span>
<a class="dropdown-item" href="#">Mark as Unread</a>
<a class="dropdown-item" href="#">Add to Tasks</a>
<a class="dropdown-item" href="#">Add Star</a>
<a class="dropdown-item" href="#">Mute</a>
</div>
</ButtonGroup>
</div>
<ButtonGroup>
<button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<Icon name="folder" />
</button>
<div class="dropdown-menu">
<span class="dropdown-header">Move to</span>
<a class="dropdown-item" href="#">Social</a>
<a class="dropdown-item" href="#">Promotions</a>
<a class="dropdown-item" href="#">Updates</a>
<a class="dropdown-item" href="#">Forums</a>
</div>
</ButtonGroup>
<div class="mt-3">
<ul class="email-list">
{
mails && mails.length > 0 ? (
mails.map((mail) => (
<li>
<div class="email-sender-info">
<div class="checkbox-wrapper-mail">
<div class="form-check">
<input type="checkbox" class="form-check-input" id={`mail-${mail.id}`} />
<label class="form-check-label" for={`mail-${mail.id}`} />
</div>
</div>
<span class="star-toggle">
<Icon name="star" />
</span>
<a href="#" class="email-title">
{mail.sender}
</a>
</div>
<ButtonGroup>
<button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<Icon name="tag" />
</button>
<div class="dropdown-menu">
<span class="dropdown-header">Label as:</span>
<a class="dropdown-item" href="#">Updates</a>
<a class="dropdown-item" href="#">Social</a>
<a class="dropdown-item" href="#">Promotions</a>
<a class="dropdown-item" href="#">Forums</a>
</div>
</ButtonGroup>
<div class="email-content">
<a href="#" class="email-subject">
{mail.subject} &nbsp;&nbsp;
<span>{mail.preview}</span>
</a>
<div class="email-date">{mail.date}</div>
</div>
<ButtonGroup>
<button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<Icon name="dots" /> More
</button>
<div class="dropdown-menu">
<span class="dropdown-header">More Options :</span>
<a class="dropdown-item" href="#">Mark as Unread</a>
<a class="dropdown-item" href="#">Add to Tasks</a>
<a class="dropdown-item" href="#">Add Star</a>
<a class="dropdown-item" href="#">Mute</a>
</div>
</ButtonGroup>
</div>
<div class="email-action-icons">
<ul class="list-inline">
<li class="list-inline-item">
<a href="#">
<Icon name="archive" />
</a>
</li>
<li class="list-inline-item">
<a href="#">
<Icon name="trash" />
</a>
</li>
<li class="list-inline-item">
<a href="#">
<Icon name="mail-opened" class="email-action-icons-item" />
</a>
</li>
<li class="list-inline-item">
<a href="#">
<Icon name="clock" />
</a>
</li>
</ul>
</div>
</li>
))
) : (
<li class="text-muted">No emails</li>
)
}
</ul>
</div>
<div class="mt-3">
<ul class="email-list">
{mails && mails.length > 0 ? (
mails.map((mail) => (
<li>
<div class="email-sender-info">
<div class="checkbox-wrapper-mail">
<div class="form-check">
<input type="checkbox" class="form-check-input" id={`mail-${mail.id}`} />
<label class="form-check-label" for={`mail-${mail.id}`}></label>
</div>
</div>
<span class="star-toggle"><Icon name="star" /></span>
<a href="#" class="email-title">{mail.sender}</a>
</div>
<div class="row">
<div class="col-7 mt-1">
Showing 1 - {mails.length} of {mails.length}
</div>
<!-- end col-->
<div class="col-5">
<ButtonGroup class="float-end">
<button type="button" class="btn btn-icon"><Icon name="chevron-left" /></button>
<button type="button" class="btn btn-icon"><Icon name="chevron-right" /></button>
</ButtonGroup>
</div>
<!-- end col-->
</div>
<!-- end row-->
</div>
</div>
</div>
</Card>
<div class="email-content">
<a href="#" class="email-subject">{mail.subject} &nbsp;&nbsp;
<span>{mail.preview}</span>
</a>
<div class="email-date">{mail.date}</div>
</div>
<div class="email-action-icons">
<ul class="list-inline">
<li class="list-inline-item">
<a href="#"><Icon name="archive" /></a>
</li>
<li class="list-inline-item">
<a href="#"><Icon name="trash" /></a>
</li>
<li class="list-inline-item">
<a href="#">
<Icon name="mail-opened" class="email-action-icons-item" />
</a>
</li>
<li class="list-inline-item">
<a href="#"><Icon name="clock" /></a>
</li>
</ul>
</div>
</li>
))
) : (
<li class="text-muted">No emails</li>
)}
</ul>
</div>
<div class="row">
<div class="col-7 mt-1">
Showing 1 - {mails.length} of {mails.length}
</div> <!-- end col-->
<div class="col-5">
<ButtonGroup class="float-end">
<button type="button" class="btn btn-icon"><Icon name="chevron-left" /></button>
<button type="button" class="btn btn-icon"><Icon name="chevron-right" /></button>
</ButtonGroup>
</div> <!-- end col-->
</div> <!-- end row-->
</div>
</div>
</div>
</Card>
<CaptureModal>
<Modal modalId="new-email">
<NewEmailModalContent />
</Modal>
</CaptureModal>
<CaptureModal>
<Modal modalId="new-email">
<NewEmailModalContent />
</Modal>
</CaptureModal>
</DefaultLayout>
+87 -96
View File
@@ -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)
---
<DefaultLayout
pageHeader="Email templates"
pageMenu="addons.emails"
pageLibs={['masonry', 'fslightbox']}
>
<div class="row row-cards">
<div class="col-12">
<div class="card card-md">
<CardStamp size="lg" icon="mail" color="primary" />
<div class="card-body">
<div class="row align-items-center">
<div class="col-10">
<h3 class="h1">Tabler Emails</h3>
<Prose class="text-secondary fs-3">
{emailEntries.length} eye-catching, customizable and responsive email templates to improve your email communication. No coding skills needed.
</Prose>
<div class="mt-3">
<a href={site.emails.buy_link} class="btn btn-primary" target="_blank">Buy all emails for {site.emails.price}</a>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-12">
<div class="row row-cards" data-masonry={'{"percentPosition": true }'}>
{emailEntries.map(([key, email]) => (
<div class="col-4">
<a
href={`./static/emails/${key}.jpg`}
data-bs-toggle="modal"
data-bs-target="#email-modal"
data-bs-title={email.descriptionShort}
data-bs-description={email.descriptionLong}
data-bs-image={`./static/emails/${key}.jpg`}
>
<img src={`./static/emails/${key}.jpg`} class="img-fluid rounded" alt={email.descriptionShort} width={email.width} height={email.height} />
</a>
</div>
))}
</div>
</div>
</div>
<DefaultLayout pageHeader="Email templates" pageMenu="addons.emails" pageLibs={['masonry', 'fslightbox']}>
<div class="row row-cards">
<div class="col-12">
<div class="card card-md">
<CardStamp size="lg" icon="mail" color="primary" />
<div class="card-body">
<div class="row align-items-center">
<div class="col-10">
<h3 class="h1">Tabler Emails</h3>
<Prose class="text-secondary fs-3">
{emailEntries.length} eye-catching, customizable and responsive email templates to improve your email communication. No coding skills needed.
</Prose>
<div class="mt-3">
<a href={site.emails.buy_link} class="btn btn-primary" target="_blank">Buy all emails for {site.emails.price}</a>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-12">
<div class="row row-cards" data-masonry={'{"percentPosition": true }'}>
{
emailEntries.map(([key, email]) => (
<div class="col-4">
<a href={`./static/emails/${key}.jpg`} data-bs-toggle="modal" data-bs-target="#email-modal" data-bs-title={email.descriptionShort} data-bs-description={email.descriptionLong} data-bs-image={`./static/emails/${key}.jpg`}>
<img src={`./static/emails/${key}.jpg`} class="img-fluid rounded" alt={email.descriptionShort} width={email.width} height={email.height} />
</a>
</div>
))
}
</div>
</div>
</div>
<div class="modal fade" id="email-modal" aria-hidden="true" aria-labelledby="email-modal-label" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<div class="modal-body p-0">
<div class="row g-0">
<div class="col-6">
<div class="p-6 bg-surface-secondary rounded-start">
<img src="" class="img-fluid rounded-start" data-email-image />
</div>
</div>
<div class="col-6">
<div class="p-7">
<Prose>
<h3 data-email-title></h3>
<p data-email-description></p>
</Prose>
<div class="modal fade" id="email-modal" aria-hidden="true" aria-labelledby="email-modal-label" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<div class="modal-body p-0">
<div class="row g-0">
<div class="col-6">
<div class="p-6 bg-surface-secondary rounded-start">
<img src="" class="img-fluid rounded-start" data-email-image />
</div>
</div>
<div class="col-6">
<div class="p-7">
<Prose>
<h3 data-email-title></h3>
<p data-email-description></p>
</Prose>
<div class="mt-6">
<a href={site.emails.buy_link} class="btn btn-primary w-100" target="_blank">Buy {site.emails.count} emails for {site.emails.price}</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<CaptureScript>
<!-- BEGIN EMAIL MODAL SCRIPT -->
<script is:inline>
const emailModal = document.getElementById('email-modal');
if (emailModal) {
emailModal.addEventListener('show.bs.modal', function (e) {
const button = e.relatedTarget;
if (!(button instanceof HTMLElement)) return;
<div class="mt-6">
<a href={site.emails.buy_link} class="btn btn-primary w-100" target="_blank">Buy {site.emails.count} emails for {site.emails.price}</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<CaptureScript>
<!-- BEGIN EMAIL MODAL SCRIPT -->
<script is:inline>
const emailModal = document.getElementById('email-modal')
if (emailModal) {
emailModal.addEventListener('show.bs.modal', function (e) {
const button = e.relatedTarget
if (!(button instanceof HTMLElement)) return
const image = button.getAttribute('data-bs-image'),
title = button.getAttribute('data-bs-title'),
description = button.getAttribute('data-bs-description');
const image = button.getAttribute('data-bs-image'),
title = button.getAttribute('data-bs-title'),
description = button.getAttribute('data-bs-description')
emailModal.querySelector('[data-email-title]').textContent = title;
emailModal.querySelector('[data-email-description]').textContent = description;
emailModal.querySelector('[data-email-image]').src = image;
});
}
</script>
<!-- END EMAIL MODAL SCRIPT -->
</CaptureScript>
emailModal.querySelector('[data-email-title]').textContent = title
emailModal.querySelector('[data-email-description]').textContent = description
emailModal.querySelector('[data-email-image]').src = image
})
}
</script>
<!-- END EMAIL MODAL SCRIPT -->
</CaptureScript>
</DefaultLayout>
+25 -30
View File
@@ -1,37 +1,32 @@
---
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import Card from '@ui/Card.astro';
import CardHeader from '@ui/CardHeader.astro';
import CardBody from '@ui/CardBody.astro';
import flags from '@data/flags.json';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import Card from '@ui/Card.astro'
import CardHeader from '@ui/CardHeader.astro'
import CardBody from '@ui/CardBody.astro'
import flags from '@data/flags.json'
// 21 filler divs for flexbox layout.
const fillers = Array.from({ length: 21 });
const fillers = Array.from({ length: 21 })
---
<DefaultLayout title="Flags" pageHeader="Flags" pageMenu="addons.flags">
<Card>
<CardHeader>
<div class="card-title">List of all flags</div>
</CardHeader>
<CardBody class="p-0">
<div class="demo-icons-list-wrap">
<div class="demo-icons-list">
{
flags.map((country) => (
<span
class="demo-icons-list-item"
title={country.name}
data-bs-toggle="tooltip"
data-bs-placement="top"
>
<span class={`flag flag-country-${country.flag.toLowerCase()}`} />
</span>
))
}
{fillers.map(() => <div></div>)}
</div>
</div>
</CardBody>
</Card>
<Card>
<CardHeader>
<div class="card-title">List of all flags</div>
</CardHeader>
<CardBody class="p-0">
<div class="demo-icons-list-wrap">
<div class="demo-icons-list">
{
flags.map((country) => (
<span class="demo-icons-list-item" title={country.name} data-bs-toggle="tooltip" data-bs-placement="top">
<span class={`flag flag-country-${country.flag.toLowerCase()}`} />
</span>
))
}
{fillers.map(() => <div />)}
</div>
</div>
</CardBody>
</Card>
</DefaultLayout>
+19 -25
View File
@@ -1,32 +1,26 @@
---
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import GalleryPhoto from '@shared/components/cards/GalleryPhoto.astro';
import Pagination from '@ui/Pagination.astro';
import photos from '@data/photos.json';
import people from '@data/people.json';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import GalleryPhoto from '@shared/components/cards/GalleryPhoto.astro'
import Pagination from '@ui/Pagination.astro'
import photos from '@data/photos.json'
import people from '@data/people.json'
// Horizontal photos, limit 15; person = people[loop index].
const galleryPhotos = photos.filter((photo) => photo.horizontal).slice(0, 15);
const galleryPhotos = photos.filter((photo) => photo.horizontal).slice(0, 15)
---
<DefaultLayout
title="Gallery"
pageHeader="Gallery"
description="1-15 of 241 photos"
actions="photos"
pageMenu="extra.gallery"
>
<div class="row row-cards">
{
galleryPhotos.map((photo, index) => (
<div class="col-sm-6 col-lg-4">
<GalleryPhoto photo={photo} person={people[index]} index={index + 1} />
</div>
))
}
</div>
<DefaultLayout title="Gallery" pageHeader="Gallery" description="1-15 of 241 photos" actions="photos" pageMenu="extra.gallery">
<div class="row row-cards">
{
galleryPhotos.map((photo, index) => (
<div class="col-sm-6 col-lg-4">
<GalleryPhoto photo={photo} person={people[index]} index={index + 1} />
</div>
))
}
</div>
<div class="d-flex mt-5">
<Pagination class="ms-auto" />
</div>
<div class="d-flex mt-5">
<Pagination class="ms-auto" />
</div>
</DefaultLayout>
+173 -187
View File
@@ -1,207 +1,193 @@
---
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import Icon from '@ui/Icon.astro';
import CardStamp from '@ui/CardStamp.astro';
import Card from '@ui/Card.astro';
import CardBody from '@ui/CardBody.astro';
import Prose from '@ui/Prose.astro';
import CaptureScript from '@shared/components/CaptureScript.astro';
import freeIllustrations from '@data/free-illustrations.json';
import illustrationsList from '@data/illustrations.json';
import siteData from '@data/site.json';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import Icon from '@ui/Icon.astro'
import CardStamp from '@ui/CardStamp.astro'
import Card from '@ui/Card.astro'
import CardBody from '@ui/CardBody.astro'
import Prose from '@ui/Prose.astro'
import CaptureScript from '@shared/components/CaptureScript.astro'
import freeIllustrations from '@data/free-illustrations.json'
import illustrationsList from '@data/illustrations.json'
import siteData from '@data/site.json'
const autodark = (freeIllustrations as { autodark: Record<string, string> }).autodark;
const autodarkEntries = Object.entries(autodark);
const autodark = (freeIllustrations as { autodark: Record<string, string> }).autodark
const autodarkEntries = Object.entries(autodark)
// Last autodark entry (loop overwrites each pass).
const firstIllustration = autodarkEntries.length
? autodarkEntries[autodarkEntries.length - 1][1]
: '';
const firstIllustration = autodarkEntries.length ? autodarkEntries[autodarkEntries.length - 1][1] : ''
const withClass = (svg: string) => svg.replaceAll('<svg ', '<svg class="w-100 h-auto" ');
const withClass = (svg: string) => svg.replaceAll('<svg ', '<svg class="w-100 h-auto" ')
const colors = siteData.colors as Record<string, { hex: string; class: string; prop: string }>;
const skinColors = siteData.skinColors as Record<string, { hex: string; class: string }>;
const colorEntries = Object.values(colors);
const skinEntries = Object.values(skinColors);
const colors = siteData.colors as Record<string, { hex: string; class: string; prop: string }>
const skinColors = siteData.skinColors as Record<string, { hex: string; class: string }>
const colorEntries = Object.values(colors)
const skinEntries = Object.values(skinColors)
const skinFirst = skinEntries[0];
const buyLink = (siteData.illustrations as { buy_link: string }).buy_link;
const skinFirst = skinEntries[0]
const buyLink = (siteData.illustrations as { buy_link: string }).buy_link
const moreCount = illustrationsList.length - 4;
const moreCount = illustrationsList.length - 4
const illustrationsData = Object.fromEntries(
autodarkEntries.map(([key, svg]) => [key, { svg: withClass(svg) }]),
);
const illustrationsData = Object.fromEntries(autodarkEntries.map(([key, svg]) => [key, { svg: withClass(svg) }]))
---
<DefaultLayout
title="SVG Illustrations"
pageHeader="SVG Illustrations"
pageMenu="addons.illustrations"
>
<div
class="mb-7"
style={`--tblr-illustrations-primary: var(--tblr-color-primary); --tblr-illustrations-skin: ${skinFirst.hex};`}
id="current-illustration-style"
>
<div class="row row-cards">
<div class="col-12">
<div class="row row-cards row-deck g-4">
<div class="col-md-7">
<Card>
<CardBody class="d-flex align-items-center">
<div id="current-illustration" set:html={withClass(firstIllustration)} />
</CardBody>
</Card>
</div>
<div class="col-md-5">
<div class="row">
<div class="col-12">
<Card>
<CardBody>
<div>
<div class="form-label">Primary color</div>
<div class="row g-2">
<div class="col-auto">
<label class="form-colorinput">
<input name="color" type="radio" value="var(--tblr-color-primary)" class="form-colorinput-input js-select-color" checked />
<span class="form-colorinput-color bg-primary"></span>
</label>
</div>
<div class="col-auto">
<label class="form-colorinput">
<input name="color" type="radio" value="var(--tblr-bg-surface-inverted)" class="form-colorinput-input js-select-color" />
<span class="form-colorinput-color bg-inverted"></span>
</label>
</div>
{
colorEntries.map((color) => (
<div class="col-auto">
<label class="form-colorinput">
<input name="color" type="radio" value={color.hex} class="form-colorinput-input js-select-color" />
<span class={`form-colorinput-color bg-${color.class}`} />
</label>
</div>
))
}
</div>
<DefaultLayout title="SVG Illustrations" pageHeader="SVG Illustrations" pageMenu="addons.illustrations">
<div class="mb-7" style={`--tblr-illustrations-primary: var(--tblr-color-primary); --tblr-illustrations-skin: ${skinFirst.hex};`} id="current-illustration-style">
<div class="row row-cards">
<div class="col-12">
<div class="row row-cards row-deck g-4">
<div class="col-md-7">
<Card>
<CardBody class="d-flex align-items-center">
<div id="current-illustration" set:html={withClass(firstIllustration)} />
</CardBody>
</Card>
</div>
<div class="col-md-5">
<div class="row">
<div class="col-12">
<Card>
<CardBody>
<div>
<div class="form-label">Primary color</div>
<div class="row g-2">
<div class="col-auto">
<label class="form-colorinput">
<input name="color" type="radio" value="var(--tblr-color-primary)" class="form-colorinput-input js-select-color" checked />
<span class="form-colorinput-color bg-primary"></span>
</label>
</div>
<div class="col-auto">
<label class="form-colorinput">
<input name="color" type="radio" value="var(--tblr-bg-surface-inverted)" class="form-colorinput-input js-select-color" />
<span class="form-colorinput-color bg-inverted"></span>
</label>
</div>
{
colorEntries.map((color) => (
<div class="col-auto">
<label class="form-colorinput">
<input name="color" type="radio" value={color.hex} class="form-colorinput-input js-select-color" />
<span class={`form-colorinput-color bg-${color.class}`} />
</label>
</div>
))
}
</div>
<div class="form-label mt-4">Skin color</div>
<div class="row g-2">
{
skinEntries.map((color, i) => (
<div class="col-auto">
<label class="form-colorinput">
<input name="skin-color" type="radio" value={color.hex} class="form-colorinput-input js-select-skin-color" checked={i === 0} />
<span class="form-colorinput-color" style={`background-color: ${color.hex}`} />
</label>
</div>
))
}
</div>
<div class="form-label mt-4">Skin color</div>
<div class="row g-2">
{
skinEntries.map((color, i) => (
<div class="col-auto">
<label class="form-colorinput">
<input name="skin-color" type="radio" value={color.hex} class="form-colorinput-input js-select-skin-color" checked={i === 0} />
<span class="form-colorinput-color" style={`background-color: ${color.hex}`} />
</label>
</div>
))
}
</div>
<div class="form-label mt-4">Select SVG illustration</div>
<div class="row">
{
autodarkEntries.map(([key, svg], i) => (
<div class="col-3">
<label class="form-imagecheck mb-2">
<input name="form-imagecheck" type="radio" value={key} class="form-imagecheck-input js-select-illustration" checked={i === autodarkEntries.length - 1} />
<span class="form-imagecheck-figure" set:html={withClass(svg)} />
</label>
</div>
))
}
</div>
</div>
</CardBody>
</Card>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="form-label mt-4">Select SVG illustration</div>
<div class="row">
{
autodarkEntries.map(([key, svg], i) => (
<div class="col-3">
<label class="form-imagecheck mb-2">
<input name="form-imagecheck" type="radio" value={key} class="form-imagecheck-input js-select-illustration" checked={i === autodarkEntries.length - 1} />
<span class="form-imagecheck-figure" set:html={withClass(svg)} />
</label>
</div>
))
}
</div>
</div>
</CardBody>
</Card>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<h2 class="page-title my-5">
{moreCount} more SVG Illustrations
</h2>
<h2 class="page-title my-5">
{moreCount} more SVG Illustrations
</h2>
<div class="row row-cards">
<div class="col-lg-4">
<Card size="md" class="sticky-top">
<CardStamp size="lg" icon="brand-figma" color="primary" />
<CardBody>
<div class="row align-items-center">
<div class="col-10">
<h3 class="h1">Tabler Illustrations</h3>
<Prose class="text-secondary">
Access a wide range of SVG illustrations for various projects. Effortlessly customize any illustration to align perfectly with your chosen color scheme!
</Prose>
<div class="mt-3">
<a href={buyLink} class="btn btn-primary" target="_blank" rel="noopener">
<Icon name="download" />
Get lifetime access
</a>
</div>
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-lg-8">
<div class="row row-cards">
{
illustrationsList.map((illustration) => (
<div class="col-6 col-md-3">
<Card>
<CardBody>
<a href={buyLink} target="_blank">
<img src={`./static/illustrations/light/${illustration}.png`} alt={illustration} class="img-light" />
<img src={`./static/illustrations/dark/${illustration}.png`} alt={illustration} class="img-dark" />
</a>
</CardBody>
</Card>
</div>
))
}
</div>
</div>
</div>
<CaptureScript>
<!-- BEGIN ILLUSTRATIONS SCRIPT -->
<script is:inline define:vars={{ illustrationsData }}>
// @ts-nocheck — illustrationsData is injected at runtime by define:vars, which
// the type checker can't see since is:inline skips TS processing of this script.
let skinColor = 'var()',
primaryColor = 'var()';
<div class="row row-cards">
<div class="col-lg-4">
<Card size="md" class="sticky-top">
<CardStamp size="lg" icon="brand-figma" color="primary" />
<CardBody>
<div class="row align-items-center">
<div class="col-10">
<h3 class="h1">Tabler Illustrations</h3>
<Prose class="text-secondary"> Access a wide range of SVG illustrations for various projects. Effortlessly customize any illustration to align perfectly with your chosen color scheme! </Prose>
<div class="mt-3">
<a href={buyLink} class="btn btn-primary" target="_blank" rel="noopener">
<Icon name="download" />
Get lifetime access
</a>
</div>
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-lg-8">
<div class="row row-cards">
{
illustrationsList.map((illustration) => (
<div class="col-6 col-md-3">
<Card>
<CardBody>
<a href={buyLink} target="_blank">
<img src={`./static/illustrations/light/${illustration}.png`} alt={illustration} class="img-light" />
<img src={`./static/illustrations/dark/${illustration}.png`} alt={illustration} class="img-dark" />
</a>
</CardBody>
</Card>
</div>
))
}
</div>
</div>
</div>
<CaptureScript>
<!-- BEGIN ILLUSTRATIONS SCRIPT -->
<script is:inline define:vars={{ illustrationsData }}>
// @ts-nocheck — illustrationsData is injected at runtime by define:vars, which
// the type checker can't see since is:inline skips TS processing of this script.
let skinColor = 'var()',
primaryColor = 'var()'
const illustrations = illustrationsData;
const currentIllustration = document.getElementById('current-illustration');
const illustrations = illustrationsData
const currentIllustration = document.getElementById('current-illustration')
document.querySelectorAll('.js-select-illustration').forEach((elem) => {
elem.addEventListener('change', (e) => {
const selectedIllustration = illustrations[e.target.value];
currentIllustration.innerHTML = selectedIllustration.svg;
});
});
document.querySelectorAll('.js-select-illustration').forEach((elem) => {
elem.addEventListener('change', (e) => {
const selectedIllustration = illustrations[e.target.value]
currentIllustration.innerHTML = selectedIllustration.svg
})
})
document.querySelectorAll('.js-select-color').forEach((elem) => {
elem.addEventListener('change', (e) => {
primaryColor = e.target.value;
document.getElementById('current-illustration-style').style.setProperty('--tblr-illustrations-primary', primaryColor);
});
});
document.querySelectorAll('.js-select-color').forEach((elem) => {
elem.addEventListener('change', (e) => {
primaryColor = e.target.value
document.getElementById('current-illustration-style').style.setProperty('--tblr-illustrations-primary', primaryColor)
})
})
document.querySelectorAll('.js-select-skin-color').forEach((elem) => {
elem.addEventListener('change', (e) => {
skinColor = e.target.value;
document.getElementById('current-illustration-style').style.setProperty('--tblr-illustrations-skin', skinColor);
});
});
</script>
<!-- END ILLUSTRATIONS SCRIPT -->
</CaptureScript>
document.querySelectorAll('.js-select-skin-color').forEach((elem) => {
elem.addEventListener('change', (e) => {
skinColor = e.target.value
document.getElementById('current-illustration-style').style.setProperty('--tblr-illustrations-skin', skinColor)
})
})
</script>
<!-- END ILLUSTRATIONS SCRIPT -->
</CaptureScript>
</DefaultLayout>
+22 -27
View File
@@ -1,34 +1,29 @@
---
// One card per inline-player provider.
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 InlinePlayer from '@ui/InlinePlayer.astro';
import players from '@data/inline-players.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 InlinePlayer from '@ui/InlinePlayer.astro'
import players from '@data/inline-players.json'
type Provider = { title: string; type: string; id: string; 'embed-id': string | number };
type Provider = { 'title': string; 'type': string; 'id': string; 'embed-id': string | number }
---
<DefaultLayout
title="Inline Player"
pageHeader="Inline Player"
pageMenu="plugins.plyr"
pageLibs={['plyr']}
>
<div class="row row-cards">
{
(players as Provider[]).map((provider) => (
<div class="col-lg-6">
<Card>
<CardBody>
<CardTitle>{provider.title}</CardTitle>
<DefaultLayout title="Inline Player" pageHeader="Inline Player" pageMenu="plugins.plyr" pageLibs={['plyr']}>
<div class="row row-cards">
{
(players as Provider[]).map((provider) => (
<div class="col-lg-6">
<Card>
<CardBody>
<CardTitle>{provider.title}</CardTitle>
<InlinePlayer id={provider.id} type={provider.type} embedId={provider['embed-id']} />
</CardBody>
</Card>
</div>
))
}
</div>
<InlinePlayer id={provider.id} type={provider.type} embedId={provider['embed-id']} />
</CardBody>
</Card>
</div>
))
}
</div>
</DefaultLayout>
+133 -135
View File
@@ -1,151 +1,149 @@
---
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import Icon from '@ui/Icon.astro';
import Card from '@ui/Card.astro';
import Avatar from '@ui/Avatar.astro';
import Check from '@ui/form/Check.astro';
import jobsData from '@data/jobs.json';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import Icon from '@ui/Icon.astro'
import Card from '@ui/Card.astro'
import Avatar from '@ui/Avatar.astro'
import Check from '@ui/form/Check.astro'
import jobsData from '@data/jobs.json'
interface Job {
company: string;
location: string;
title: string;
type: string;
image: string;
salary?: string;
tags: string[];
company: string
location: string
title: string
type: string
image: string
salary?: string
tags: string[]
}
const jobs = jobsData as Job[];
const jobs = jobsData as Job[]
const types = ['Programming', 'Design', 'Management / Finance', 'Customer Support', 'Sales / Marketing'];
const salaries = ['$20K - $50K', '$50K - $100K', '> $100K', 'Drawing / Painting'];
const types = ['Programming', 'Design', 'Management / Finance', 'Customer Support', 'Sales / Marketing']
const salaries = ['$20K - $50K', '$50K - $100K', '> $100K', 'Drawing / Painting']
---
<DefaultLayout title="Search for Jobs" pageHeader="Search for Jobs" actions="add-job" pageMenu="extra.job-listing">
<div class="row g-4">
<div class="col-md-3">
<form action="./" method="get" autocomplete="off" novalidate class="sticky-top">
<div class="form-label">Job Types</div>
<div class="mb-4">
{
types.map((type, i) => (
<label class="form-check">
<input type="checkbox" class="form-check-input" name="form-type[]" value={i + 1} checked={i + 1 <= 2} />
<span class="form-check-label">{type}</span>
</label>
))
}
</div>
<div class="row g-4">
<div class="col-md-3">
<form action="./" method="get" autocomplete="off" novalidate class="sticky-top">
<div class="form-label">Job Types</div>
<div class="mb-4">
{
types.map((type, i) => (
<label class="form-check">
<input type="checkbox" class="form-check-input" name="form-type[]" value={i + 1} checked={i + 1 <= 2} />
<span class="form-check-label">{type}</span>
</label>
))
}
</div>
<div class="form-label">Remote</div>
<div class="mb-4">
<Check switch={true} titleOn="On" titleOff="Off" />
</div>
<div class="form-label">Remote</div>
<div class="mb-4">
<Check switch={true} titleOn="On" titleOff="Off" />
</div>
<div class="form-label">Salary Range</div>
<div class="mb-4">
{
salaries.map((salary, i) => (
<label class="form-check">
<input type="radio" class="form-check-input" name="form-salary" value={i + 1} checked={i + 1 <= 2} />
<span class="form-check-label">{salary}</span>
</label>
))
}
</div>
<div class="form-label">Salary Range</div>
<div class="mb-4">
{
salaries.map((salary, i) => (
<label class="form-check">
<input type="radio" class="form-check-input" name="form-salary" value={i + 1} checked={i + 1 <= 2} />
<span class="form-check-label">{salary}</span>
</label>
))
}
</div>
<div class="form-label">Immigration</div>
<div class="mb-4">
<Check switch={true} titleOn="On" titleOff="Off" />
<div class="form-label">Immigration</div>
<div class="mb-4">
<Check switch={true} titleOn="On" titleOff="Off" />
<div class="small text-secondary">Only show companies that can sponsor a visa</div>
</div>
<div class="small text-secondary">Only show companies that can sponsor a visa</div>
</div>
<label class="form-label" for="job-location">Location</label>
<div class="mb-4">
<select class="form-select" id="job-location" name="location">
<option>Anywhere</option>
<option>London</option>
<option>San Francisco</option>
<option>New York</option>
<option>Berlin</option>
</select>
</div>
<label class="form-label" for="job-location">Location</label>
<div class="mb-4">
<select class="form-select" id="job-location" name="location">
<option>Anywhere</option>
<option>London</option>
<option>San Francisco</option>
<option>New York</option>
<option>Berlin</option>
</select>
</div>
<div class="mt-5">
<button class="btn btn-primary w-100">
Confirm changes
</button>
<a href="#" class="btn btn-link w-100">
Reset to defaults
</a>
</div>
</form>
</div>
<div class="col-md-9">
<div class="row row-cards">
<div class="space-y">
{
jobs.map((job) => (
<Card>
<div class="row g-0">
<div class="col-auto">
<div class="card-body">
<Avatar src={`static/jobs/${job.image}`} size="md" />
</div>
</div>
<div class="col">
<div class="card-body ps-0">
<div class="row">
<div class="col">
<h3 class="mb-0">
<a href="#">{job.title}</a>
</h3>
</div>
{job.salary && <div class="col-auto fs-3 text-green">{job.salary}</div>}
</div>
<div class="row">
<div class="col-md">
<div class="mt-3 list-inline list-inline-dots mb-0 text-secondary d-sm-block d-none">
<div class="list-inline-item">
<Icon name="building-community" class="icon-inline" /> {job.company}
</div>
<div class="list-inline-item">
<Icon name="license" class="icon-inline" /> {job.type}
</div>
<div class="list-inline-item">
<Icon name="map-pin" class="icon-inline" /> {job.location}
</div>
</div>
<div class="mt-3 list mb-0 text-secondary d-block d-sm-none">
<div class="list-item">
<Icon name="building-community" class="icon-inline" /> {job.company}
</div>
<div class="list-item">
<Icon name="license" class="icon-inline" /> {job.type}
</div>
<div class="list-item">
<Icon name="map-pin" class="icon-inline" /> {job.location}
</div>
</div>
</div>
<div class="col-md-auto">
<div class="mt-3 badges">
{job.tags.map((tag) => (
<a href="#" class="badge badge-outline text-secondary fw-normal badge-pill">{tag}</a>
))}
</div>
</div>
</div>
</div>
</div>
</div>
</Card>
))
}
</div>
</div>
</div>
</div>
<div class="mt-5">
<button class="btn btn-primary w-100"> Confirm changes </button>
<a href="#" class="btn btn-link w-100"> Reset to defaults </a>
</div>
</form>
</div>
<div class="col-md-9">
<div class="row row-cards">
<div class="space-y">
{
jobs.map((job) => (
<Card>
<div class="row g-0">
<div class="col-auto">
<div class="card-body">
<Avatar src={`static/jobs/${job.image}`} size="md" />
</div>
</div>
<div class="col">
<div class="card-body ps-0">
<div class="row">
<div class="col">
<h3 class="mb-0">
<a href="#">{job.title}</a>
</h3>
</div>
{job.salary && <div class="col-auto fs-3 text-green">{job.salary}</div>}
</div>
<div class="row">
<div class="col-md">
<div class="mt-3 list-inline list-inline-dots mb-0 text-secondary d-sm-block d-none">
<div class="list-inline-item">
<Icon name="building-community" class="icon-inline" /> {job.company}
</div>
<div class="list-inline-item">
<Icon name="license" class="icon-inline" /> {job.type}
</div>
<div class="list-inline-item">
<Icon name="map-pin" class="icon-inline" /> {job.location}
</div>
</div>
<div class="mt-3 list mb-0 text-secondary d-block d-sm-none">
<div class="list-item">
<Icon name="building-community" class="icon-inline" /> {job.company}
</div>
<div class="list-item">
<Icon name="license" class="icon-inline" /> {job.type}
</div>
<div class="list-item">
<Icon name="map-pin" class="icon-inline" /> {job.location}
</div>
</div>
</div>
<div class="col-md-auto">
<div class="mt-3 badges">
{job.tags.map((tag) => (
<a href="#" class="badge badge-outline text-secondary fw-normal badge-pill">
{tag}
</a>
))}
</div>
</div>
</div>
</div>
</div>
</div>
</Card>
))
}
</div>
</div>
</div>
</div>
</DefaultLayout>
+54 -60
View File
@@ -1,10 +1,10 @@
---
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import Icon from '@ui/Icon.astro';
import Card from '@ui/Card.astro';
import CardBody from '@ui/CardBody.astro';
import CardFooter from '@ui/CardFooter.astro';
import Prose from '@ui/Prose.astro';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import Icon from '@ui/Icon.astro'
import Card from '@ui/Card.astro'
import CardBody from '@ui/CardBody.astro'
import CardFooter from '@ui/CardFooter.astro'
import Prose from '@ui/Prose.astro'
const licenseHtml = `
<p>This is a legal agreement between you, the Purchaser, and Tabler. Purchasing or downloading of any Tabler product (Tabler Free, Tabler PRO, Tabler Email), constitutes your acceptance of the terms of this license, <a href="https://tabler.io/terms-of-service.html">Tabler terms of service</a> and <a href="https://tabler.io/privacy-policy.html">Tabler private policy</a>.</p>
@@ -31,66 +31,60 @@ const licenseHtml = `
<li>You cannot add our source code to any open source repository.</li>
<li>The source code may not be placed on any website in a complete or archived downloadable format.</li>
</ol>
`;
`
---
<DefaultLayout title="License" pageHeader="Tabler License" pageMenu="extra.license">
<div class="row row-cards">
<div class="col-lg-8">
<Card size="lg">
<CardBody>
<Prose>
<Fragment set:html={licenseHtml} />
</Prose>
</CardBody>
</Card>
</div>
<div class="col-lg-4">
<Card>
<CardBody>
<div class="d-flex align-items-center mb-3">
<div class="me-3">
<Icon name="scale" size="md" />
</div>
<div>
<small class="text-secondary">tabler/tabler is licensed under the</small>
<h3 class="lh-1">MIT License</h3>
</div>
</div>
<div class="row row-cards">
<div class="col-lg-8">
<Card size="lg">
<CardBody>
<Prose>
<Fragment set:html={licenseHtml} />
</Prose>
</CardBody>
</Card>
</div>
<div class="col-lg-4">
<Card>
<CardBody>
<div class="d-flex align-items-center mb-3">
<div class="me-3">
<Icon name="scale" size="md" />
</div>
<div>
<small class="text-secondary">tabler/tabler is licensed under the</small>
<h3 class="lh-1">MIT License</h3>
</div>
</div>
<div class="text-secondary mb-3">
A short and simple permissive license with conditions only requiring preservation of copyright and
license notices. Licensed works, modifications, and larger works may be distributed under different terms
and without source code.
</div>
<div class="text-secondary mb-3">A short and simple permissive license with conditions only requiring preservation of copyright and license notices. Licensed works, modifications, and larger works may be distributed under different terms and without source code.</div>
<h4>Permissions</h4>
<h4>Permissions</h4>
<ul class="list-unstyled space-y-1">
<li><Icon name="check" class="text-green" /> Commercial use</li>
<li><Icon name="check" class="text-green" /> Modification</li>
<li><Icon name="check" class="text-green" /> Distribution</li>
<li><Icon name="check" class="text-green" /> Private use</li>
</ul>
<ul class="list-unstyled space-y-1">
<li><Icon name="check" class="text-green" /> Commercial use</li>
<li><Icon name="check" class="text-green" /> Modification</li>
<li><Icon name="check" class="text-green" /> Distribution</li>
<li><Icon name="check" class="text-green" /> Private use</li>
</ul>
<h4>Limitations</h4>
<ul class="list-unstyled space-y-1">
<li><Icon name="x" class="text-red" /> Liability</li>
<li><Icon name="x" class="text-red" /> Warranty</li>
</ul>
<h4>Limitations</h4>
<ul class="list-unstyled space-y-1">
<li><Icon name="x" class="text-red" /> Liability</li>
<li><Icon name="x" class="text-red" /> Warranty</li>
</ul>
<h4>Conditions</h4>
<ul class="list-unstyled space-y-1">
<li><Icon name="info-circle" class="text-blue" /> License and copyright notice</li>
</ul>
</CardBody>
<CardFooter>
This is not legal advice.
<a href="#" target="_blank">Learn more about repository licenses.</a>
</CardFooter>
</Card>
</div>
</div>
<h4>Conditions</h4>
<ul class="list-unstyled space-y-1">
<li><Icon name="info-circle" class="text-blue" /> License and copyright notice</li>
</ul>
</CardBody>
<CardFooter>
This is not legal advice.
<a href="#" target="_blank">Learn more about repository licenses.</a>
</CardFooter>
</Card>
</div>
</div>
</DefaultLayout>
+16 -21
View File
@@ -1,27 +1,22 @@
---
// Gallery uses horizontal photos only.
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import Photo from '@ui/Photo.astro';
import photos from '@data/photos.json';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import Photo from '@ui/Photo.astro'
import photos from '@data/photos.json'
const filteredPhotos = photos.filter((photo) => photo.horizontal);
const filteredPhotos = photos.filter((photo) => photo.horizontal)
---
<DefaultLayout
title="Lightbox"
pageHeader="Lightbox"
pageMenu="plugins.lightbox"
pageLibs={['fslightbox']}
>
<div class="row row-cols-3 row-cols-md-4 row-cols-lg-6 g-3">
{
filteredPhotos.map((photo) => (
<div class="col">
<a data-fslightbox="gallery" href={`./static/photos/${photo.file}`}>
<Photo photo={photo} class="rounded border" ratio="1x1" />
</a>
</div>
))
}
</div>
<DefaultLayout title="Lightbox" pageHeader="Lightbox" pageMenu="plugins.lightbox" pageLibs={['fslightbox']}>
<div class="row row-cols-3 row-cols-md-4 row-cols-lg-6 g-3">
{
filteredPhotos.map((photo) => (
<div class="col">
<a data-fslightbox="gallery" href={`./static/photos/${photo.file}`}>
<Photo photo={photo} class="rounded border" ratio="1x1" />
</a>
</div>
))
}
</div>
</DefaultLayout>
+20 -26
View File
@@ -1,37 +1,31 @@
---
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import CaptureScript from '@shared/components/CaptureScript.astro';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import CaptureScript from '@shared/components/CaptureScript.astro'
---
<DefaultLayout
pageMenu="plugins.map-fullsize"
pageLibs={['google-maps']}
sidebar
hideTopbar
wrapperFull
>
<div class="map flex-fill" id="map-google"></div>
<!--
<DefaultLayout pageMenu="plugins.map-fullsize" pageLibs={['google-maps']} sidebar hideTopbar wrapperFull>
<div class="map flex-fill" id="map-google"></div>
<!--
is:inline: google-maps loads via a deferred page-lib <script>; see
FormElements1.astro for why a processed (module) script here would run too
early and find window.google undefined.
-->
<CaptureScript>
<!-- BEGIN MAP SCRIPT -->
<script is:inline>
window.tabler_map ??= {};
<CaptureScript>
<!-- BEGIN MAP SCRIPT -->
<script is:inline>
window.tabler_map ??= {}
function initMap() {
const map = new google.maps.Map(document.getElementById('map-google'), {
center: { lat: -34.397, lng: 150.644 },
zoom: 8,
});
function initMap() {
const map = new google.maps.Map(document.getElementById('map-google'), {
center: { lat: -34.397, lng: 150.644 },
zoom: 8,
})
window.tabler_map['map-google'] = map;
}
window.tabler_map['map-google'] = map
}
document.readyState !== 'loading' ? initMap() : document.addEventListener('DOMContentLoaded', initMap, { once: true });
</script>
<!-- END MAP SCRIPT -->
</CaptureScript>
document.readyState !== 'loading' ? initMap() : document.addEventListener('DOMContentLoaded', initMap, { once: true })
</script>
<!-- END MAP SCRIPT -->
</CaptureScript>
</DefaultLayout>
+29 -29
View File
@@ -1,36 +1,36 @@
---
// Card maps span full-width column with no card-body; non-card maps get card-body with title.
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import Map from '@ui/Map.astro';
import Card from '@ui/Card.astro';
import CardBody from '@ui/CardBody.astro';
import maps from '@data/maps.json';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import Map from '@ui/Map.astro'
import Card from '@ui/Card.astro'
import CardBody from '@ui/CardBody.astro'
import maps from '@data/maps.json'
type MapData = { title?: string; card?: boolean };
const mapEntries = Object.entries(maps as Record<string, MapData>);
type MapData = { title?: string; card?: boolean }
const mapEntries = Object.entries(maps as Record<string, MapData>)
---
<DefaultLayout title="Maps" pageHeader="Maps" pageMenu="plugins.maps" pageLibs={['mapbox']}>
<div class="row row-cards">
{
mapEntries.map(([mapId, data]) =>
data.card ? (
<div class="col-lg-12">
<Card>
<Map mapId={mapId} ratio="21x9" />
</Card>
</div>
) : (
<div class="col-lg-6">
<Card>
<CardBody>
<div class="card-title">{data.title}</div>
<Map mapId={mapId} />
</CardBody>
</Card>
</div>
),
)
}
</div>
<div class="row row-cards">
{
mapEntries.map(([mapId, data]) =>
data.card ? (
<div class="col-lg-12">
<Card>
<Map mapId={mapId} ratio="21x9" />
</Card>
</div>
) : (
<div class="col-lg-6">
<Card>
<CardBody>
<div class="card-title">{data.title}</div>
<Map mapId={mapId} />
</CardBody>
</Card>
</div>
),
)
}
</div>
</DefaultLayout>
+1 -1
View File
@@ -1,5 +1,5 @@
---
import RedirectLayout from '@shared/layouts/RedirectLayout.astro';
import RedirectLayout from '@shared/layouts/RedirectLayout.astro'
---
<RedirectLayout />
+13 -13
View File
@@ -1,19 +1,19 @@
---
import MarketingLayout from '@shared/layouts/MarketingLayout.astro';
import Pricing from '@shared/components/marketing/sections/Pricing.astro';
import PricingBanner from '@shared/components/marketing/sections/PricingBanner.astro';
import Faq from '@shared/components/marketing/sections/Faq.astro';
import MarketingLayout from '@shared/layouts/MarketingLayout.astro'
import Pricing from '@shared/components/marketing/sections/Pricing.astro'
import PricingBanner from '@shared/components/marketing/sections/PricingBanner.astro'
import Faq from '@shared/components/marketing/sections/Faq.astro'
---
<MarketingLayout title="Pricing">
<header class="hero">
<div class="container">
<h2 class="hero-title">Simple, transparent pricing</h2>
<p class="hero-description">Get early access to 100+ components and free updates every month. Make it yours today!</p>
</div>
</header>
<header class="hero">
<div class="container">
<h2 class="hero-title">Simple, transparent pricing</h2>
<p class="hero-description">Get early access to 100+ components and free updates every month. Make it yours today!</p>
</div>
</header>
<Pricing class="pt-0" />
<PricingBanner />
<Faq background="light" />
<Pricing class="pt-0" />
<PricingBanner />
<Faq background="light" />
</MarketingLayout>
+156 -161
View File
@@ -1,169 +1,164 @@
---
// Every modal is rendered inline via ModalInline (in place, unlike Modal.astro
// which drains to <body>).
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import Card from '@ui/Card.astro';
import CardBody from '@ui/CardBody.astro';
import ModalInline from '@shared/components/modals/ModalInline.astro';
import SimpleModalContent from '@shared/components/modals/SimpleModalContent.astro';
import LargeModalContent from '@shared/components/modals/LargeModalContent.astro';
import SmallModalContent from '@shared/components/modals/SmallModalContent.astro';
import FullWidthModalContent from '@shared/components/modals/FullWidthModalContent.astro';
import ScrollableModalContent from '@shared/components/modals/ScrollableModalContent.astro';
import ReportModalContent from '@shared/components/modals/ReportModalContent.astro';
import SuccessModalContent from '@shared/components/modals/SuccessModalContent.astro';
import DangerModalContent from '@shared/components/modals/DangerModalContent.astro';
import TeamModalContent from '@shared/components/modals/TeamModalContent.astro';
import SignatureModalContent from '@shared/components/modals/SignatureModalContent.astro';
import NewEmailModalContent from '@shared/components/modals/NewEmailModalContent.astro';
import NewEventModalContent from '@shared/components/modals/NewEventModalContent.astro';
import NewTaskModalContent from '@shared/components/modals/NewTaskModalContent.astro';
import EditProfileModalContent from '@shared/components/modals/EditProfileModalContent.astro';
import ConfirmDeleteModalContent from '@shared/components/modals/ConfirmDeleteModalContent.astro';
import ChangePasswordModalContent from '@shared/components/modals/ChangePasswordModalContent.astro';
import AddTaskModalContent from '@shared/components/modals/AddTaskModalContent.astro';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import Card from '@ui/Card.astro'
import CardBody from '@ui/CardBody.astro'
import ModalInline from '@shared/components/modals/ModalInline.astro'
import SimpleModalContent from '@shared/components/modals/SimpleModalContent.astro'
import LargeModalContent from '@shared/components/modals/LargeModalContent.astro'
import SmallModalContent from '@shared/components/modals/SmallModalContent.astro'
import FullWidthModalContent from '@shared/components/modals/FullWidthModalContent.astro'
import ScrollableModalContent from '@shared/components/modals/ScrollableModalContent.astro'
import ReportModalContent from '@shared/components/modals/ReportModalContent.astro'
import SuccessModalContent from '@shared/components/modals/SuccessModalContent.astro'
import DangerModalContent from '@shared/components/modals/DangerModalContent.astro'
import TeamModalContent from '@shared/components/modals/TeamModalContent.astro'
import SignatureModalContent from '@shared/components/modals/SignatureModalContent.astro'
import NewEmailModalContent from '@shared/components/modals/NewEmailModalContent.astro'
import NewEventModalContent from '@shared/components/modals/NewEventModalContent.astro'
import NewTaskModalContent from '@shared/components/modals/NewTaskModalContent.astro'
import EditProfileModalContent from '@shared/components/modals/EditProfileModalContent.astro'
import ConfirmDeleteModalContent from '@shared/components/modals/ConfirmDeleteModalContent.astro'
import ChangePasswordModalContent from '@shared/components/modals/ChangePasswordModalContent.astro'
import AddTaskModalContent from '@shared/components/modals/AddTaskModalContent.astro'
const cardClass = 'position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0';
const cardClass = 'position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0'
// add-task bakes `show` into the class string (no `show` flag) → aria-hidden="true".
const addTaskClass = 'position-relative rounded d-block show bg-surface-backdrop py-6 w-auto h-auto z-0';
const addTaskClass = 'position-relative rounded d-block show bg-surface-backdrop py-6 w-auto h-auto z-0'
---
<DefaultLayout
title="Modals"
pageHeader="Modals"
pageMenu="base.modals"
pageLibs={['signature_pad', 'hugerte', 'litepicker']}
>
<Card>
<CardBody>
<div class="row g-5">
<div class="d-none d-md-block col-3">
<div class="nav nav-vertical sticky-top pt-4">
<a href="#modal-simple" class="nav-link">Simple modal</a>
<a href="#modal-large" class="nav-link">Large modal</a>
<a href="#modal-small" class="nav-link">Small modal</a>
<a href="#modal-full-width" class="nav-link">Full width modal</a>
<a href="#modal-scrollable" class="nav-link">Scrollable modal</a>
<a href="#modal-report" class="nav-link">Modal with form</a>
<a href="#modal-success" class="nav-link">Success modal</a>
<a href="#modal-danger" class="nav-link">Danger modal</a>
<a href="#modal-team" class="nav-link">Modal with simple form</a>
<a href="#modal-signature" class="nav-link">Modal with signature form</a>
<a href="#modal-new-email" class="nav-link">New email modal</a>
<a href="#modal-new-event" class="nav-link">New event modal</a>
<a href="#modal-new-task" class="nav-link">New task modal</a>
<a href="#modal-edit-profile" class="nav-link">Edit profile modal</a>
<a href="#modal-confirm-delete" class="nav-link">Confirm delete modal</a>
<a href="#modal-change-password" class="nav-link">Change password modal</a>
</div>
</div>
<div class="col">
<div class="space-y-6">
<div>
<h3>Simple modal</h3>
<ModalInline class={cardClass} modalId="simple" show>
<SimpleModalContent />
</ModalInline>
</div>
<div>
<h3>Large modal</h3>
<ModalInline class={cardClass} modalId="large" size="lg" show>
<LargeModalContent />
</ModalInline>
</div>
<div>
<h3>Small modal</h3>
<ModalInline class={cardClass} modalId="small" size="sm" show>
<SmallModalContent />
</ModalInline>
</div>
<div>
<h3>Full width modal</h3>
<ModalInline class={cardClass} modalId="full-width" size="full-width" show>
<FullWidthModalContent />
</ModalInline>
</div>
<div>
<h3>Scrollable modal</h3>
<ModalInline class={cardClass} modalId="scrollable" scrollable style="max-height: 30rem" show>
<ScrollableModalContent />
</ModalInline>
</div>
<div>
<h3>Modal with form</h3>
<ModalInline class={cardClass} modalId="report" size="lg" show>
<ReportModalContent />
</ModalInline>
</div>
<div>
<h3>Success modal</h3>
<ModalInline class={cardClass} modalId="success" size="sm" show>
<SuccessModalContent />
</ModalInline>
</div>
<div>
<h3>Danger modal</h3>
<ModalInline class={cardClass} modalId="danger" size="sm" show>
<DangerModalContent />
</ModalInline>
</div>
<div>
<h3>Modal with simple form</h3>
<ModalInline class={cardClass} modalId="team" show>
<TeamModalContent />
</ModalInline>
</div>
<div>
<h3>Modal with signature form</h3>
<ModalInline class={cardClass} modalId="signature" show>
<SignatureModalContent />
</ModalInline>
</div>
<div>
<h3>New email modal</h3>
<ModalInline class={cardClass} modalId="new-email" show>
<NewEmailModalContent />
</ModalInline>
</div>
<div>
<h3>New event modal</h3>
<ModalInline class={cardClass} modalId="new-event" show>
<NewEventModalContent />
</ModalInline>
</div>
<div>
<h3>New task modal</h3>
<ModalInline class={cardClass} modalId="new-task" show>
<NewTaskModalContent />
</ModalInline>
</div>
<div>
<h3>Edit profile modal</h3>
<ModalInline class={cardClass} modalId="edit-profile" size="lg" show>
<EditProfileModalContent />
</ModalInline>
</div>
<div>
<h3>Confirm delete modal</h3>
<ModalInline class={cardClass} modalId="confirm-delete" size="sm" show>
<ConfirmDeleteModalContent />
</ModalInline>
</div>
<div>
<h3>Change password modal</h3>
<ModalInline class={cardClass} modalId="change-password" show>
<ChangePasswordModalContent />
</ModalInline>
</div>
<div>
<h3>Add task modal</h3>
<ModalInline class={addTaskClass} modalId="add-task">
<AddTaskModalContent />
</ModalInline>
</div>
</div>
</div>
</div>
</CardBody>
</Card>
<DefaultLayout title="Modals" pageHeader="Modals" pageMenu="base.modals" pageLibs={['signature_pad', 'hugerte', 'litepicker']}>
<Card>
<CardBody>
<div class="row g-5">
<div class="d-none d-md-block col-3">
<div class="nav nav-vertical sticky-top pt-4">
<a href="#modal-simple" class="nav-link">Simple modal</a>
<a href="#modal-large" class="nav-link">Large modal</a>
<a href="#modal-small" class="nav-link">Small modal</a>
<a href="#modal-full-width" class="nav-link">Full width modal</a>
<a href="#modal-scrollable" class="nav-link">Scrollable modal</a>
<a href="#modal-report" class="nav-link">Modal with form</a>
<a href="#modal-success" class="nav-link">Success modal</a>
<a href="#modal-danger" class="nav-link">Danger modal</a>
<a href="#modal-team" class="nav-link">Modal with simple form</a>
<a href="#modal-signature" class="nav-link">Modal with signature form</a>
<a href="#modal-new-email" class="nav-link">New email modal</a>
<a href="#modal-new-event" class="nav-link">New event modal</a>
<a href="#modal-new-task" class="nav-link">New task modal</a>
<a href="#modal-edit-profile" class="nav-link">Edit profile modal</a>
<a href="#modal-confirm-delete" class="nav-link">Confirm delete modal</a>
<a href="#modal-change-password" class="nav-link">Change password modal</a>
</div>
</div>
<div class="col">
<div class="space-y-6">
<div>
<h3>Simple modal</h3>
<ModalInline class={cardClass} modalId="simple" show>
<SimpleModalContent />
</ModalInline>
</div>
<div>
<h3>Large modal</h3>
<ModalInline class={cardClass} modalId="large" size="lg" show>
<LargeModalContent />
</ModalInline>
</div>
<div>
<h3>Small modal</h3>
<ModalInline class={cardClass} modalId="small" size="sm" show>
<SmallModalContent />
</ModalInline>
</div>
<div>
<h3>Full width modal</h3>
<ModalInline class={cardClass} modalId="full-width" size="full-width" show>
<FullWidthModalContent />
</ModalInline>
</div>
<div>
<h3>Scrollable modal</h3>
<ModalInline class={cardClass} modalId="scrollable" scrollable style="max-height: 30rem" show>
<ScrollableModalContent />
</ModalInline>
</div>
<div>
<h3>Modal with form</h3>
<ModalInline class={cardClass} modalId="report" size="lg" show>
<ReportModalContent />
</ModalInline>
</div>
<div>
<h3>Success modal</h3>
<ModalInline class={cardClass} modalId="success" size="sm" show>
<SuccessModalContent />
</ModalInline>
</div>
<div>
<h3>Danger modal</h3>
<ModalInline class={cardClass} modalId="danger" size="sm" show>
<DangerModalContent />
</ModalInline>
</div>
<div>
<h3>Modal with simple form</h3>
<ModalInline class={cardClass} modalId="team" show>
<TeamModalContent />
</ModalInline>
</div>
<div>
<h3>Modal with signature form</h3>
<ModalInline class={cardClass} modalId="signature" show>
<SignatureModalContent />
</ModalInline>
</div>
<div>
<h3>New email modal</h3>
<ModalInline class={cardClass} modalId="new-email" show>
<NewEmailModalContent />
</ModalInline>
</div>
<div>
<h3>New event modal</h3>
<ModalInline class={cardClass} modalId="new-event" show>
<NewEventModalContent />
</ModalInline>
</div>
<div>
<h3>New task modal</h3>
<ModalInline class={cardClass} modalId="new-task" show>
<NewTaskModalContent />
</ModalInline>
</div>
<div>
<h3>Edit profile modal</h3>
<ModalInline class={cardClass} modalId="edit-profile" size="lg" show>
<EditProfileModalContent />
</ModalInline>
</div>
<div>
<h3>Confirm delete modal</h3>
<ModalInline class={cardClass} modalId="confirm-delete" size="sm" show>
<ConfirmDeleteModalContent />
</ModalInline>
</div>
<div>
<h3>Change password modal</h3>
<ModalInline class={cardClass} modalId="change-password" show>
<ChangePasswordModalContent />
</ModalInline>
</div>
<div>
<h3>Add task modal</h3>
<ModalInline class={addTaskClass} modalId="add-task">
<AddTaskModalContent />
</ModalInline>
</div>
</div>
</div>
</div>
</CardBody>
</Card>
</DefaultLayout>
+21 -21
View File
@@ -1,28 +1,28 @@
---
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import TracksList from '@shared/components/cards/music/TracksList.astro';
import TrackInfo from '@shared/components/cards/music/TrackInfo.astro';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import TracksList from '@shared/components/cards/music/TracksList.astro'
import TrackInfo from '@shared/components/cards/music/TrackInfo.astro'
const topTrackIds = [8, 9, 10, 11, 12, 13];
const topTrackIds = [8, 9, 10, 11, 12, 13]
---
<DefaultLayout title="Music components" pageHeader="Music components" pageMenu="extra.music">
<div class="row">
<div class="col-lg-8">
<TracksList />
</div>
<div class="col-lg-4">
<h3 class="mb-3">Top tracks</h3>
<div class="row">
<div class="col-lg-8">
<TracksList />
</div>
<div class="col-lg-4">
<h3 class="mb-3">Top tracks</h3>
<div class="row row-cards">
{
topTrackIds.map((trackId) => (
<div class="col-md-6 col-lg-12">
<TrackInfo trackId={trackId} />
</div>
))
}
</div>
</div>
</div>
<div class="row row-cards">
{
topTrackIds.map((trackId) => (
<div class="col-md-6 col-lg-12">
<TrackInfo trackId={trackId} />
</div>
))
}
</div>
</div>
</div>
</DefaultLayout>
+25 -35
View File
@@ -2,48 +2,38 @@
// with variant params, rendered with the shared Navbar component.
//
// fluid-search (variant 5) is a no-op — search block in condensed branch is dead code.
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import Navbar from '@shared/components/navbar/Navbar.astro';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import Navbar from '@shared/components/navbar/Navbar.astro'
---
<DefaultLayout title="Navigation" pageHeader="Navigation" pageMenu="base.navigation">
<div class="box">
<div class="mb-3">
<Navbar sample condensed transparent personId={3} />
</div>
<div class="box">
<div class="mb-3">
<Navbar sample condensed transparent personId={3} />
</div>
<div class="mb-3">
<Navbar sample condensed personId={4} />
</div>
<div class="mb-3">
<Navbar sample condensed personId={4} />
</div>
<div class="mb-3">
<Navbar sample condensed dark hideLogo showTitle hideIcons hideUsername personId={5} />
</div>
<div class="mb-3">
<Navbar sample condensed dark hideLogo showTitle hideIcons hideUsername personId={5} />
</div>
<div class="mb-3">
<Navbar sample condensed dark smallLogo backgroundColor="#7952b3" hideSearch personId={6} />
</div>
<div class="mb-3">
<Navbar sample condensed dark smallLogo backgroundColor="#7952b3" hideSearch personId={6} />
</div>
<div class="mb-3">
<Navbar
sample
condensed
dark
background="primary"
hideBrand
hideIcons
fluidSearch
hideUsername
personId={7}
/>
</div>
<div class="mb-3">
<Navbar sample condensed dark background="primary" hideBrand hideIcons fluidSearch hideUsername personId={7} />
</div>
<div class="mb-3">
<Navbar sample smallLogo showTitle personId={8} />
</div>
<div class="mb-3">
<Navbar sample smallLogo showTitle personId={8} />
</div>
<div class="mb-3">
<Navbar sample dark personId={9} />
</div>
</div>
<div class="mb-3">
<Navbar sample dark personId={9} />
</div>
</div>
</DefaultLayout>
+118 -118
View File
@@ -1,126 +1,126 @@
---
// NOTE: `current=2` passed to ProgressSteps is a no-op (reads `active`, not `current`) — only step 1 is active.
import BaseLayout from '@shared/layouts/BaseLayout.astro';
import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro';
import ProgressSteps from '@ui/ProgressSteps.astro';
import Button from '@ui/Button.astro';
import ButtonList from '@ui/ButtonList.astro';
import FormGroup from '@ui/FormGroup.astro';
import BaseLayout from '@shared/layouts/BaseLayout.astro'
import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro'
import ProgressSteps from '@ui/ProgressSteps.astro'
import Button from '@ui/Button.astro'
import ButtonList from '@ui/ButtonList.astro'
import FormGroup from '@ui/FormGroup.astro'
---
<BaseLayout title="Onboarding">
<nav class="navbar">
<div class="container">
<div class="row w-100 align-items-center">
<div class="col me-auto">
<NavbarLogo class="logo-gray" />
</div>
<div class="col-2">
<ProgressSteps count={5} />
</div>
<div class="col text-end">
<a href="." class="btn btn-ghost">Skip<span class="d-none d-md-inline">&nbsp;to dashboard</span></a>
</div>
</div>
</div>
</nav>
<nav class="navbar">
<div class="container">
<div class="row w-100 align-items-center">
<div class="col me-auto">
<NavbarLogo class="logo-gray" />
</div>
<div class="col-2">
<ProgressSteps count={5} />
</div>
<div class="col text-end">
<a href="." class="btn btn-ghost">Skip<span class="d-none d-md-inline">&nbsp;to dashboard</span></a>
</div>
</div>
</div>
</nav>
<main class="py-5">
<div class="container container-tight">
<div class="page-header">
<h1 class="page-title"> Let's set up your account </h1>
</div>
<div class="card mt-5">
<div class="card-body space-y-4">
<FormGroup label="Full name" class="">
<input type="text" class="form-control" placeholder="Enter your full name" />
</FormGroup>
<FormGroup label="Company name" class="">
<input type="text" class="form-control" placeholder="Enter your company name" />
</FormGroup>
<FormGroup label="Role" class="">
<select class="form-select">
<option value="">Select your role</option>
<option value="developer">Developer</option>
<option value="designer">Designer</option>
<option value="manager">Manager</option>
<option value="founder">Founder</option>
<option value="other">Other</option>
</select>
</FormGroup>
<FormGroup label="Team size" class="">
<div class="form-selectgroup">
<label class="form-selectgroup-item">
<input type="radio" name="team-size" value="1" class="form-selectgroup-input" checked />
<span class="form-selectgroup-label">Just me</span>
</label>
<label class="form-selectgroup-item">
<input type="radio" name="team-size" value="2-10" class="form-selectgroup-input" />
<span class="form-selectgroup-label">2-10 people</span>
</label>
<label class="form-selectgroup-item">
<input type="radio" name="team-size" value="11-50" class="form-selectgroup-input" />
<span class="form-selectgroup-label">11-50 people</span>
</label>
<label class="form-selectgroup-item">
<input type="radio" name="team-size" value="50+" class="form-selectgroup-input" />
<span class="form-selectgroup-label">50+ people</span>
</label>
</div>
</FormGroup>
<FormGroup label="What are you planning to use this for?" class="">
<div class="form-check">
<input class="form-check-input" type="radio" value="personal" id="use-personal" checked />
<label class="form-check-label" for="use-personal"> Personal projects </label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" value="business" id="use-business" />
<label class="form-check-label" for="use-business"> Business applications </label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" value="client" id="use-client" />
<label class="form-check-label" for="use-client"> Client work </label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" value="learning" id="use-learning" />
<label class="form-check-label" for="use-learning"> Learning and experimentation </label>
</div>
</FormGroup>
<FormGroup label="How did you hear about us?" class="">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="referral" value="search" id="ref-search" />
<label class="form-check-label" for="ref-search"> Search engine </label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="referral" value="social" id="ref-social" />
<label class="form-check-label" for="ref-social"> Social media </label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="referral" value="friend" id="ref-friend" />
<label class="form-check-label" for="ref-friend"> Friend or colleague </label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="referral" value="blog" id="ref-blog" />
<label class="form-check-label" for="ref-blog"> Blog or article </label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="referral" value="other" id="ref-other" />
<label class="form-check-label" for="ref-other"> Other </label>
</div>
</FormGroup>
<div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="notifications" checked />
<label class="form-check-label" for="notifications"> Send me product updates and tips via email </label>
</div>
</div>
</div>
</div>
<main class="py-5">
<div class="container container-tight">
<div class="page-header">
<h1 class="page-title">Let's set up your account</h1>
</div>
<div class="card mt-5">
<div class="card-body space-y-4">
<FormGroup label="Full name" class="">
<input type="text" class="form-control" placeholder="Enter your full name" />
</FormGroup>
<FormGroup label="Company name" class="">
<input type="text" class="form-control" placeholder="Enter your company name" />
</FormGroup>
<FormGroup label="Role" class="">
<select class="form-select">
<option value="">Select your role</option>
<option value="developer">Developer</option>
<option value="designer">Designer</option>
<option value="manager">Manager</option>
<option value="founder">Founder</option>
<option value="other">Other</option>
</select>
</FormGroup>
<FormGroup label="Team size" class="">
<div class="form-selectgroup">
<label class="form-selectgroup-item">
<input type="radio" name="team-size" value="1" class="form-selectgroup-input" checked />
<span class="form-selectgroup-label">Just me</span>
</label>
<label class="form-selectgroup-item">
<input type="radio" name="team-size" value="2-10" class="form-selectgroup-input" />
<span class="form-selectgroup-label">2-10 people</span>
</label>
<label class="form-selectgroup-item">
<input type="radio" name="team-size" value="11-50" class="form-selectgroup-input" />
<span class="form-selectgroup-label">11-50 people</span>
</label>
<label class="form-selectgroup-item">
<input type="radio" name="team-size" value="50+" class="form-selectgroup-input" />
<span class="form-selectgroup-label">50+ people</span>
</label>
</div>
</FormGroup>
<FormGroup label="What are you planning to use this for?" class="">
<div class="form-check">
<input class="form-check-input" type="radio" value="personal" id="use-personal" checked />
<label class="form-check-label" for="use-personal"> Personal projects </label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" value="business" id="use-business" />
<label class="form-check-label" for="use-business"> Business applications </label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" value="client" id="use-client" />
<label class="form-check-label" for="use-client"> Client work </label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" value="learning" id="use-learning" />
<label class="form-check-label" for="use-learning"> Learning and experimentation </label>
</div>
</FormGroup>
<FormGroup label="How did you hear about us?" class="">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="referral" value="search" id="ref-search" />
<label class="form-check-label" for="ref-search"> Search engine </label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="referral" value="social" id="ref-social" />
<label class="form-check-label" for="ref-social"> Social media </label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="referral" value="friend" id="ref-friend" />
<label class="form-check-label" for="ref-friend"> Friend or colleague </label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="referral" value="blog" id="ref-blog" />
<label class="form-check-label" for="ref-blog"> Blog or article </label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="referral" value="other" id="ref-other" />
<label class="form-check-label" for="ref-other"> Other </label>
</div>
</FormGroup>
<div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="notifications" checked />
<label class="form-check-label" for="notifications"> Send me product updates and tips via email </label>
</div>
</div>
</div>
</div>
<ButtonList class="justify-content-between mt-4">
<Button text="Back" color="link link-secondary" />
<Button text="Continue" color="primary" />
</ButtonList>
</div>
</main>
<ButtonList class="justify-content-between mt-4">
<Button text="Back" color="link link-secondary" />
<Button text="Continue" color="primary" />
</ButtonList>
</div>
</main>
</BaseLayout>
+76 -119
View File
@@ -1,133 +1,90 @@
---
// tabler-payments is not in core/libs.json — only imask emits a script tag.
import PayLayout from '@shared/layouts/PayLayout.astro';
import Avatar from '@ui/Avatar.astro';
import Payment from '@ui/Payment.astro';
import FormGroup from '@ui/FormGroup.astro';
import Icon from '@ui/Icon.astro';
import PayLayout from '@shared/layouts/PayLayout.astro'
import Avatar from '@ui/Avatar.astro'
import Payment from '@ui/Payment.astro'
import FormGroup from '@ui/FormGroup.astro'
import Icon from '@ui/Icon.astro'
---
<PayLayout title="Pay" pageLibs={['tabler-payments', 'imask']}>
<div class="bg-dark bg-cover" style="background-image: url(./static/bg-cover.jpg)">
<div class="bg-dark bg-opacity-75 pt-5 bg-blur">
<div class="container container-tight bg-overlay">
<img src="./static/bg-cover.jpg" alt="" class="img-fluid rounded-lg rounded-top" />
</div>
</div>
</div>
<div class="bg-dark bg-cover" style="background-image: url(./static/bg-cover.jpg)">
<div class="bg-dark bg-opacity-75 pt-5 bg-blur">
<div class="container container-tight bg-overlay">
<img src="./static/bg-cover.jpg" alt="" class="img-fluid rounded-lg rounded-top" />
</div>
</div>
</div>
<div class="container container-tight">
<div class="card rounded-top-0 border-top-0">
<div class="card-body pt-0">
<div class="mb-3 text-center">
<Avatar personId={1} size="xl" class="avatar-cover rounded-circle mb-3" />
<div class="mb-4">
<h2 class="h2">Front-End Learning</h2>
<div class="text-secondary">
Learn to build web apps with HTML & CSS. Get started quickly with included templates.
</div>
</div>
</div>
<div class="container container-tight">
<div class="card rounded-top-0 border-top-0">
<div class="card-body pt-0">
<div class="mb-3 text-center">
<Avatar personId={1} size="xl" class="avatar-cover rounded-circle mb-3" />
<div class="mb-4">
<h2 class="h2">Front-End Learning</h2>
<div class="text-secondary">Learn to build web apps with HTML & CSS. Get started quickly with included templates.</div>
</div>
</div>
<nav class="nav nav-segmented w-100 mb-4" role="tablist">
<button data-bs-target="#tab-card" class="nav-link active" data-bs-toggle="tab" role="tab" aria-controls="tab-card" aria-selected="true">
<Icon name="credit-card" />
<span>Pay With Card</span>
</button>
<button data-bs-target="#tab-paypal" class="nav-link" data-bs-toggle="tab" role="tab" aria-controls="tab-paypal" aria-selected="false">
<Icon name="brand-paypal" />
<span>Pay With PayPal</span>
</button>
</nav>
<nav class="nav nav-segmented w-100 mb-4" role="tablist">
<button
data-bs-target="#tab-card"
class="nav-link active"
data-bs-toggle="tab"
role="tab"
aria-controls="tab-card"
aria-selected="true"
>
<Icon name="credit-card" />
<span>Pay With Card</span>
</button>
<button
data-bs-target="#tab-paypal"
class="nav-link"
data-bs-toggle="tab"
role="tab"
aria-controls="tab-paypal"
aria-selected="false"
>
<Icon name="brand-paypal" />
<span>Pay With PayPal</span>
</button>
</nav>
<div class="tab-content">
<div class="tab-pane active show" id="tab-card" role="tabpanel">
<form>
<div class="space-y">
<FormGroup label="Card Number" for="card-number" class="">
<div class="input-group input-group-flat">
<span class="input-group-text">
<Payment payment="visa" size="xs" />
</span>
<input type="text" class="form-control" placeholder="0000 0000 0000 0000" autocomplete="off" id="card-number-addon" />
</div>
</FormGroup>
<div class="tab-content">
<div class="tab-pane active show" id="tab-card" role="tabpanel">
<form>
<div class="space-y">
<FormGroup label="Card Number" for="card-number" class="">
<div class="input-group input-group-flat">
<span class="input-group-text">
<Payment payment="visa" size="xs" />
</span>
<input
type="text"
class="form-control"
placeholder="0000 0000 0000 0000"
autocomplete="off"
id="card-number-addon"
/>
</div>
</FormGroup>
<div class="row g-3">
<FormGroup label="Expiry Date" for="card-expiry" class="col-sm-6">
<input type="text" class="form-control" id="card-expiry" placeholder="MM/YY" inputmode="numeric" aria-required="true" maxlength="5" />
</FormGroup>
<FormGroup label="CVC" for="card-cvc" class="col-sm-6">
<input type="text" class="form-control" id="card-cvc" placeholder="CVC" inputmode="numeric" aria-required="true" maxlength="3" />
</FormGroup>
</div>
<div class="row g-3">
<FormGroup label="Expiry Date" for="card-expiry" class="col-sm-6">
<input
type="text"
class="form-control"
id="card-expiry"
placeholder="MM/YY"
inputmode="numeric"
aria-required="true"
maxlength="5"
/>
</FormGroup>
<FormGroup label="CVC" for="card-cvc" class="col-sm-6">
<input
type="text"
class="form-control"
id="card-cvc"
placeholder="CVC"
inputmode="numeric"
aria-required="true"
maxlength="3"
/>
</FormGroup>
</div>
<FormGroup label="Name on Card" for="card-name" class="">
<input type="text" class="form-control" id="card-name" placeholder="Full name" aria-required="true" />
</FormGroup>
<FormGroup label="Name on Card" for="card-name" class="">
<input type="text" class="form-control" id="card-name" placeholder="Full name" aria-required="true" />
</FormGroup>
<FormGroup label="Email" for="card-email" class="">
<input type="email" class="form-control" id="card-email" placeholder="you@example.com" aria-required="true" />
</FormGroup>
<FormGroup label="Email" for="card-email" class="">
<input type="email" class="form-control" id="card-email" placeholder="you@example.com" aria-required="true" />
</FormGroup>
<div>
<button type="button" class="btn btn-primary w-100"> Pay $253 </button>
<div class="text-secondary text-center small mt-3">You'll be charged $253, including $48 for VAT in Poland</div>
</div>
</div>
</form>
</div>
<div>
<button type="button" class="btn btn-primary w-100"> Pay $253 </button>
<div class="text-secondary text-center small mt-3">
You'll be charged $253, including $48 for VAT in Poland
</div>
</div>
</div>
</form>
</div>
<div class="tab-pane" id="tab-paypal" role="tabpanel">
<button type="button" class="btn btn-primary w-100">
<Icon name="brand-paypal" />
<span class="ms-2">Pay with PayPal - $253</span>
</button>
<div class="text-secondary text-center small mt-3">
You'll be charged $253, including $48 for VAT in Poland
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="tab-paypal" role="tabpanel">
<button type="button" class="btn btn-primary w-100">
<Icon name="brand-paypal" />
<span class="ms-2">Pay with PayPal - $253</span>
</button>
<div class="text-secondary text-center small mt-3">You'll be charged $253, including $48 for VAT in Poland</div>
</div>
</div>
</div>
</div>
</div>
</PayLayout>
+309 -309
View File
@@ -1,324 +1,324 @@
---
import ButtonGroup from '@ui/ButtonGroup.astro';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import Icon from '@ui/Icon.astro';
import Button from '@ui/Button.astro';
import Badge from '@ui/Badge.astro';
import Avatar from '@ui/Avatar.astro';
import AvatarList from '@ui/AvatarList.astro';
import Pagination from '@ui/Pagination.astro';
import Card from '@ui/Card.astro';
import CardBody from '@ui/CardBody.astro';
import ButtonGroup from '@ui/ButtonGroup.astro'
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import Icon from '@ui/Icon.astro'
import Button from '@ui/Button.astro'
import Badge from '@ui/Badge.astro'
import Avatar from '@ui/Avatar.astro'
import AvatarList from '@ui/AvatarList.astro'
import Pagination from '@ui/Pagination.astro'
import Card from '@ui/Card.astro'
import CardBody from '@ui/CardBody.astro'
---
<DefaultLayout title="Categories playground">
<!-- Header -->
<div class="row align-items-center gy-3 mb-5">
<div class="col">
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-1">
<li class="breadcrumb-item">
<a class="text-secondary" href="#">
<Icon name="arrow-left" class="icon-inline me-1" />
Back
</a>
</li>
</ol>
</nav>
<h1 class="fs-3 mb-0">Categories</h1>
</div>
<div class="col-12 col-lg-auto order-last order-lg-0">
<div class="row align-items-center gx-3">
<div class="col col-lg-auto">
<div class="input-group input-group-flat rounded-pill">
<input type="text" class="form-control" placeholder="Search" aria-label="Search" aria-describedby="playground-categories-search">
<span class="input-group-text" id="playground-categories-search">
<Icon name="search" />
</span>
</div>
</div>
<div class="col-auto">
<div class="dropdown">
<Button element="button" type="button" icon="filter" iconOnly text="Open filters" />
<div class="dropdown-menu rounded-3 p-6">
<h4 class="fs-lg mb-4">Filter</h4>
<form id="playgroundCategoriesFilterForm" style="width: 350px">
<div class="row align-items-center mb-3">
<div class="col-3">
<label class="form-label mb-0" for="playgroundFilterUser">User</label>
</div>
<div class="col-9">
<select class="form-select" id="playgroundFilterUser" aria-label="User">
<option value="Emily Thompson" selected>Emily Thompson</option>
<option value="Michael Johnson">Michael Johnson</option>
<option value="Robert Garcia">Robert Garcia</option>
<option value="Jessica Miller">Jessica Miller</option>
</select>
</div>
</div>
<div class="row align-items-center mb-3">
<div class="col-3">
<label class="form-label mb-0" for="playgroundFilterCompany">Company</label>
</div>
<div class="col-9">
<select class="form-select" id="playgroundFilterCompany" aria-label="Company">
<option value="TechPinnacle Solutions" selected>TechPinnacle Solutions</option>
<option value="Quantum Dynamics">Quantum Dynamics</option>
<option value="Pinnacle Technologies">Pinnacle Technologies</option>
<option value="Apex Innovations">Apex Innovations</option>
</select>
</div>
</div>
<div class="row align-items-center">
<div class="col-3">
<label class="form-label mb-0" for="playgroundFilterLocation">Location</label>
</div>
<div class="col-9">
<select class="form-select" id="playgroundFilterLocation" aria-label="Location">
<option value="San Francisco, CA" selected>San Francisco, CA</option>
<option value="Austin, TX">Austin, TX</option>
<option value="Miami, FL">Miami, FL</option>
<option value="Seattle, WA">Seattle, WA</option>
</select>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="col-auto ms-n2">
<div class="dropdown">
<Button element="button" type="button" icon="sort-ascending-letters" iconOnly text="Open sort options" />
<div class="dropdown-menu rounded-3 p-6">
<h4 class="fs-lg mb-4">Sort</h4>
<form id="playgroundCategoriesSortForm" style="width: 350px">
<div class="row gx-3">
<div class="col">
<select class="form-select" id="playgroundSortField" aria-label="Sort by">
<option value="user" selected>User</option>
<option value="company">Company</option>
<option value="phone">Phone</option>
<option value="location">Location</option>
</select>
</div>
<div class="col-auto">
<ButtonGroup aria-label="Sort direction">
<input type="radio" class="btn-check" name="playgroundSortDir" id="playgroundSortAsc" autocomplete="off" checked>
<label class="btn btn-light px-0 btn-icon" aria-label="Ascending"><Icon name="arrow-up" /></label>
<input type="radio" class="btn-check" name="playgroundSortDir" id="playgroundSortDesc" autocomplete="off">
<label class="btn btn-light px-0 btn-icon" aria-label="Descending"><Icon name="arrow-down" /></label>
</ButtonGroup>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="col-auto">
<button class="btn btn-primary" type="button">New category</button>
</div>
</div>
<!-- Header -->
<div class="row align-items-center gy-3 mb-5">
<div class="col">
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-1">
<li class="breadcrumb-item">
<a class="text-secondary" href="#">
<Icon name="arrow-left" class="icon-inline me-1" />
Back
</a>
</li>
</ol>
</nav>
<h1 class="fs-3 mb-0">Categories</h1>
</div>
<div class="col-12 col-lg-auto order-last order-lg-0">
<div class="row align-items-center gx-3">
<div class="col col-lg-auto">
<div class="input-group input-group-flat rounded-pill">
<input type="text" class="form-control" placeholder="Search" aria-label="Search" aria-describedby="playground-categories-search" />
<span class="input-group-text" id="playground-categories-search">
<Icon name="search" />
</span>
</div>
</div>
<div class="col-auto">
<div class="dropdown">
<Button element="button" type="button" icon="filter" iconOnly text="Open filters" />
<div class="dropdown-menu rounded-3 p-6">
<h4 class="fs-lg mb-4">Filter</h4>
<form id="playgroundCategoriesFilterForm" style="width: 350px">
<div class="row align-items-center mb-3">
<div class="col-3">
<label class="form-label mb-0" for="playgroundFilterUser">User</label>
</div>
<div class="col-9">
<select class="form-select" id="playgroundFilterUser" aria-label="User">
<option value="Emily Thompson" selected>Emily Thompson</option>
<option value="Michael Johnson">Michael Johnson</option>
<option value="Robert Garcia">Robert Garcia</option>
<option value="Jessica Miller">Jessica Miller</option>
</select>
</div>
</div>
<div class="row align-items-center mb-3">
<div class="col-3">
<label class="form-label mb-0" for="playgroundFilterCompany">Company</label>
</div>
<div class="col-9">
<select class="form-select" id="playgroundFilterCompany" aria-label="Company">
<option value="TechPinnacle Solutions" selected>TechPinnacle Solutions</option>
<option value="Quantum Dynamics">Quantum Dynamics</option>
<option value="Pinnacle Technologies">Pinnacle Technologies</option>
<option value="Apex Innovations">Apex Innovations</option>
</select>
</div>
</div>
<div class="row align-items-center">
<div class="col-3">
<label class="form-label mb-0" for="playgroundFilterLocation">Location</label>
</div>
<div class="col-9">
<select class="form-select" id="playgroundFilterLocation" aria-label="Location">
<option value="San Francisco, CA" selected>San Francisco, CA</option>
<option value="Austin, TX">Austin, TX</option>
<option value="Miami, FL">Miami, FL</option>
<option value="Seattle, WA">Seattle, WA</option>
</select>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="col-auto ms-n2">
<div class="dropdown">
<Button element="button" type="button" icon="sort-ascending-letters" iconOnly text="Open sort options" />
<div class="dropdown-menu rounded-3 p-6">
<h4 class="fs-lg mb-4">Sort</h4>
<form id="playgroundCategoriesSortForm" style="width: 350px">
<div class="row gx-3">
<div class="col">
<select class="form-select" id="playgroundSortField" aria-label="Sort by">
<option value="user" selected>User</option>
<option value="company">Company</option>
<option value="phone">Phone</option>
<option value="location">Location</option>
</select>
</div>
<div class="col-auto">
<ButtonGroup aria-label="Sort direction">
<input type="radio" class="btn-check" name="playgroundSortDir" id="playgroundSortAsc" autocomplete="off" checked />
<label class="btn btn-light px-0 btn-icon" aria-label="Ascending"><Icon name="arrow-up" /></label>
<input type="radio" class="btn-check" name="playgroundSortDir" id="playgroundSortDesc" autocomplete="off" />
<label class="btn btn-light px-0 btn-icon" aria-label="Descending"><Icon name="arrow-down" /></label>
</ButtonGroup>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="col-auto">
<button class="btn btn-primary" type="button">New category</button>
</div>
</div>
<hr class="my-7">
<hr class="my-7" />
<div class="row row-cards">
<div class="col-12 col-lg-6">
<Card>
<CardBody>
<div class="row align-items-center mb-4">
<div class="col">
<Badge text="Featured" color="primary" />
</div>
<div class="col-auto">
<Icon name="confetti" class="icon-lg text-primary opacity-50" />
</div>
</div>
<h3 class="fs-5 mb-1 text-truncate"><a class="text-body" href="./empty.html">Announcements</a></h3>
<p class="text-secondary text-truncate">For company updates, system changes, or important notices that everyone should see.</p>
<p class="fs-sm text-secondary mb-0">Last post 2 days ago</p>
<hr>
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
<Avatar src="static/avatars/001f.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Emily Thompson" />
<Avatar src="static/avatars/002m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Michael Johnson" />
<Avatar src="static/avatars/003m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Robert Garcia" />
</AvatarList>
<div class="text-end">
<Badge text="20 posts" class="bg-light text-body" />
</div>
</div>
</CardBody>
</Card>
</div>
<div class="row row-cards">
<div class="col-12 col-lg-6">
<Card>
<CardBody>
<div class="row align-items-center mb-4">
<div class="col">
<Badge text="Featured" color="primary" />
</div>
<div class="col-auto">
<Icon name="confetti" class="icon-lg text-primary opacity-50" />
</div>
</div>
<h3 class="fs-5 mb-1 text-truncate"><a class="text-body" href="./empty.html">Announcements</a></h3>
<p class="text-secondary text-truncate">For company updates, system changes, or important notices that everyone should see.</p>
<p class="fs-sm text-secondary mb-0">Last post 2 days ago</p>
<hr />
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
<Avatar src="static/avatars/001f.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Emily Thompson" />
<Avatar src="static/avatars/002m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Michael Johnson" />
<Avatar src="static/avatars/003m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Robert Garcia" />
</AvatarList>
<div class="text-end">
<Badge text="20 posts" class="bg-light text-body" />
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-lg-6">
<Card>
<CardBody>
<div class="row align-items-center mb-4">
<div class="col">
<Badge text="Featured" color="success" />
</div>
<div class="col-auto">
<Icon name="slideshow" class="icon-lg text-success opacity-50" />
</div>
</div>
<h3 class="fs-5 mb-1 text-truncate"><a class="text-body" href="./empty.html">Marketing campaigns</a></h3>
<p class="text-secondary text-truncate">Posts related to new campaigns, marketing strategies, and promotions.</p>
<p class="fs-sm text-secondary mb-0">Last post 1 day ago</p>
<hr>
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true} size="xs" offset={3} limit={3} />
<div class="text-end">
<Badge text="62 posts" class="bg-light text-body" />
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-lg-6">
<Card>
<CardBody>
<div class="row align-items-center mb-4">
<div class="col">
<Badge text="Featured" color="success" />
</div>
<div class="col-auto">
<Icon name="slideshow" class="icon-lg text-success opacity-50" />
</div>
</div>
<h3 class="fs-5 mb-1 text-truncate"><a class="text-body" href="./empty.html">Marketing campaigns</a></h3>
<p class="text-secondary text-truncate">Posts related to new campaigns, marketing strategies, and promotions.</p>
<p class="fs-sm text-secondary mb-0">Last post 1 day ago</p>
<hr />
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true} size="xs" offset={3} limit={3} />
<div class="text-end">
<Badge text="62 posts" class="bg-light text-body" />
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-md-6 col-lg-4">
<Card class="position-relative overflow-hidden">
<Icon name="chart-pie" class="icon-lg text-danger opacity-25 position-absolute top-0 end-0 m-3" />
<CardBody>
<small class="text-secondary d-block mb-4">12 followers</small>
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Reports &amp; analytics</a></h3>
<p class="text-secondary text-truncate">Data-driven insights and performance reports across all departments.</p>
<p class="fs-sm text-secondary mb-0">Last post 5 days ago</p>
<hr>
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
<Avatar src="static/avatars/001f.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Emily Thompson" />
<Avatar src="static/avatars/003m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Robert Garcia" />
</AvatarList>
<div class="text-end">
<Badge text="28 posts" class="bg-light text-body" />
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-md-6 col-lg-4">
<Card class="position-relative overflow-hidden">
<Icon name="chart-pie" class="icon-lg text-danger opacity-25 position-absolute top-0 end-0 m-3" />
<CardBody>
<small class="text-secondary d-block mb-4">12 followers</small>
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Reports &amp; analytics</a></h3>
<p class="text-secondary text-truncate">Data-driven insights and performance reports across all departments.</p>
<p class="fs-sm text-secondary mb-0">Last post 5 days ago</p>
<hr />
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
<Avatar src="static/avatars/001f.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Emily Thompson" />
<Avatar src="static/avatars/003m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Robert Garcia" />
</AvatarList>
<div class="text-end">
<Badge text="28 posts" class="bg-light text-body" />
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-md-6 col-lg-4">
<Card class="position-relative overflow-hidden">
<Icon name="id-badge" class="icon-lg text-warning opacity-25 position-absolute top-0 end-0 m-3" />
<CardBody>
<small class="text-secondary d-block mb-4">16 followers</small>
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Events &amp; webinars</a></h3>
<p class="text-secondary text-truncate">Upcoming events, webinar recordings, and community meetups.</p>
<p class="fs-sm text-secondary mb-0">Last post 3 days ago</p>
<hr>
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
<Avatar src="static/avatars/005f.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Olivia Davis" />
<Avatar src="static/avatars/002m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Michael Johnson" />
</AvatarList>
<div class="text-end">
<Badge text="34 posts" class="bg-light text-body" />
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-md-6 col-lg-4">
<Card class="position-relative overflow-hidden">
<Icon name="id-badge" class="icon-lg text-warning opacity-25 position-absolute top-0 end-0 m-3" />
<CardBody>
<small class="text-secondary d-block mb-4">16 followers</small>
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Events &amp; webinars</a></h3>
<p class="text-secondary text-truncate">Upcoming events, webinar recordings, and community meetups.</p>
<p class="fs-sm text-secondary mb-0">Last post 3 days ago</p>
<hr />
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
<Avatar src="static/avatars/005f.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Olivia Davis" />
<Avatar src="static/avatars/002m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Michael Johnson" />
</AvatarList>
<div class="text-end">
<Badge text="34 posts" class="bg-light text-body" />
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-md-6 col-lg-4">
<Card class="position-relative overflow-hidden">
<Icon name="user" class="icon-lg text-warning opacity-25 position-absolute top-0 end-0 m-3" />
<CardBody>
<small class="text-secondary d-block mb-4">9 followers</small>
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Client success stories</a></h3>
<p class="text-secondary text-truncate">Case studies and testimonials from our most satisfied customers.</p>
<p class="fs-sm text-secondary mb-0">Last post 1 week ago</p>
<hr>
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
<Avatar src="static/avatars/006m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="James Wilson" />
<Avatar src="static/avatars/004f.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Jessica Miller" />
</AvatarList>
<div class="text-end">
<Badge text="64 posts" class="bg-light text-body" />
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-md-6 col-lg-4">
<Card class="position-relative overflow-hidden">
<Icon name="user" class="icon-lg text-warning opacity-25 position-absolute top-0 end-0 m-3" />
<CardBody>
<small class="text-secondary d-block mb-4">9 followers</small>
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Client success stories</a></h3>
<p class="text-secondary text-truncate">Case studies and testimonials from our most satisfied customers.</p>
<p class="fs-sm text-secondary mb-0">Last post 1 week ago</p>
<hr />
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
<Avatar src="static/avatars/006m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="James Wilson" />
<Avatar src="static/avatars/004f.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Jessica Miller" />
</AvatarList>
<div class="text-end">
<Badge text="64 posts" class="bg-light text-body" />
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-md-6 col-lg-4">
<Card class="position-relative overflow-hidden">
<Icon name="message" class="icon-lg text-primary opacity-25 position-absolute top-0 end-0 m-3" />
<CardBody>
<small class="text-secondary d-block mb-4">10 followers</small>
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Team highlights</a></h3>
<p class="text-secondary text-truncate">Spotlights, shoutouts, and stories from people across the team.</p>
<p class="fs-sm text-secondary mb-0">Last post 4 days ago</p>
<hr>
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
<Avatar src="static/avatars/002m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Michael Johnson" />
<Avatar src="static/avatars/001f.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Emily Thompson" />
</AvatarList>
<div class="text-end">
<Badge text="42 posts" class="bg-light text-body" />
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-md-6 col-lg-4">
<Card class="position-relative overflow-hidden">
<Icon name="message" class="icon-lg text-primary opacity-25 position-absolute top-0 end-0 m-3" />
<CardBody>
<small class="text-secondary d-block mb-4">10 followers</small>
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Team highlights</a></h3>
<p class="text-secondary text-truncate">Spotlights, shoutouts, and stories from people across the team.</p>
<p class="fs-sm text-secondary mb-0">Last post 4 days ago</p>
<hr />
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
<Avatar src="static/avatars/002m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Michael Johnson" />
<Avatar src="static/avatars/001f.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Emily Thompson" />
</AvatarList>
<div class="text-end">
<Badge text="42 posts" class="bg-light text-body" />
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-md-6 col-lg-4">
<Card class="position-relative overflow-hidden">
<Icon name="building-bank" class="icon-lg text-danger opacity-25 position-absolute top-0 end-0 m-3" />
<CardBody>
<small class="text-secondary d-block mb-4">26 followers</small>
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Training &amp; resources</a></h3>
<p class="text-secondary text-truncate">Access to learning materials, tutorials, or resources for users and staff.</p>
<p class="fs-sm text-secondary mb-0">Last post 6 days ago</p>
<hr>
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
<Avatar src="static/avatars/003m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Robert Garcia" />
<Avatar src="static/avatars/005f.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Olivia Davis" />
</AvatarList>
<div class="text-end">
<Badge text="38 posts" class="bg-light text-body" />
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-md-6 col-lg-4">
<Card class="position-relative overflow-hidden">
<Icon name="building-bank" class="icon-lg text-danger opacity-25 position-absolute top-0 end-0 m-3" />
<CardBody>
<small class="text-secondary d-block mb-4">26 followers</small>
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Training &amp; resources</a></h3>
<p class="text-secondary text-truncate">Access to learning materials, tutorials, or resources for users and staff.</p>
<p class="fs-sm text-secondary mb-0">Last post 6 days ago</p>
<hr />
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
<Avatar src="static/avatars/003m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Robert Garcia" />
<Avatar src="static/avatars/005f.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Olivia Davis" />
</AvatarList>
<div class="text-end">
<Badge text="38 posts" class="bg-light text-body" />
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-12 col-md-6 col-lg-4">
<Card class="position-relative overflow-hidden">
<Icon name="brand-apple" class="icon-lg text-primary opacity-25 position-absolute top-0 end-0 m-3" />
<CardBody>
<small class="text-secondary d-block mb-4">6 followers</small>
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Industry news</a></h3>
<p class="text-secondary text-truncate">Trends, news, and commentary from around the industry landscape.</p>
<p class="fs-sm text-secondary mb-0">Last post 2 weeks ago</p>
<hr>
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
<Avatar src="static/avatars/006m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="James Wilson" />
<Avatar src="static/avatars/004f.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Jessica Miller" />
</AvatarList>
<div class="text-end">
<Badge text="59 posts" class="bg-light text-body" />
</div>
</div>
</CardBody>
</Card>
</div>
</div>
<div class="col-12 col-md-6 col-lg-4">
<Card class="position-relative overflow-hidden">
<Icon name="brand-apple" class="icon-lg text-primary opacity-25 position-absolute top-0 end-0 m-3" />
<CardBody>
<small class="text-secondary d-block mb-4">6 followers</small>
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Industry news</a></h3>
<p class="text-secondary text-truncate">Trends, news, and commentary from around the industry landscape.</p>
<p class="fs-sm text-secondary mb-0">Last post 2 weeks ago</p>
<hr />
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
<Avatar src="static/avatars/006m.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="James Wilson" />
<Avatar src="static/avatars/004f.jpg" size="xs" data-bs-toggle="tooltip" data-bs-title="Jessica Miller" />
</AvatarList>
<div class="text-end">
<Badge text="59 posts" class="bg-light text-body" />
</div>
</div>
</CardBody>
</Card>
</div>
</div>
<div class="row align-items-center mt-7">
<div class="col">
<p class="text-secondary mb-0">1 8 (8 total)</p>
</div>
<div class="col-auto">
<nav aria-label="Page navigation">
<Pagination count={3} offset={3} activeItem={1} class="mb-0" />
</nav>
</div>
</div>
<div class="row align-items-center mt-7">
<div class="col">
<p class="text-secondary mb-0">1 8 (8 total)</p>
</div>
<div class="col-auto">
<nav aria-label="Page navigation">
<Pagination count={3} offset={3} activeItem={1} class="mb-0" />
</nav>
</div>
</div>
</DefaultLayout>
+15 -21
View File
@@ -1,25 +1,19 @@
---
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 DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import Card from '@ui/Card.astro'
import CardBody from '@ui/CardBody.astro'
import CardTitle from '@ui/CardTitle.astro'
---
<DefaultLayout
title="Layout playground"
pageHeader="Layout playground"
pretitle="Playground"
sidebar
sidebarDark
>
<div class="row row-cards">
<div class="col-12">
<Card>
<CardBody>
<CardTitle as="h2">Sandbox</CardTitle>
<p class="text-secondary mb-0">Edit this page to try layout options, components, and styles.</p>
</CardBody>
</Card>
</div>
</div>
<DefaultLayout title="Layout playground" pageHeader="Layout playground" pretitle="Playground" sidebar sidebarDark>
<div class="row row-cards">
<div class="col-12">
<Card>
<CardBody>
<CardTitle as="h2">Sandbox</CardTitle>
<p class="text-secondary mb-0">Edit this page to try layout options, components, and styles.</p>
</CardBody>
</Card>
</div>
</div>
</DefaultLayout>
+219 -232
View File
@@ -1,250 +1,237 @@
---
import Subheader from '@ui/Subheader.astro';
import DefaultLayout from '@shared/layouts/DefaultLayout.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 ProgressSteps from '@ui/ProgressSteps.astro';
import Datepicker from '@ui/Datepicker.astro';
import FormGroup from '@ui/FormGroup.astro';
import Trending from '@ui/Trending.astro';
import Subheader from '@ui/Subheader.astro'
import DefaultLayout from '@shared/layouts/DefaultLayout.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 ProgressSteps from '@ui/ProgressSteps.astro'
import Datepicker from '@ui/Datepicker.astro'
import FormGroup from '@ui/FormGroup.astro'
import Trending from '@ui/Trending.astro'
---
<DefaultLayout title="Playground" pageHeader="Project Settings" pageLibs={['vanilla-calendar-pro']}>
<div class="row row-cards row-deck">
<div class="row row-cards row-deck">
<div class="col-12">
<Card>
<CardBody>
<div class="d-flex flex-wrap gap-4">
<div class="bg-pattern-diagonal w-10 h-10 border rounded"></div>
<div class="bg-pattern-diagonal-2 w-10 h-10 border rounded"></div>
<div class="bg-pattern-dots w-10 h-10 border rounded"></div>
<div class="bg-pattern-lines w-10 h-10 border rounded"></div>
<div class="bg-pattern-rectangles w-10 h-10 border rounded"></div>
<div class="bg-pattern-lines-vertical w-10 h-10 border rounded"></div>
<div class="bg-pattern-grid w-10 h-10 border rounded"></div>
<div class="bg-pattern-grid-diagonal w-10 h-10 border rounded"></div>
<div class="bg-pattern-blueprint w-10 h-10 border rounded"></div>
<div class="bg-pattern-cross-dots w-10 h-10 border rounded"></div>
<div class="bg-pattern-diagonal-stripes w-10 h-10 border rounded"></div>
<div class="bg-pattern-diagonal-stripes-2 w-10 h-10 border rounded"></div>
<div class="bg-pattern-zigzag w-10 h-10 border rounded"></div>
<div class="bg-pattern-vertical-stripes w-10 h-10 border rounded"></div>
<div class="bg-pattern-horizontal-stripes w-10 h-10 border rounded"></div>
</div>
</CardBody>
</Card>
</div>
<div class="col-6">
<Card>
<CardBody>
<ButtonList>
<a href="" class="btn" style="border-color: var(--tblr-border-color);">Button</a>
<a href="" class="btn" style="border-color: var(--tblr-border-color-translucent);">Button</a>
<a href="" class="btn" style="border-color: var(--tblr-border-dark-color);">Button</a>
<a href="" class="btn" style="border-color: var(--tblr-border-dark-color-translucent);">Button</a>
<a href="" class="btn btn-primary">Button</a>
</ButtonList>
<ButtonList class="mt-4">
<a href="" class="btn">Button</a>
<a href="" class="btn">Button</a>
<a href="" class="btn">Button</a>
<a href="" class="btn">Button</a>
<a href="" class="btn btn-primary">Button</a>
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-3">
<Card>
<CardBody> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
<div class="d-flex flex-wrap gap-4">
<div class="bg-pattern-diagonal w-10 h-10 border rounded"></div>
<div class="bg-pattern-diagonal-2 w-10 h-10 border rounded"></div>
<div class="bg-pattern-dots w-10 h-10 border rounded"></div>
<div class="bg-pattern-lines w-10 h-10 border rounded"></div>
<div class="bg-pattern-rectangles w-10 h-10 border rounded"></div>
<div class="bg-pattern-lines-vertical w-10 h-10 border rounded"></div>
<div class="bg-pattern-grid w-10 h-10 border rounded"></div>
<div class="bg-pattern-grid-diagonal w-10 h-10 border rounded"></div>
<div class="bg-pattern-blueprint w-10 h-10 border rounded"></div>
<div class="bg-pattern-cross-dots w-10 h-10 border rounded"></div>
<div class="bg-pattern-diagonal-stripes w-10 h-10 border rounded"></div>
<div class="bg-pattern-diagonal-stripes-2 w-10 h-10 border rounded"></div>
<div class="bg-pattern-zigzag w-10 h-10 border rounded"></div>
<div class="bg-pattern-vertical-stripes w-10 h-10 border rounded"></div>
<div class="bg-pattern-horizontal-stripes w-10 h-10 border rounded"></div>
<div class="col-6">
<Card>
<CardBody> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </CardBody>
</Card>
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-6">
<Card>
<CardBody>
<ButtonList>
<a href="" class="btn" style="border-color: var(--tblr-border-color);">Button</a>
<a href="" class="btn" style="border-color: var(--tblr-border-color-translucent);">Button</a>
<a href="" class="btn" style="border-color: var(--tblr-border-dark-color);">Button</a>
<a href="" class="btn" style="border-color: var(--tblr-border-dark-color-translucent);">Button</a>
<a href="" class="btn btn-primary">Button</a>
</ButtonList>
<ButtonList class="mt-4">
<a href="" class="btn">Button</a>
<a href="" class="btn">Button</a>
<a href="" class="btn">Button</a>
<a href="" class="btn">Button</a>
<a href="" class="btn btn-primary">Button</a>
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody> Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequuntur earum perferendis expedita suscipit quasi maiores quas sint harum dolor! Dolorem cumque autem error ea fuga. Ea excepturi temporibus officiis perspiciatis. </CardBody>
</Card>
</div>
<div class="col-3">
<Card>
<CardBody>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua.
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardBody>
<div class="space-y">
<ProgressSteps labels={['Step A', 'Step B', 'Step C']} active={2} />
<ProgressSteps count={9} active={4} />
<ProgressSteps count={9} active={9} color="success" />
</div>
</CardBody>
</Card>
</div>
<div class="col-6">
<Card>
<CardBody>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua.
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardHeader title="Datepicker Examples" />
<CardBody>
<div class="row g-4">
<div class="col-md-6">
<h4 class="mb-3">Basic Datepicker</h4>
<FormGroup label="Default datepicker">
<Datepicker id="playground-default" value="2024-01-15" />
</FormGroup>
<FormGroup label="With icon">
<Datepicker id="playground-icon" layout="icon" value="2024-01-15" />
</FormGroup>
<FormGroup label="Icon prepend">
<Datepicker id="playground-icon-prepend" layout="icon-prepend" value="2024-01-15" />
</FormGroup>
</div>
<div class="col-md-6">
<h4 class="mb-3">Datepicker Options</h4>
<FormGroup label="With date range (min/max)">
<Datepicker id="playground-range" value="2024-06-15" />
</FormGroup>
<FormGroup label="Multiple selection">
<Datepicker id="playground-multiple" value="2024-01-15" />
</FormGroup>
<FormGroup label="Date range selection">
<Datepicker id="playground-range-selection" value="2024-01-15" />
</FormGroup>
</div>
<div class="col-md-6">
<h4 class="mb-3">Placement Options</h4>
<FormGroup label="Left placement">
<Datepicker id="playground-placement-left" value="2024-01-15" />
</FormGroup>
<FormGroup label="Center placement">
<Datepicker id="playground-placement-center" value="2024-01-15" />
</FormGroup>
<FormGroup label="Right placement">
<Datepicker id="playground-placement-right" value="2024-01-15" />
</FormGroup>
</div>
<div class="col-md-6">
<h4 class="mb-3">Advanced Options</h4>
<FormGroup label="Multiple months">
<Datepicker id="playground-multiple-months" value="2024-01-15" />
</FormGroup>
<FormGroup label="Custom first weekday (Monday)">
<Datepicker id="playground-weekday" value="2024-01-15" />
</FormGroup>
<FormGroup label="With pre-selected dates">
<Datepicker id="playground-preselected" />
</FormGroup>
</div>
<div class="col-12">
<h4 class="mb-3">Inline Datepicker</h4>
<FormGroup label="Inline calendar">
<Datepicker id="playground-inline" inline value="2024-01-15" />
</FormGroup>
<FormGroup label="Inline with multiple selection">
<Datepicker id="playground-inline-multiple" inline value="2024-01-15" />
</FormGroup>
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-8">
<Card>
<CardBody>
<p class="card-title mb-4">Cohort Statistics</p>
<dl class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
<div class="col">
<Subheader as="dt">Total Users</Subheader>
<dd class="mt-1 d-flex align-items-baseline">
<span class="h2 mb-0">25,784</span>
<Trending value={23} class="ms-2" />
</dd>
</div>
<div class="col-12">
<Card>
<CardBody>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequuntur earum perferendis expedita suscipit quasi maiores quas sint harum dolor! Dolorem cumque autem error ea fuga. Ea excepturi temporibus officiis perspiciatis.
</CardBody>
</Card>
</div>
<div class="col">
<Subheader as="dt">Average CSAT Score</Subheader>
<dd class="mt-1 d-flex align-items-baseline">
<span class="h2 mb-0">86.9</span>
<Trending value={-4} class="ms-2" />
</dd>
</div>
<div class="col-12">
<Card>
<CardBody>
<div class="space-y">
<ProgressSteps labels={['Step A', 'Step B', 'Step C']} active={2} />
<ProgressSteps count={9} active={4} />
<ProgressSteps count={9} active={9} color="success" />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Subheader as="dt">Average Response Time</Subheader>
<dd class="mt-1 d-flex align-items-baseline">
<span class="h2 mb-0">7.2m</span>
<Trending value={8} class="ms-2" />
</dd>
</div>
<div class="col-12">
<Card>
<CardHeader title="Datepicker Examples" />
<CardBody>
<div class="row g-4">
<div class="col-md-6">
<h4 class="mb-3">Basic Datepicker</h4>
<FormGroup label="Default datepicker">
<Datepicker id="playground-default" value="2024-01-15" />
</FormGroup>
<FormGroup label="With icon">
<Datepicker id="playground-icon" layout="icon" value="2024-01-15" />
</FormGroup>
<FormGroup label="Icon prepend">
<Datepicker id="playground-icon-prepend" layout="icon-prepend" value="2024-01-15" />
</FormGroup>
</div>
<div class="col-md-6">
<h4 class="mb-3">Datepicker Options</h4>
<FormGroup label="With date range (min/max)">
<Datepicker id="playground-range" value="2024-06-15" />
</FormGroup>
<FormGroup label="Multiple selection">
<Datepicker id="playground-multiple" value="2024-01-15" />
</FormGroup>
<FormGroup label="Date range selection">
<Datepicker id="playground-range-selection" value="2024-01-15" />
</FormGroup>
</div>
<div class="col-md-6">
<h4 class="mb-3">Placement Options</h4>
<FormGroup label="Left placement">
<Datepicker id="playground-placement-left" value="2024-01-15" />
</FormGroup>
<FormGroup label="Center placement">
<Datepicker id="playground-placement-center" value="2024-01-15" />
</FormGroup>
<FormGroup label="Right placement">
<Datepicker id="playground-placement-right" value="2024-01-15" />
</FormGroup>
</div>
<div class="col-md-6">
<h4 class="mb-3">Advanced Options</h4>
<FormGroup label="Multiple months">
<Datepicker id="playground-multiple-months" value="2024-01-15" />
</FormGroup>
<FormGroup label="Custom first weekday (Monday)">
<Datepicker id="playground-weekday" value="2024-01-15" />
</FormGroup>
<FormGroup label="With pre-selected dates">
<Datepicker id="playground-preselected" />
</FormGroup>
</div>
<div class="col-12">
<h4 class="mb-3">Inline Datepicker</h4>
<FormGroup label="Inline calendar">
<Datepicker id="playground-inline" inline value="2024-01-15" />
</FormGroup>
<FormGroup label="Inline with multiple selection">
<Datepicker id="playground-inline-multiple" inline value="2024-01-15" />
</FormGroup>
</div>
</div>
</CardBody>
</Card>
</div>
<div class="col-8">
<Card>
<CardBody>
<p class="card-title mb-4">Cohort Statistics</p>
<dl class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
<div class="col">
<Subheader as="dt">Total Tickets</Subheader>
<dd class="mt-1 d-flex align-items-baseline">
<span class="h2 mb-0">51,960</span>
<Trending value={-7} class="ms-2" />
</dd>
</div>
<div class="col">
<Subheader as="dt">Total Users</Subheader>
<dd class="mt-1 d-flex align-items-baseline">
<span class="h2 mb-0">25,784</span>
<Trending value={23} class="ms-2" />
</dd>
</div>
<div class="col">
<Subheader as="dt">Resolution Rate</Subheader>
<dd class="mt-1 d-flex align-items-baseline">
<span class="h2 mb-0">90.0%</span>
<Trending value={0} class="ms-2" />
</dd>
</div>
<div class="col">
<Subheader as="dt">Average CSAT Score</Subheader>
<dd class="mt-1 d-flex align-items-baseline">
<span class="h2 mb-0">86.9</span>
<Trending value={-4} class="ms-2" />
</dd>
</div>
<div class="col">
<Subheader as="dt">Total Cohorts</Subheader>
<dd class="mt-1 d-flex align-items-baseline">
<span class="h2 mb-0">10</span>
<Trending value={15} class="ms-2" />
</dd>
</div>
<div class="col">
<Subheader as="dt">Average Response Time</Subheader>
<dd class="mt-1 d-flex align-items-baseline">
<span class="h2 mb-0">7.2m</span>
<Trending value={8} class="ms-2" />
</dd>
</div>
<div class="col">
<Subheader as="dt">Avg. Handling Time</Subheader>
<dd class="mt-1 d-flex align-items-baseline">
<span class="h2 mb-0">14.1m</span>
<Trending value={-12} class="ms-2" />
</dd>
</div>
<div class="col">
<Subheader as="dt">Total Tickets</Subheader>
<dd class="mt-1 d-flex align-items-baseline">
<span class="h2 mb-0">51,960</span>
<Trending value={-7} class="ms-2" />
</dd>
</div>
<div class="col">
<Subheader as="dt">First Contact Resolution</Subheader>
<dd class="mt-1 d-flex align-items-baseline">
<span class="h2 mb-0">80.0%</span>
<Trending value={6} class="ms-2" />
</dd>
</div>
<div class="col">
<Subheader as="dt">Resolution Rate</Subheader>
<dd class="mt-1 d-flex align-items-baseline">
<span class="h2 mb-0">90.0%</span>
<Trending value={0} class="ms-2" />
</dd>
</div>
<div class="col">
<Subheader as="dt">Total Cohorts</Subheader>
<dd class="mt-1 d-flex align-items-baseline">
<span class="h2 mb-0">10</span>
<Trending value={15} class="ms-2" />
</dd>
</div>
<div class="col">
<Subheader as="dt">Avg. Handling Time</Subheader>
<dd class="mt-1 d-flex align-items-baseline">
<span class="h2 mb-0">14.1m</span>
<Trending value={-12} class="ms-2" />
</dd>
</div>
<div class="col">
<Subheader as="dt">First Contact Resolution</Subheader>
<dd class="mt-1 d-flex align-items-baseline">
<span class="h2 mb-0">80.0%</span>
<Trending value={6} class="ms-2" />
</dd>
</div>
<div class="col">
<Subheader as="dt">Retention Rate</Subheader>
<dd class="mt-1 d-flex align-items-baseline">
<span class="h2 mb-0">28.0%</span>
<Trending value={-3} class="ms-2" />
</dd>
</div>
</dl>
</CardBody>
</Card>
</div>
</div>
<div class="col">
<Subheader as="dt">Retention Rate</Subheader>
<dd class="mt-1 d-flex align-items-baseline">
<span class="h2 mb-0">28.0%</span>
<Trending value={-3} class="ms-2" />
</dd>
</div>
</dl>
</CardBody>
</Card>
</div>
</div>
</DefaultLayout>
+221 -235
View File
@@ -1,250 +1,236 @@
---
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import CaptureScript from '@shared/components/CaptureScript.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 Progress from '@ui/Progress.astro';
import ProgressSteps from '@ui/ProgressSteps.astro';
import ProgressBg from '@ui/ProgressBg.astro';
import ProgressDescription from '@ui/ProgressDescription.astro';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import CaptureScript from '@shared/components/CaptureScript.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 Progress from '@ui/Progress.astro'
import ProgressSteps from '@ui/ProgressSteps.astro'
import ProgressBg from '@ui/ProgressBg.astro'
import ProgressDescription from '@ui/ProgressDescription.astro'
---
<DefaultLayout title="Progress" pageHeader="Progress" pageMenu="base.progress">
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3">
<div class="col">
<Card>
<CardBody>
<CardTitle>Default</CardTitle>
<div class="space-y">
<Progress value="0" />
<Progress value="20" />
<Progress value="40" />
<Progress value="100" />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>With value</CardTitle>
<div class="space-y">
<Progress value="10" showValue size="lg" />
<Progress value="20" showValue size="lg" />
<Progress value="90" showValue size="lg" />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Colors</CardTitle>
<div class="space-y">
<Progress color="blue" value="20" />
<Progress color="green" value="40" />
<Progress color="yellow" value="60" />
<Progress color="red" value="80" />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Sizes</CardTitle>
<div class="space-y">
<Progress value="20" size="sm" />
<Progress value="40" />
<Progress value="60" size="lg" />
<Progress value="80" size="xl" />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Indeterminate</CardTitle>
<div class="space-y">
<Progress indeterminate />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Multiple values</CardTitle>
<div class="space-y">
<Progress values={[20, 30, 10]} />
<Progress values={[10, 20, 30, 40]} class="progress-separated" />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Striped</CardTitle>
<div class="space-y">
<Progress color="blue" value="20" striped />
<Progress color="green" value="40" striped />
<Progress color="yellow" value="60" striped />
<Progress color="red" value="80" striped />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Animated</CardTitle>
<div class="space-y">
<Progress value="20" striped animated />
<Progress value="40" color="green" striped animated />
<Progress value="60" color="yellow" striped animated />
<Progress value="80" color="red" striped animated />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>
Animated with JavaScript
</CardTitle>
<div class="row align-items-center g-0">
<div class="col">
<Progress value="0" id="progress-animated" />
</div>
<div class="col-2 text-end" id="progress-animated-value">
0%
</div>
</div>
<ButtonList class="mt-3">
<button class="btn btn-sm" id="progress-animated-0">0%</button>
<button class="btn btn-sm" id="progress-animated-10">10%</button>
<button class="btn btn-sm" id="progress-animated-50">50%</button>
<button class="btn btn-sm" id="progress-animated-100">100%</button>
<button class="btn btn-sm ms-3" id="progress-animated-minus-10">-10%</button>
<button class="btn btn-sm" id="progress-animated-add-10">+10%</button>
</ButtonList>
<CaptureScript>
<!-- BEGIN SCRIPT OF ANIMATION -->
<script is:inline>
/*
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3">
<div class="col">
<Card>
<CardBody>
<CardTitle>Default</CardTitle>
<div class="space-y">
<Progress value="0" />
<Progress value="20" />
<Progress value="40" />
<Progress value="100" />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>With value</CardTitle>
<div class="space-y">
<Progress value="10" showValue size="lg" />
<Progress value="20" showValue size="lg" />
<Progress value="90" showValue size="lg" />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Colors</CardTitle>
<div class="space-y">
<Progress color="blue" value="20" />
<Progress color="green" value="40" />
<Progress color="yellow" value="60" />
<Progress color="red" value="80" />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Sizes</CardTitle>
<div class="space-y">
<Progress value="20" size="sm" />
<Progress value="40" />
<Progress value="60" size="lg" />
<Progress value="80" size="xl" />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Indeterminate</CardTitle>
<div class="space-y">
<Progress indeterminate />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Multiple values</CardTitle>
<div class="space-y">
<Progress values={[20, 30, 10]} />
<Progress values={[10, 20, 30, 40]} class="progress-separated" />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Striped</CardTitle>
<div class="space-y">
<Progress color="blue" value="20" striped />
<Progress color="green" value="40" striped />
<Progress color="yellow" value="60" striped />
<Progress color="red" value="80" striped />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Animated</CardTitle>
<div class="space-y">
<Progress value="20" striped animated />
<Progress value="40" color="green" striped animated />
<Progress value="60" color="yellow" striped animated />
<Progress value="80" color="red" striped animated />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle> Animated with JavaScript </CardTitle>
<div class="row align-items-center g-0">
<div class="col">
<Progress value="0" id="progress-animated" />
</div>
<div class="col-2 text-end" id="progress-animated-value">0%</div>
</div>
<ButtonList class="mt-3">
<button class="btn btn-sm" id="progress-animated-0">0%</button>
<button class="btn btn-sm" id="progress-animated-10">10%</button>
<button class="btn btn-sm" id="progress-animated-50">50%</button>
<button class="btn btn-sm" id="progress-animated-100">100%</button>
<button class="btn btn-sm ms-3" id="progress-animated-minus-10">-10%</button>
<button class="btn btn-sm" id="progress-animated-add-10">+10%</button>
</ButtonList>
<CaptureScript>
<!-- BEGIN SCRIPT OF ANIMATION -->
<script is:inline>
/*
This script is for animation of the last progress bar.
It increases the progress bar value by a random amount every 2 seconds until it reaches 100%.
When it reaches 100%, it changes the color to green and stops the animation.
This is just a demo script to show how to animate the progress bar. You can modify it as needed.
*/
const progress = document.getElementById('progress-animated')!;
let width = 0;
const progress = document.getElementById('progress-animated')
let width = 0
const setWidth = (w: number) => {
width = Math.min(Math.max(w, 0), 100);
const setWidth = (w) => {
width = Math.min(Math.max(w, 0), 100)
const bar = progress.querySelector<HTMLElement>('.progress-bar')!;
bar.style.width = `${width}%`;
bar.setAttribute('aria-valuenow', `${width}`);
document.getElementById('progress-animated-value')!.innerText = `${width}%`;
bar.classList.toggle('bg-green', width >= 100);
};
const bar = progress.querySelector('.progress-bar')
bar.style.width = `${width}%`
bar.setAttribute('aria-valuenow', `${width}`)
document.getElementById('progress-animated-value').innerText = `${width}%`
bar.classList.toggle('bg-green', width >= 100)
}
const interval = setInterval(() => {
setWidth(width + Math.ceil(Math.random() * 10));
if (width >= 100) clearInterval(interval);
}, 2000);
const interval = setInterval(() => {
setWidth(width + Math.ceil(Math.random() * 10))
if (width >= 100) clearInterval(interval)
}, 2000)
document.getElementById('progress-animated-0')!.addEventListener('click', () => setWidth(0));
document.getElementById('progress-animated-add-10')!.addEventListener('click', () => setWidth(width + 10));
document.getElementById('progress-animated-minus-10')!.addEventListener('click', () => setWidth(width - 10));
document.getElementById('progress-animated-100')!.addEventListener('click', () => setWidth(100));
</script>
<!-- END SCRIPT OF ANIMATION -->
</CaptureScript>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>
Steps Progress
</CardTitle>
<div class="space-y">
<ProgressSteps count={3} />
<ProgressSteps count={5} active={4} />
<ProgressSteps count={10} color="red" />
<ProgressSteps count={8} color="green" active={8} />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>
Progress Background
</CardTitle>
<div class="space-y">
<ProgressBg value="85" text="Poland" showValue />
<ProgressBg value="65" text="Germany" showValue />
<ProgressBg value="45" text="United States" showValue />
<ProgressBg value="25" text="France" showValue />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>
Progress Background Colors
</CardTitle>
<div class="space-y">
<ProgressBg value="75" text="Success" color="success-lt" showValue />
<ProgressBg value="60" text="Warning" color="warning-lt" showValue />
<ProgressBg value="40" text="Danger" color="danger-lt" showValue />
<ProgressBg value="90" text="Info" color="info-lt" showValue />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>
Progress Description
</CardTitle>
<div class="space-y">
<ProgressDescription label="Project completion" value="85" color="green" />
<ProgressDescription label="Storage usage" description="2.4GB of 5GB" value="48" color="blue" />
<ProgressDescription label="Download progress" value="75" color="yellow" />
<ProgressDescription label="Skills assessment" description="HTML/CSS" value="92" color="red" />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>
Progress Description Sizes
</CardTitle>
<div class="space-y">
<ProgressDescription label="Small progress" value="60" size="sm" color="blue" />
<ProgressDescription label="Default progress" value="70" color="green" />
<ProgressDescription label="Large progress" value="80" size="lg" color="orange" />
<ProgressDescription label="Extra large" value="90" size="xl" color="purple" />
</div>
</CardBody>
</Card>
</div>
</div>
document.getElementById('progress-animated-0').addEventListener('click', () => setWidth(0))
document.getElementById('progress-animated-add-10').addEventListener('click', () => setWidth(width + 10))
document.getElementById('progress-animated-minus-10').addEventListener('click', () => setWidth(width - 10))
document.getElementById('progress-animated-100').addEventListener('click', () => setWidth(100))
</script>
<!-- END SCRIPT OF ANIMATION -->
</CaptureScript>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle> Steps Progress </CardTitle>
<div class="space-y">
<ProgressSteps count={3} />
<ProgressSteps count={5} active={4} />
<ProgressSteps count={10} color="red" />
<ProgressSteps count={8} color="green" active={8} />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle> Progress Background </CardTitle>
<div class="space-y">
<ProgressBg value="85" text="Poland" showValue />
<ProgressBg value="65" text="Germany" showValue />
<ProgressBg value="45" text="United States" showValue />
<ProgressBg value="25" text="France" showValue />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle> Progress Background Colors </CardTitle>
<div class="space-y">
<ProgressBg value="75" text="Success" color="success-lt" showValue />
<ProgressBg value="60" text="Warning" color="warning-lt" showValue />
<ProgressBg value="40" text="Danger" color="danger-lt" showValue />
<ProgressBg value="90" text="Info" color="info-lt" showValue />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle> Progress Description </CardTitle>
<div class="space-y">
<ProgressDescription label="Project completion" value="85" color="green" />
<ProgressDescription label="Storage usage" description="2.4GB of 5GB" value="48" color="blue" />
<ProgressDescription label="Download progress" value="75" color="yellow" />
<ProgressDescription label="Skills assessment" description="HTML/CSS" value="92" color="red" />
</div>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle> Progress Description Sizes </CardTitle>
<div class="space-y">
<ProgressDescription label="Small progress" value="60" size="sm" color="blue" />
<ProgressDescription label="Default progress" value="70" color="green" />
<ProgressDescription label="Large progress" value="80" size="lg" color="orange" />
<ProgressDescription label="Extra large" value="90" size="xl" color="purple" />
</div>
</CardBody>
</Card>
</div>
</div>
</DefaultLayout>
+62 -62
View File
@@ -1,76 +1,76 @@
---
// Color swatches use site.themeColors (blue…cyan).
// Large prose/nav-segmented block from source template omitted (was commented out).
import BaseLayout from '@shared/layouts/BaseLayout.astro';
import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro';
import { site } from '@shared/lib/site';
import BaseLayout from '@shared/layouts/BaseLayout.astro'
import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro'
import { site } from '@shared/lib/site'
---
<BaseLayout pageLibs={['signature_pad']}>
<style is:inline>
.screenshot {
width: 1024px;
height: 768px;
background: var(--tblr-bg-surface-tertiary) linear-gradient(to top left, rgba(0, 0, 0, 0.04), rgba(0, 0, 0, 0));
position: relative;
}
<style is:inline>
.screenshot {
width: 1024px;
height: 768px;
background: var(--tblr-bg-surface-tertiary) linear-gradient(to top left, rgba(0, 0, 0, 0.04), rgba(0, 0, 0, 0));
position: relative;
}
.screenshot-card {
padding: 64px;
border-radius: 16px;
box-shadow:
0 1px 1px 0 rgba(0, 0, 0, 0.02),
0 8px 24px -4px rgba(0, 0, 0, 0.04),
0 24px 40px -8px rgba(0, 0, 0, 0.1);
}
.screenshot-card {
padding: 64px;
border-radius: 16px;
box-shadow:
0 1px 1px 0 rgba(0, 0, 0, 0.02),
0 8px 24px -4px rgba(0, 0, 0, 0.04),
0 24px 40px -8px rgba(0, 0, 0, 0.1);
}
.screenshot-logo {
display: flex;
align-items: center;
justify-content: center;
padding: 0 5rem;
flex: 1;
}
.screenshot-logo {
display: flex;
align-items: center;
justify-content: center;
padding: 0 5rem;
flex: 1;
}
.cursor {
position: absolute;
width: 26px;
height: 37px;
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 26 37'%3E%3Cg filter='url(%23a)'%3E%3Cpath fill='%23fff' fill-rule='evenodd' d='M8.56 13.902c-.284-.358-.63-1.092-1.243-1.983-.348-.505-1.211-1.454-1.468-1.935-.223-.426-.2-.617-.146-.97.094-.628.738-1.117 1.425-1.051.519.049.959.392 1.355.716.239.195.533.574.71.788.163.196.203.277.377.509.23.306.302.458.214.12-.071-.495-.187-1.342-.355-2.091-.128-.568-.16-.657-.281-1.093-.13-.464-.195-.79-.316-1.281-.084-.348-.235-1.06-.276-1.46-.057-.546-.087-1.438.264-1.848.275-.321.906-.418 1.296-.22.513.259.803 1.003.937 1.3.238.534.386 1.15.515 1.96.165 1.032.466 2.463.476 2.764.024-.37-.068-1.146-.004-1.5.059-.321.329-.694.667-.795.285-.085.62-.116.915-.055.313.064.643.288.767.499.362.624.369 1.899.384 1.83.085-.375.07-1.228.284-1.583.14-.234.496-.445.686-.48.295-.051.655-.067.964-.007.25.049.587.345.677.487.218.344.343 1.317.38 1.658.015.14.073-.392.293-.736.405-.64 1.843-.763 1.898.639.025.654.02.624.02 1.064 0 .517-.013.828-.04 1.202-.031.4-.117 1.303-.242 1.742-.087.3-.372.977-.652 1.383 0 0-1.075 1.25-1.192 1.814-.117.562-.079.566-.102.964-.023.398.122.922.122.922s-.802.104-1.235.035c-.39-.063-.875-.84-1-1.079-.171-.328-.539-.264-.681-.023-.226.383-.71 1.07-1.051 1.113-.668.084-2.054.032-3.14.02 0 0 .185-1.01-.226-1.357-.306-.26-.83-.784-1.144-1.06l-.832-.921Z' clip-rule='evenodd'/%3E%3Cpath stroke='%23000' stroke-linecap='round' stroke-linejoin='round' d='M16.794 14.257v-3.459m-2.015 3.47-.016-3.472m-1.98.031.02 3.426m-4.243-.35c-.284-.36-.63-1.094-1.243-1.985-.348-.503-1.211-1.452-1.468-1.934-.223-.426-.2-.617-.146-.97.094-.628.738-1.117 1.425-1.051.519.049.959.392 1.355.716.239.195.533.574.71.788.163.196.203.277.377.509.23.306.302.458.214.12-.071-.495-.187-1.342-.355-2.091-.128-.568-.16-.657-.281-1.093-.13-.464-.195-.79-.316-1.281-.084-.348-.235-1.06-.276-1.46-.057-.546-.087-1.438.264-1.848.275-.321.906-.418 1.296-.22.513.259.803 1.003.937 1.3.238.534.386 1.15.515 1.96.165 1.032.466 2.463.476 2.764.024-.37-.068-1.146-.004-1.5.059-.321.329-.694.667-.795.285-.085.62-.116.915-.055.313.064.643.288.767.499.362.624.369 1.899.384 1.83.085-.375.07-1.228.284-1.583.14-.234.496-.445.686-.48.295-.051.655-.067.964-.007.25.049.587.345.677.487.218.344.343 1.317.38 1.658.015.14.073-.392.293-.736.405-.64 1.843-.763 1.898.639.025.654.02.624.02 1.064 0 .517-.013.828-.04 1.202-.031.4-.117 1.303-.242 1.742-.087.3-.372.977-.652 1.383 0 0-1.075 1.25-1.192 1.814-.117.562-.079.566-.102.964-.023.398.122.922.122.922s-.802.104-1.235.035c-.39-.063-.875-.84-1-1.079-.171-.328-.539-.264-.681-.023-.226.383-.71 1.07-1.051 1.113-.668.084-2.054.032-3.14.02 0 0 .185-1.01-.226-1.357-.306-.26-.83-.784-1.144-1.06l-.832-.921Z'/%3E%3C/g%3E%3Cdefs%3E%3Cfilter id='a' width='30' height='40' x='-2' y='-1' color-interpolation-filters='sRGB' filterUnits='userSpaceOnUse'%3E%3CfeFlood flood-opacity='0' result='BackgroundImageFix'/%3E%3CfeColorMatrix in='SourceAlpha' result='hardAlpha' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'/%3E%3CfeOffset dy='1'/%3E%3CfeGaussianBlur stdDeviation='1'/%3E%3CfeColorMatrix values='0 0 0 0 0.156863 0 0 0 0 0.156863 0 0 0 0 0.2 0 0 0 0.05 0'/%3E%3CfeBlend in2='BackgroundImageFix' result='effect1_dropShadow_55_1571'/%3E%3CfeColorMatrix in='SourceAlpha' result='hardAlpha' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'/%3E%3CfeOffset dy='3'/%3E%3CfeGaussianBlur stdDeviation='1.5'/%3E%3CfeColorMatrix values='0 0 0 0 0.156863 0 0 0 0 0.156863 0 0 0 0 0.2 0 0 0 0.04 0'/%3E%3CfeBlend in2='effect1_dropShadow_55_1571' result='effect2_dropShadow_55_1571'/%3E%3CfeColorMatrix in='SourceAlpha' result='hardAlpha' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'/%3E%3CfeOffset dy='8'/%3E%3CfeGaussianBlur stdDeviation='2.5'/%3E%3CfeColorMatrix values='0 0 0 0 0.156863 0 0 0 0 0.156863 0 0 0 0 0.2 0 0 0 0.03 0'/%3E%3CfeBlend in2='effect2_dropShadow_55_1571' result='effect3_dropShadow_55_1571'/%3E%3CfeColorMatrix in='SourceAlpha' result='hardAlpha' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'/%3E%3CfeOffset dy='14'/%3E%3CfeGaussianBlur stdDeviation='2.5'/%3E%3CfeColorMatrix values='0 0 0 0 0.156863 0 0 0 0 0.156863 0 0 0 0 0.2 0 0 0 0.01 0'/%3E%3CfeBlend in2='effect3_dropShadow_55_1571' result='effect4_dropShadow_55_1571'/%3E%3CfeBlend in='SourceGraphic' in2='effect4_dropShadow_55_1571' result='shape'/%3E%3C/filter%3E%3C/defs%3E%3C/svg%3E");
top: calc(100% - 10px);
left: calc(50% - 10px);
}
.cursor {
position: absolute;
width: 26px;
height: 37px;
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 26 37'%3E%3Cg filter='url(%23a)'%3E%3Cpath fill='%23fff' fill-rule='evenodd' d='M8.56 13.902c-.284-.358-.63-1.092-1.243-1.983-.348-.505-1.211-1.454-1.468-1.935-.223-.426-.2-.617-.146-.97.094-.628.738-1.117 1.425-1.051.519.049.959.392 1.355.716.239.195.533.574.71.788.163.196.203.277.377.509.23.306.302.458.214.12-.071-.495-.187-1.342-.355-2.091-.128-.568-.16-.657-.281-1.093-.13-.464-.195-.79-.316-1.281-.084-.348-.235-1.06-.276-1.46-.057-.546-.087-1.438.264-1.848.275-.321.906-.418 1.296-.22.513.259.803 1.003.937 1.3.238.534.386 1.15.515 1.96.165 1.032.466 2.463.476 2.764.024-.37-.068-1.146-.004-1.5.059-.321.329-.694.667-.795.285-.085.62-.116.915-.055.313.064.643.288.767.499.362.624.369 1.899.384 1.83.085-.375.07-1.228.284-1.583.14-.234.496-.445.686-.48.295-.051.655-.067.964-.007.25.049.587.345.677.487.218.344.343 1.317.38 1.658.015.14.073-.392.293-.736.405-.64 1.843-.763 1.898.639.025.654.02.624.02 1.064 0 .517-.013.828-.04 1.202-.031.4-.117 1.303-.242 1.742-.087.3-.372.977-.652 1.383 0 0-1.075 1.25-1.192 1.814-.117.562-.079.566-.102.964-.023.398.122.922.122.922s-.802.104-1.235.035c-.39-.063-.875-.84-1-1.079-.171-.328-.539-.264-.681-.023-.226.383-.71 1.07-1.051 1.113-.668.084-2.054.032-3.14.02 0 0 .185-1.01-.226-1.357-.306-.26-.83-.784-1.144-1.06l-.832-.921Z' clip-rule='evenodd'/%3E%3Cpath stroke='%23000' stroke-linecap='round' stroke-linejoin='round' d='M16.794 14.257v-3.459m-2.015 3.47-.016-3.472m-1.98.031.02 3.426m-4.243-.35c-.284-.36-.63-1.094-1.243-1.985-.348-.503-1.211-1.452-1.468-1.934-.223-.426-.2-.617-.146-.97.094-.628.738-1.117 1.425-1.051.519.049.959.392 1.355.716.239.195.533.574.71.788.163.196.203.277.377.509.23.306.302.458.214.12-.071-.495-.187-1.342-.355-2.091-.128-.568-.16-.657-.281-1.093-.13-.464-.195-.79-.316-1.281-.084-.348-.235-1.06-.276-1.46-.057-.546-.087-1.438.264-1.848.275-.321.906-.418 1.296-.22.513.259.803 1.003.937 1.3.238.534.386 1.15.515 1.96.165 1.032.466 2.463.476 2.764.024-.37-.068-1.146-.004-1.5.059-.321.329-.694.667-.795.285-.085.62-.116.915-.055.313.064.643.288.767.499.362.624.369 1.899.384 1.83.085-.375.07-1.228.284-1.583.14-.234.496-.445.686-.48.295-.051.655-.067.964-.007.25.049.587.345.677.487.218.344.343 1.317.38 1.658.015.14.073-.392.293-.736.405-.64 1.843-.763 1.898.639.025.654.02.624.02 1.064 0 .517-.013.828-.04 1.202-.031.4-.117 1.303-.242 1.742-.087.3-.372.977-.652 1.383 0 0-1.075 1.25-1.192 1.814-.117.562-.079.566-.102.964-.023.398.122.922.122.922s-.802.104-1.235.035c-.39-.063-.875-.84-1-1.079-.171-.328-.539-.264-.681-.023-.226.383-.71 1.07-1.051 1.113-.668.084-2.054.032-3.14.02 0 0 .185-1.01-.226-1.357-.306-.26-.83-.784-1.144-1.06l-.832-.921Z'/%3E%3C/g%3E%3Cdefs%3E%3Cfilter id='a' width='30' height='40' x='-2' y='-1' color-interpolation-filters='sRGB' filterUnits='userSpaceOnUse'%3E%3CfeFlood flood-opacity='0' result='BackgroundImageFix'/%3E%3CfeColorMatrix in='SourceAlpha' result='hardAlpha' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'/%3E%3CfeOffset dy='1'/%3E%3CfeGaussianBlur stdDeviation='1'/%3E%3CfeColorMatrix values='0 0 0 0 0.156863 0 0 0 0 0.156863 0 0 0 0 0.2 0 0 0 0.05 0'/%3E%3CfeBlend in2='BackgroundImageFix' result='effect1_dropShadow_55_1571'/%3E%3CfeColorMatrix in='SourceAlpha' result='hardAlpha' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'/%3E%3CfeOffset dy='3'/%3E%3CfeGaussianBlur stdDeviation='1.5'/%3E%3CfeColorMatrix values='0 0 0 0 0.156863 0 0 0 0 0.156863 0 0 0 0 0.2 0 0 0 0.04 0'/%3E%3CfeBlend in2='effect1_dropShadow_55_1571' result='effect2_dropShadow_55_1571'/%3E%3CfeColorMatrix in='SourceAlpha' result='hardAlpha' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'/%3E%3CfeOffset dy='8'/%3E%3CfeGaussianBlur stdDeviation='2.5'/%3E%3CfeColorMatrix values='0 0 0 0 0.156863 0 0 0 0 0.156863 0 0 0 0 0.2 0 0 0 0.03 0'/%3E%3CfeBlend in2='effect2_dropShadow_55_1571' result='effect3_dropShadow_55_1571'/%3E%3CfeColorMatrix in='SourceAlpha' result='hardAlpha' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'/%3E%3CfeOffset dy='14'/%3E%3CfeGaussianBlur stdDeviation='2.5'/%3E%3CfeColorMatrix values='0 0 0 0 0.156863 0 0 0 0 0.156863 0 0 0 0 0.2 0 0 0 0.01 0'/%3E%3CfeBlend in2='effect3_dropShadow_55_1571' result='effect4_dropShadow_55_1571'/%3E%3CfeBlend in='SourceGraphic' in2='effect4_dropShadow_55_1571' result='shape'/%3E%3C/filter%3E%3C/defs%3E%3C/svg%3E");
top: calc(100% - 10px);
left: calc(50% - 10px);
}
.avatar-2 {
background-image: url(/static/avatars/032f.jpg);
}
</style>
.avatar-2 {
background-image: url(/static/avatars/032f.jpg);
}
</style>
<div class="page page-center align-items-center">
<div class="screenshot d-flex flex-column justify-content-center align-items-center px-12">
<div class="screenshot-logo">
<NavbarLogo class="brand-gray" />
</div>
<div class="screenshot-card card p-6">
<div class="row g-4">
{
site.themeColors.map((color) => (
<div class="col-2">
<div class={`p-5 bg-gradient bg-gradient-from-${color} bg-gradient-to-transparent bg-gradient-to-ts rounded ratio ratio-1x1 border`} />
</div>
))
}
</div>
</div>
<div class="page page-center align-items-center">
<div class="screenshot d-flex flex-column justify-content-center align-items-center px-12">
<div class="screenshot-logo">
<NavbarLogo class="brand-gray" />
</div>
<div class="screenshot-card card p-6">
<div class="row g-4">
{
site.themeColors.map((color) => (
<div class="col-2">
<div class={`p-5 bg-gradient bg-gradient-from-${color} bg-gradient-to-transparent bg-gradient-to-ts rounded ratio ratio-1x1 border`} />
</div>
))
}
</div>
</div>
<div class="screenshot-logo"></div>
</div>
<div class="screenshot-logo"></div>
</div>
<nav class="nav-segmented mt-4">
<a href="?theme=light" class="nav-link hide-theme-light">Light</a>
<a href="?theme=light" class="nav-link active hide-theme-dark">Light</a>
<a href="?theme=dark" class="nav-link hide-theme-dark">Dark</a>
<a href="?theme=dark" class="nav-link active hide-theme-light">Dark</a>
</nav>
</div>
<nav class="nav-segmented mt-4">
<a href="?theme=light" class="nav-link hide-theme-light">Light</a>
<a href="?theme=light" class="nav-link active hide-theme-dark">Light</a>
<a href="?theme=dark" class="nav-link hide-theme-dark">Dark</a>
<a href="?theme=dark" class="nav-link active hide-theme-light">Dark</a>
</nav>
</div>
</BaseLayout>
+22 -22
View File
@@ -1,31 +1,31 @@
---
// Photo id=11 → 12th entry of the unfiltered photos list; no horizontal filter.
import BaseLayout from '@shared/layouts/BaseLayout.astro';
import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro';
import SignInForm from '@shared/components/cards/SignInForm.astro';
import Photo from '@ui/Photo.astro';
import photos from '@data/photos.json';
import BaseLayout from '@shared/layouts/BaseLayout.astro'
import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro'
import SignInForm from '@shared/components/cards/SignInForm.astro'
import Photo from '@ui/Photo.astro'
import photos from '@data/photos.json'
---
<BaseLayout title="Sign in with cover" bodyClass="d-flex flex-column bg-white">
<div class="row g-0 flex-fill">
<div class="col-12 col-lg-6 col-xl-4 border-top-wide border-primary d-flex flex-column justify-content-center">
<div class="container container-tight my-5 px-lg-5">
<div class="text-center mb-4">
<NavbarLogo />
</div>
<div class="row g-0 flex-fill">
<div class="col-12 col-lg-6 col-xl-4 border-top-wide border-primary d-flex flex-column justify-content-center">
<div class="container container-tight my-5 px-lg-5">
<div class="text-center mb-4">
<NavbarLogo />
</div>
<h2 class="h3 text-center mb-3"> Login to your account </h2>
<h2 class="h3 text-center mb-3">Login to your account</h2>
<SignInForm />
<SignInForm />
<div class="text-center text-secondary mt-3">
Don't have account yet? <a href="./sign-up.html" tabindex="-1">Sign up</a>
</div>
</div>
</div>
<div class="col-12 col-lg-6 col-xl-8 d-none d-lg-block">
<Photo photo={photos[11]} class="bg-cover h-100 min-vh-100" background />
</div>
</div>
<div class="text-center text-secondary mt-3">
Don't have account yet? <a href="./sign-up.html" tabindex="-1">Sign up</a>
</div>
</div>
</div>
<div class="col-12 col-lg-6 col-xl-8 d-none d-lg-block">
<Photo photo={photos[11]} class="bg-cover h-100 min-vh-100" background />
</div>
</div>
</BaseLayout>
+15 -15
View File
@@ -1,24 +1,24 @@
---
import SingleLayout from '@shared/layouts/SingleLayout.astro';
import SingleLayout from '@shared/layouts/SingleLayout.astro'
// TODO: add `email` to src/lib/site.ts — kept local here to avoid cross-agent conflicts.
const siteEmail = 'support@tabler.io';
const siteEmail = 'support@tabler.io'
---
<SingleLayout title="Sign in link">
<div class="text-center">
<div class="my-5">
<h2 class="h1">Check your inbox</h2>
<div class="text-center">
<div class="my-5">
<h2 class="h1">Check your inbox</h2>
<p class="fs-h3 text-secondary">
We've sent you a magic link to <strong>{siteEmail}</strong>.<br />
Please click the link to confirm your address.
</p>
</div>
<p class="fs-h3 text-secondary">
We've sent you a magic link to <strong>{siteEmail}</strong>.<br />
Please click the link to confirm your address.
</p>
</div>
<div class="text-center text-secondary mt-3">
Can't see the email? Please check the spam folder.<br />
Wrong email? Please <a href="#">re-enter your address</a>.
</div>
</div>
<div class="text-center text-secondary mt-3">
Can't see the email? Please check the spam folder.<br />
Wrong email? Please <a href="#">re-enter your address</a>.
</div>
</div>
</SingleLayout>
File diff suppressed because one or more lines are too long
+85 -90
View File
@@ -1,104 +1,99 @@
---
// plugins: ['social'] front matter is inert — .social styles ship globally in tabler-socials.css.
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import ButtonList from '@ui/ButtonList.astro';
import Trending from '@ui/Trending.astro';
import Card from '@ui/Card.astro';
import CardHeader from '@ui/CardHeader.astro';
import CardBody from '@ui/CardBody.astro';
import socialTiles from '@data/social-tiles.json';
import socials from '@data/socials.json';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import ButtonList from '@ui/ButtonList.astro'
import Trending from '@ui/Trending.astro'
import Card from '@ui/Card.astro'
import CardHeader from '@ui/CardHeader.astro'
import CardBody from '@ui/CardBody.astro'
import socialTiles from '@data/social-tiles.json'
import socials from '@data/socials.json'
interface SocialTile {
icon: string;
title: string;
description: string;
trending: number;
icon: string
title: string
description: string
trending: number
}
interface Social {
name: string;
file: string;
name: string
file: string
}
const tiles = socialTiles as SocialTile[];
const socialList = socials as Social[];
const fillers = Array.from({ length: 21 });
const tiles = socialTiles as SocialTile[]
const socialList = socials as Social[]
const fillers = Array.from({ length: 21 })
---
<DefaultLayout title="Social icons" pageHeader="Social icons" pageMenu="base.social">
<div class="row row-cards">
<div class="col-12">
<div class="row g-3">
{
tiles.map((tile) => (
<div class="col-3">
<Card>
<CardBody>
<div class="row align-items-center">
<div class="col-auto">
<span class={`social social-md social-app-${tile.icon}`} />
</div>
<div class="col">
<div>{tile.title}</div>
<div class="text-secondary">{tile.description}</div>
</div>
<div class="col-auto">
<Trending value={tile.trending} />
</div>
</div>
</CardBody>
</Card>
</div>
))
}
</div>
</div>
<div class="col-12">
<Card>
<CardHeader>
<div class="card-title">Sign in with social media</div>
</CardHeader>
<CardBody>
<ButtonList>
{
socialList.map((social) => (
<button class="btn">
<span class={`icon social social-app-${social.file}`} />
Sign in with {social.name}
</button>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="row row-cards">
<div class="col-12">
<div class="row g-3">
{
tiles.map((tile) => (
<div class="col-3">
<Card>
<CardBody>
<div class="row align-items-center">
<div class="col-auto">
<span class={`social social-md social-app-${tile.icon}`} />
</div>
<div class="col">
<div>{tile.title}</div>
<div class="text-secondary">{tile.description}</div>
</div>
<div class="col-auto">
<Trending value={tile.trending} />
</div>
</div>
</CardBody>
</Card>
</div>
))
}
</div>
</div>
<div class="col-12">
<Card>
<CardHeader>
<div class="card-title">Sign in with social media</div>
</CardHeader>
<CardBody>
<ButtonList>
{
socialList.map((social) => (
<button class="btn">
<span class={`icon social social-app-${social.file}`} />
Sign in with {social.name}
</button>
))
}
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card>
<CardHeader>
<div class="card-title">List of all social media icons</div>
</CardHeader>
<CardBody class="p-0">
<div class="demo-icons-list-wrap">
<div class="demo-icons-list">
{
socialList.map((social) => (
<span
class="demo-icons-list-item"
title={social.name}
data-bs-toggle="tooltip"
data-bs-placement="top"
>
<span class={`social social-app-${social.file}`} />
</span>
))
}
{fillers.map(() => <div />)}
</div>
</div>
</CardBody>
</Card>
</div>
</div>
<div class="col-12">
<Card>
<CardHeader>
<div class="card-title">List of all social media icons</div>
</CardHeader>
<CardBody class="p-0">
<div class="demo-icons-list-wrap">
<div class="demo-icons-list">
{
socialList.map((social) => (
<span class="demo-icons-list-item" title={social.name} data-bs-toggle="tooltip" data-bs-placement="top">
<span class={`social social-app-${social.file}`} />
</span>
))
}
{fillers.map(() => <div />)}
</div>
</div>
</CardBody>
</Card>
</div>
</div>
</DefaultLayout>
+97 -97
View File
@@ -1,114 +1,114 @@
---
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import Tag from '@ui/Tag.astro';
import TagsList from '@ui/TagsList.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 flags from '@data/flags.json';
import siteData from '@data/site.json';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import Tag from '@ui/Tag.astro'
import TagsList from '@ui/TagsList.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 flags from '@data/flags.json'
import siteData from '@data/site.json'
const tagIcons = ['bold', 'italic', 'underline', 'copy', 'scissors', 'file-plus', 'file-minus', 'ghost', 'star', 'script', 'photo', 'dog', 'piano'];
const tagIcons = ['bold', 'italic', 'underline', 'copy', 'scissors', 'file-plus', 'file-minus', 'ghost', 'star', 'script', 'photo', 'dog', 'piano']
const range = (a: number, b: number) => Array.from({ length: b - a + 1 }, (_, i) => a + i);
const range = (a: number, b: number) => Array.from({ length: b - a + 1 }, (_, i) => a + i)
const flags9 = flags.slice(0, 9);
const people8 = people.slice(0, 8);
const flags9 = flags.slice(0, 9)
const people8 = people.slice(0, 8)
// site.colors yields value objects with .class / .title.
const colors = Object.values(siteData.colors) as { class: string; title: string }[];
const colors = Object.values(siteData.colors) as { class: string; title: string }[]
---
<DefaultLayout title="Tags" pageHeader="Tags" pageMenu="base.tags">
<div class="row row-cards row-cols-1 row-cols-md-2 row-cols-lg-3">
<div class="col">
<Card>
<CardBody>
<CardTitle>Default tags</CardTitle>
<TagsList>
{range(1, 14).map((i) => <Tag text={`Label ${i}`} />)}
</TagsList>
</CardBody>
</Card>
</div>
<div class="row row-cards row-cols-1 row-cols-md-2 row-cols-lg-3">
<div class="col">
<Card>
<CardBody>
<CardTitle>Default tags</CardTitle>
<TagsList>
{range(1, 14).map((i) => <Tag text={`Label ${i}`} />)}
</TagsList>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Tags with flag</CardTitle>
<TagsList>
{flags9.map((country) => <Tag text={country.name} flag={(country as { code?: string }).code} />)}
</TagsList>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Tags with flag</CardTitle>
<TagsList>
{flags9.map((country) => <Tag text={country.name} flag={(country as { code?: string }).code} />)}
</TagsList>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Tags with icon</CardTitle>
<TagsList>
{tagIcons.map((icon) => <Tag text={icon} icon={icon} />)}
</TagsList>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Tags with icon</CardTitle>
<TagsList>
{tagIcons.map((icon) => <Tag text={icon} icon={icon} />)}
</TagsList>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Tags with avatar</CardTitle>
<TagsList>
{people8.map((person) => <Tag text={person.full_name} person={person} />)}
</TagsList>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Tags with avatar</CardTitle>
<TagsList>
{people8.map((person) => <Tag text={person.full_name} person={person} />)}
</TagsList>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Tags with status</CardTitle>
<TagsList>
{colors.map((color) => <Tag text={color.title} status={color.class} />)}
</TagsList>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Tags with status</CardTitle>
<TagsList>
{colors.map((color) => <Tag text={color.title} status={color.class} />)}
</TagsList>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Tags with legend</CardTitle>
<TagsList>
{colors.map((color) => <Tag text={color.title} legend={color.class} />)}
</TagsList>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Tags with legend</CardTitle>
<TagsList>
{colors.map((color) => <Tag text={color.title} legend={color.class} />)}
</TagsList>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Default tags</CardTitle>
<TagsList>
{range(1, 6).map((i) => <Tag text={`Label ${i}`} checkbox={true} />)}
{range(7, 12).map((i) => <Tag text={`Label ${i}`} checkbox={true} checked={true} />)}
</TagsList>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Default tags</CardTitle>
<TagsList>
{range(1, 6).map((i) => <Tag text={`Label ${i}`} checkbox={true} />)}
{range(7, 12).map((i) => <Tag text={`Label ${i}`} checkbox={true} checked={true} />)}
</TagsList>
</CardBody>
</Card>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Default tags</CardTitle>
<TagsList>
{range(1, 12).map((i) => <Tag text="Label" badge={i} />)}
</TagsList>
</CardBody>
</Card>
</div>
</div>
<div class="col">
<Card>
<CardBody>
<CardTitle>Default tags</CardTitle>
<TagsList>
{range(1, 12).map((i) => <Tag text="Label" badge={i} />)}
</TagsList>
</CardBody>
</Card>
</div>
</div>
</DefaultLayout>
+135 -162
View File
@@ -1,182 +1,155 @@
---
import CardActions from '@ui/CardActions.astro';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import Button from '@ui/Button.astro';
import Icon from '@ui/Icon.astro';
import Avatar from '@ui/Avatar.astro';
import Badge from '@ui/Badge.astro';
import Modal from '@shared/components/modals/Modal.astro';
import CaptureModal from '@shared/components/CaptureModal.astro';
import FormGroup from '@ui/FormGroup.astro';
import tasks from '@data/tasks.json';
import people from '@data/people.json';
import CardActions from '@ui/CardActions.astro'
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import Button from '@ui/Button.astro'
import Icon from '@ui/Icon.astro'
import Avatar from '@ui/Avatar.astro'
import Badge from '@ui/Badge.astro'
import Modal from '@shared/components/modals/Modal.astro'
import CaptureModal from '@shared/components/CaptureModal.astro'
import FormGroup from '@ui/FormGroup.astro'
import tasks from '@data/tasks.json'
import people from '@data/people.json'
interface Person {
id?: string;
full_name?: string;
photo?: string;
[key: string]: unknown;
id?: string
full_name?: string
photo?: string
[key: string]: unknown
}
interface Task {
name?: string;
assigned_to?: number;
due_date?: string;
'due-date'?: string;
priority?: string;
[key: string]: unknown;
'name'?: string
'assigned_to'?: number
'due_date'?: string
'due-date'?: string
'priority'?: string
[key: string]: unknown
}
interface Column {
name: string;
tasks: Task[];
name: string
tasks: Task[]
}
const columns = (tasks as { columns: Column[] }).columns;
const peopleList = people as Person[];
const columns = (tasks as { columns: Column[] }).columns
const peopleList = people as Person[]
// Assignable people for the add-task modal.
const selectedPeople = [5, 6, 2, 3].map((id) => peopleList[id - 1]);
const selectedPeople = [5, 6, 2, 3].map((id) => peopleList[id - 1])
---
<DefaultLayout title="Task List" pageHeader="Task List" pageMenu="extra.tasks.list">
<div class="row">
<div class="col-12">
{
columns.map((section) => (
<div class="card mb-4">
<div class="card-header">
<h3 class="card-title mb-0">{section.name}</h3>
<CardActions>
<Button text="New Task" icon="plus" modalId="add-task" size="sm" />
</CardActions>
</div>
<div class="table-responsive">
<table class="table table-vcenter card-table table-selectable">
<thead>
<tr>
<th class="w-1">
<input
type="checkbox"
class="form-check-input align-middle table-selectable-check"
aria-label="Select all tasks"
/>
</th>
<th class="w-50">Name</th>
<th class="d-none d-xl-table-cell">Assigned To</th>
<th class="d-none d-xxl-table-cell">Due Date</th>
<th>Priority</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
{section.tasks.map((task) => {
const person = task.assigned_to
? peopleList[task.assigned_to - 1]
: undefined;
return (
<tr>
<td>
<input
type="checkbox"
class="form-check-input align-middle table-selectable-check"
aria-label="Select task"
/>
</td>
<td>{task.name}</td>
<td>
{task.assigned_to && person ? (
<div class="d-flex align-items-center">
<Avatar personId={task.assigned_to} size="xs" class="me-2" />
<span>{person.full_name}</span>
</div>
) : (
<span class="text-secondary">Unassigned</span>
)}
</td>
<td class="text-secondary">
{task.due_date ? (
<Fragment>
<Icon name="calendar" class="me-1" />
{task.due_date}
</Fragment>
) : task['due-date'] ? (
<Fragment>
<Icon name="calendar" class="me-1" />
{task['due-date']}
</Fragment>
) : (
<span class="text-muted">—</span>
)}
</td>
<td>
{task.priority === 'High' ? (
<Badge text="High" color="red" light />
) : task.priority === 'Medium' ? (
<Badge text="Medium" color="yellow" light />
) : task.priority === 'Low' ? (
<Badge text="Low" color="blue" light />
) : (
<Badge text="—" light />
)}
</td>
<td class="text-end">
<Button text="View" size="sm" />
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
))
}
</div>
</div>
<div class="row">
<div class="col-12">
{
columns.map((section) => (
<div class="card mb-4">
<div class="card-header">
<h3 class="card-title mb-0">{section.name}</h3>
<CardActions>
<Button text="New Task" icon="plus" modalId="add-task" size="sm" />
</CardActions>
</div>
<div class="table-responsive">
<table class="table table-vcenter card-table table-selectable">
<thead>
<tr>
<th class="w-1">
<input type="checkbox" class="form-check-input align-middle table-selectable-check" aria-label="Select all tasks" />
</th>
<th class="w-50">Name</th>
<th class="d-none d-xl-table-cell">Assigned To</th>
<th class="d-none d-xxl-table-cell">Due Date</th>
<th>Priority</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
{section.tasks.map((task) => {
const person = task.assigned_to ? peopleList[task.assigned_to - 1] : undefined
return (
<tr>
<td>
<input type="checkbox" class="form-check-input align-middle table-selectable-check" aria-label="Select task" />
</td>
<td>{task.name}</td>
<td>
{task.assigned_to && person ? (
<div class="d-flex align-items-center">
<Avatar personId={task.assigned_to} size="xs" class="me-2" />
<span>{person.full_name}</span>
</div>
) : (
<span class="text-secondary">Unassigned</span>
)}
</td>
<td class="text-secondary">
{task.due_date ? (
<Fragment>
<Icon name="calendar" class="me-1" />
{task.due_date}
</Fragment>
) : task['due-date'] ? (
<Fragment>
<Icon name="calendar" class="me-1" />
{task['due-date']}
</Fragment>
) : (
<span class="text-muted">—</span>
)}
</td>
<td>{task.priority === 'High' ? <Badge text="High" color="red" light /> : task.priority === 'Medium' ? <Badge text="Medium" color="yellow" light /> : task.priority === 'Low' ? <Badge text="Low" color="blue" light /> : <Badge text="—" light />}</td>
<td class="text-end">
<Button text="View" size="sm" />
</td>
</tr>
)
})}
</tbody>
</table>
</div>
</div>
))
}
</div>
</div>
<CaptureModal>
<Modal modalId="add-task">
<div class="modal-header">
<h4 class="modal-title">Add task</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<CaptureModal>
<Modal modalId="add-task">
<div class="modal-header">
<h4 class="modal-title">Add task</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<FormGroup label="Name">
<input type="text" class="form-control" placeholder="Task name" />
</FormGroup>
<FormGroup label="Assigned To">
<select class="form-select">
<option value="">Select person</option>
{
selectedPeople.map((person) => (
<option value={person.id}>{person.full_name}</option>
))
}
</select>
</FormGroup>
<FormGroup label="Priority">
<select class="form-select">
<option value="Low">Low</option>
<option value="Medium">Medium</option>
<option value="High">High</option>
</select>
</FormGroup>
<FormGroup label="Description">
<textarea class="form-control" rows="3" placeholder="Task description"></textarea>
</FormGroup>
</form>
</div>
<div class="modal-body">
<form>
<FormGroup label="Name">
<input type="text" class="form-control" placeholder="Task name" />
</FormGroup>
<FormGroup label="Assigned To">
<select class="form-select">
<option value="">Select person</option>
{selectedPeople.map((person) => <option value={person.id}>{person.full_name}</option>)}
</select>
</FormGroup>
<FormGroup label="Priority">
<select class="form-select">
<option value="Low">Low</option>
<option value="Medium">Medium</option>
<option value="High">High</option>
</select>
</FormGroup>
<FormGroup label="Description">
<textarea class="form-control" rows="3" placeholder="Task description"></textarea>
</FormGroup>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary ms-auto" data-bs-dismiss="modal">
Save
</button>
</div>
</Modal>
</CaptureModal>
<div class="modal-footer">
<button type="button" class="btn" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary ms-auto" data-bs-dismiss="modal"> Save </button>
</div>
</Modal>
</CaptureModal>
</DefaultLayout>
+73 -59
View File
@@ -1,83 +1,97 @@
---
// Right column renders headings h1..h6 with dynamic tag names. Homepage URL from site.json.
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import Prose from '@ui/Prose.astro';
import siteData from '@data/site.json';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import Prose from '@ui/Prose.astro'
import siteData from '@data/site.json'
const homepage = siteData.homepage;
const homepage = siteData.homepage
---
<DefaultLayout title="Text features" pageHeader="Text features" pageMenu="extra.text-features">
<div class="row">
<div class="col-7">
<div class="card card-lg">
<div class="card-body">
<Prose>
<h3>Text features</h3>
<div class="row">
<div class="col-7">
<div class="card card-lg">
<div class="card-body">
<Prose>
<h3>Text features</h3>
<p>HTML provides various tags to format text and add meaning. For example, <strong>important words</strong> can be highlighted, and <em>emphasized text</em> can be italicized.</p>
<p>HTML provides various tags to format text and add meaning. For example, <strong>important words</strong> can be highlighted, and <em>emphasized text</em> can be italicized.</p>
<p>If you want to visit an interesting website, check out <a href={homepage} target="_blank">this page</a>.</p>
<p>If you want to visit an interesting website, check out <a href={homepage} target="_blank">this page</a>.</p>
<p>The term <abbr data-bs-toggle="tooltip" data-bs-placement="top" title="Hypertext Markup Language">HTML</abbr> is widely used in web development.</p>
<p>The term <abbr data-bs-toggle="tooltip" data-bs-placement="top" title="Hypertext Markup Language">HTML</abbr> is widely used in web development.</p>
<p>Previously, the instruction said <del>"Do not include images."</del> However, <ins>"You may now add images."</ins></p>
<p>Previously, the instruction said <del>"Do not include images."</del> However, <ins>"You may now add images."</ins></p>
<blockquote cite={homepage}>"The best way to predict the future is to create it." Peter Drucker</blockquote>
<blockquote cite={homepage}>"The best way to predict the future is to create it." Peter Drucker</blockquote>
<p>Sometimes, <mark>highlighting important text</mark> can improve readability.</p>
<p>Sometimes, <mark>highlighting important text</mark> can improve readability.</p>
<p>In JavaScript, you can log messages using the following code: <code>console.log('Hello, world!');</code></p>
<p>In JavaScript, you can log messages using the following code: <code>console.log('Hello, world!');</code></p>
<p>To copy text on Windows, use <kbd>Ctrl + C</kbd>. On macOS, use <kbd>Cmd + C</kbd>.</p>
<p>To copy text on Windows, use <kbd>Ctrl + C</kbd>. On macOS, use <kbd>Cmd + C</kbd>.</p>
<p>Water is written chemically as H<sub>2</sub>O, while Einsteins famous equation is E = mc<sup>2</sup>.</p>
<p>Water is written chemically as H<sub>2</sub>O, while Einsteins famous equation is E = mc<sup>2</sup>.</p>
<p>Many people mistakenly spell <span class="text-incorrect">"recieve"</span> instead of <span class="text-correct">"receive"</span>.</p>
<p>Many people mistakenly spell <span class="text-incorrect">"recieve"</span> instead of <span class="text-correct">"receive"</span>.</p>
<p>The correct way to write the date format is <span class="text-correct">"February 12, 2026"</span>, not <span class="text-incorrect">"12th February, 2026"</span> in American English.</p>
<p>The correct way to write the date format is <span class="text-correct">"February 12, 2026"</span>, not <span class="text-incorrect">"12th February, 2026"</span> in American English.</p>
<p>
If you need select text, you can use your mouse or keyboard. To select text using your mouse, click and drag the cursor over the text <span class="text-selected">you want to highlight</span>.
</p>
<p>
If you need select text, you can use your mouse or keyboard. To select text using your mouse, click and drag the cursor over the text <span class="text-selected">you want to highlight</span>.
</p>
<p><small>Disclaimer: This text is for demonstration purposes only.</small></p>
</Prose>
</div>
</div>
</div>
<div class="col">
<div class="card card-lg">
<div class="card-body">
<Prose>
{
[1, 2, 3, 4, 5, 6].map((i) => {
const Heading = `h${i}`;
return (
<Heading>
Heading {i} by <a class="mention"><span class="mention-avatar" style="background-image: url(/static/avatars/035f.jpg)"></span><span class="visually-hidden">@</span>JohnDoe</a>
</Heading>
);
})
}
<p><small>Disclaimer: This text is for demonstration purposes only.</small></p>
</Prose>
</div>
</div>
</div>
<div class="col">
<div class="card card-lg">
<div class="card-body">
<Prose>
{
[1, 2, 3, 4, 5, 6].map((i) => {
const Heading = `h${i}`
return (
<Heading>
Heading {i} by{' '}
<a class="mention">
<>
<span class="mention-avatar" style="background-image: url(/static/avatars/035f.jpg)" />
<span class="visually-hidden">@</span>
</>
JohnDoe
</a>
</Heading>
)
})
}
<p>
Tabler is a modern UI framework which <span class="text-incorrect" data-bs-toggle="tooltip" data-bs-placement="top" title="Tooltip on top">provide</span> developers with a lot of <span class="text-incorrect">pre-build</span> components and customizable options. It is
<span class="text-incorrect">build</span> on Bootstrap, making it easy to integrate into existing projects. The design is clean, responsive, and accessible, ensuring that <span class="text-incorrect">user</span> can navigate
through <span class="text-incorrect">interface</span> easily. Tabler also <span class="text-incorrect">support</span> all modern browsers, but some features may not work properly on Internet Explorer. With
<span class="text-incorrect">it's</span> lightweight structure and optimized performance, Tabler helps developers create stunning web applications faster.
</p>
<p>
Tabler is a modern UI framework which <span class="text-incorrect" data-bs-toggle="tooltip" data-bs-placement="top" title="Tooltip on top">provide</span> developers with a lot of <span class="text-incorrect">pre-build</span> components and customizable options. It is
<span class="text-incorrect">build</span> on Bootstrap, making it easy to integrate into existing projects. The design is clean, responsive, and accessible, ensuring that <span class="text-incorrect">user</span> can navigate through <span class="text-incorrect">interface</span> easily. Tabler also <span
class="text-incorrect">support</span
> all modern browsers, but some features may not work properly on Internet Explorer. With
<span class="text-incorrect">it's</span> lightweight structure and optimized performance, Tabler helps developers create stunning web applications faster.
</p>
<p>Hey <a class="mention"><span class="mention-avatar" style="background-image: url(/static/avatars/035f.jpg)"></span><span class="visually-hidden">@</span>JohnDoe</a>, have you seen the latest updates on <a class="mention">#WebDevelopment<span class="mention-count">16</span></a>? <a class="mention"><span class="mention-avatar" style="background-image: url(/static/avatars/035f.jpg)"></span><span class="visually-hidden">@</span>JaneSmith</a> just shared an interesting article about <a class="mention"><span class="mention-app" style="background-image: url(/static/brands/messenger.svg)"></span>Messenger</a> and <a class="mention"><span class="mention-app" style="background-image: url(/static/brands/netflix.svg)"></span>Netflix</a>!</p>
<p>
Hey <a class="mention"><span class="mention-avatar" style="background-image: url(/static/avatars/035f.jpg)"></span><span class="visually-hidden">@</span>JohnDoe</a>, have you seen the latest updates on <a class="mention">#WebDevelopment<span class="mention-count">16</span></a>? <a class="mention"
><span class="mention-avatar" style="background-image: url(/static/avatars/035f.jpg)"></span><span class="visually-hidden">@</span>JaneSmith</a
> just shared an interesting article about <a class="mention"><span class="mention-app" style="background-image: url(/static/brands/messenger.svg)"></span>Messenger</a> and <a class="mention"><span class="mention-app" style="background-image: url(/static/brands/netflix.svg)"></span>Netflix</a>!
</p>
<p>
The sky is <span class="mention"><span class="mention-color bg-blue"></span>#066fd1</span>, the grass is <span class="mention"><span class="mention-color bg-green"></span>rgb(47, 179, 68)</span>, fire trucks are often <span class="mention"><span class="mention-color bg-red"></span>red</span>, oranges are <span class="mention"><span class="mention-color bg-orange"></span>hsl(24deg, 94.49%, 49.8%)</span>. Some flowers are <span class="mention"><span class="mention-color bg-purple"></span>hwb(288.35deg, 24.31%, 21.18%)</span>.
</p>
<p>
The sky is <span class="mention"><span class="mention-color bg-blue"></span>#066fd1</span>, the grass is <span class="mention"><span class="mention-color bg-green"></span>rgb(47, 179, 68)</span>, fire trucks are often <span class="mention"><span class="mention-color bg-red"></span>red</span>, oranges are <span
class="mention"><span class="mention-color bg-orange"></span>hsl(24deg, 94.49%, 49.8%)</span
>. Some flowers are <span class="mention"><span class="mention-color bg-purple"></span>hwb(288.35deg, 24.31%, 21.18%)</span>.
</p>
<hr />
</Prose>
</div>
</div>
</div>
</div>
<hr />
</Prose>
</div>
</div>
</div>
</div>
</DefaultLayout>
+212 -221
View File
@@ -1,231 +1,222 @@
---
import CardActions from '@ui/CardActions.astro';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import CaptureScript from '@shared/components/CaptureScript.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 CardTitle from '@ui/CardTitle.astro';
import Badge from '@ui/Badge.astro';
import Icon from '@ui/Icon.astro';
import CardActions from '@ui/CardActions.astro'
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import CaptureScript from '@shared/components/CaptureScript.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 CardTitle from '@ui/CardTitle.astro'
import Badge from '@ui/Badge.astro'
import Icon from '@ui/Icon.astro'
---
<DefaultLayout
title="Driver Tour"
pageHeader="Driver Tour"
pageMenu="plugins.tour"
pageLibs={['driver.js']}
>
<div class="row row-cards">
<div class="col-12">
<Card>
<CardHeader title="Product Tour Example">
<CardActions>
<Button id="start-tour" text="Start Tour" icon="play" color="primary" element="button" />
</CardActions>
</CardHeader>
<CardBody>
<p>
Click the "Start Tour" button to begin an interactive tour of this page.
The tour will guide you through different elements and features.
</p>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card id="tour-card-1">
<CardHeader title="Welcome Section" />
<CardBody>
<p>This is the first card in our tour. It demonstrates how Driver.js highlights elements on the page.</p>
<ButtonList>
<Button text="Action Button" color="primary" element="button" />
<Button text="Secondary" element="button" />
</ButtonList>
</CardBody>
</Card>
</div>
<DefaultLayout title="Driver Tour" pageHeader="Driver Tour" pageMenu="plugins.tour" pageLibs={['driver.js']}>
<div class="row row-cards">
<div class="col-12">
<Card>
<CardHeader title="Product Tour Example">
<CardActions>
<Button id="start-tour" text="Start Tour" icon="play" color="primary" element="button" />
</CardActions>
</CardHeader>
<CardBody>
<p>Click the "Start Tour" button to begin an interactive tour of this page. The tour will guide you through different elements and features.</p>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card id="tour-card-1">
<CardHeader title="Welcome Section" />
<CardBody>
<p>This is the first card in our tour. It demonstrates how Driver.js highlights elements on the page.</p>
<ButtonList>
<Button text="Action Button" color="primary" element="button" />
<Button text="Secondary" element="button" />
</ButtonList>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card id="tour-card-2">
<CardHeader title="Features Section" />
<CardBody>
<p>This card shows additional features and demonstrates the tour's ability to navigate between different
elements.</p>
<div class="form-selectgroup">
<label class="form-selectgroup-item">
<input type="radio" name="options" value="1" class="form-selectgroup-input" checked>
<span class="form-selectgroup-label">Option 1</span>
</label>
<label class="form-selectgroup-item">
<input type="radio" name="options" value="2" class="form-selectgroup-input">
<span class="form-selectgroup-label">Option 2</span>
</label>
</div>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card id="tour-card-3">
<CardHeader title="Navigation Example" />
<CardBody>
<p>This is a full-width card that demonstrates how the tour works with larger elements.</p>
<div class="table-responsive">
<table class="table table-vcenter">
<thead>
<tr>
<th>Name</th>
<th>Status</th>
<th>Role</th>
<th class="w-1"></th>
</tr>
</thead>
<tbody>
<tr>
<td>John Doe</td>
<td><Badge text="Active" color="success" /></td>
<td>Developer</td>
<td>
<Button text="Edit" size="sm" href="#" />
</td>
</tr>
<tr>
<td>Jane Smith</td>
<td><Badge text="Pending" color="warning" /></td>
<td>Designer</td>
<td>
<Button text="Edit" size="sm" href="#" />
</td>
</tr>
</tbody>
</table>
</div>
</CardBody>
</Card>
</div>
<div class="col-md-6">
<Card id="tour-card-2">
<CardHeader title="Features Section" />
<CardBody>
<p>This card shows additional features and demonstrates the tour's ability to navigate between different elements.</p>
<div class="form-selectgroup">
<label class="form-selectgroup-item">
<input type="radio" name="options" value="1" class="form-selectgroup-input" checked />
<span class="form-selectgroup-label">Option 1</span>
</label>
<label class="form-selectgroup-item">
<input type="radio" name="options" value="2" class="form-selectgroup-input" />
<span class="form-selectgroup-label">Option 2</span>
</label>
</div>
</CardBody>
</Card>
</div>
<div class="col-12">
<Card id="tour-card-3">
<CardHeader title="Navigation Example" />
<CardBody>
<p>This is a full-width card that demonstrates how the tour works with larger elements.</p>
<div class="table-responsive">
<table class="table table-vcenter">
<thead>
<tr>
<th>Name</th>
<th>Status</th>
<th>Role</th>
<th class="w-1"></th>
</tr>
</thead>
<tbody>
<tr>
<td>John Doe</td>
<td><Badge text="Active" color="success" /></td>
<td>Developer</td>
<td>
<Button text="Edit" size="sm" href="#" />
</td>
</tr>
<tr>
<td>Jane Smith</td>
<td><Badge text="Pending" color="warning" /></td>
<td>Designer</td>
<td>
<Button text="Edit" size="sm" href="#" />
</td>
</tr>
</tbody>
</table>
</div>
</CardBody>
</Card>
</div>
<div class="col-md-4">
<Card id="tour-card-4">
<CardBody class="text-center">
<div class="mb-3">
<Icon name="settings" size="48" />
</div>
<CardTitle>Settings</CardTitle>
<p class="text-secondary">Configure your application settings here.</p>
</CardBody>
</Card>
</div>
<div class="col-md-4">
<Card id="tour-card-4">
<CardBody class="text-center">
<div class="mb-3">
<Icon name="settings" size="48" />
</div>
<CardTitle>Settings</CardTitle>
<p class="text-secondary">Configure your application settings here.</p>
</CardBody>
</Card>
</div>
<div class="col-md-4">
<Card id="tour-card-5">
<CardBody class="text-center">
<div class="mb-3">
<Icon name="users" size="48" />
</div>
<CardTitle>Users</CardTitle>
<p class="text-secondary">Manage your team members and permissions.</p>
</CardBody>
</Card>
</div>
<div class="col-md-4">
<Card id="tour-card-5">
<CardBody class="text-center">
<div class="mb-3">
<Icon name="users" size="48" />
</div>
<CardTitle>Users</CardTitle>
<p class="text-secondary">Manage your team members and permissions.</p>
</CardBody>
</Card>
</div>
<div class="col-md-4">
<Card id="tour-card-6">
<CardBody class="text-center">
<div class="mb-3">
<Icon name="chart-bar" size="48" />
</div>
<CardTitle>Analytics</CardTitle>
<p class="text-secondary">View your application statistics and reports.</p>
</CardBody>
</Card>
</div>
</div>
<div class="col-md-4">
<Card id="tour-card-6">
<CardBody class="text-center">
<div class="mb-3">
<Icon name="chart-bar" size="48" />
</div>
<CardTitle>Analytics</CardTitle>
<p class="text-secondary">View your application statistics and reports.</p>
</CardBody>
</Card>
</div>
</div>
<CaptureScript>
<!-- BEGIN TOUR SCRIPT -->
<script is:inline>
document.addEventListener('DOMContentLoaded', function () {
const driverObj = driver.js.driver({
allowClose: true,
overlayClickNext: true,
showButtons: ['next', 'previous', 'close'],
showProgress: true,
steps: [
{
element: '#start-tour',
popover: {
title: 'Welcome to the Tour!',
description: 'This button starts the interactive tour. Click it to begin exploring the page.',
side: 'bottom',
align: 'start'
}
},
{
element: '#tour-card-1',
popover: {
title: 'Welcome Section',
description: 'This is the first card in our tour. It demonstrates how Driver.js highlights elements on the page.',
side: 'right',
align: 'start'
}
},
{
element: '#tour-card-2',
popover: {
title: 'Features Section',
description: 'This card shows additional features and demonstrates the tour\'s ability to navigate between different elements.',
side: 'left',
align: 'start'
}
},
{
element: '#tour-card-3',
popover: {
title: 'Data Table',
description: 'This table shows how Driver.js works with larger elements. You can highlight entire sections of your page.',
side: 'top',
align: 'center'
}
},
{
element: '#tour-card-4',
popover: {
title: 'Settings Card',
description: 'This card demonstrates how the tour works with smaller, centered elements.',
side: 'top',
align: 'center'
}
},
{
element: '#tour-card-5',
popover: {
title: 'Users Card',
description: 'Another example card showing user management features.',
side: 'top',
align: 'center'
}
},
{
element: '#tour-card-6',
popover: {
title: 'Analytics Card',
description: 'The final step of the tour. This card shows analytics features.',
side: 'top',
align: 'center'
}
}
]
});
<CaptureScript>
<!-- BEGIN TOUR SCRIPT -->
<script is:inline>
document.addEventListener('DOMContentLoaded', function () {
const driverObj = driver.js.driver({
allowClose: true,
overlayClickNext: true,
showButtons: ['next', 'previous', 'close'],
showProgress: true,
steps: [
{
element: '#start-tour',
popover: {
title: 'Welcome to the Tour!',
description: 'This button starts the interactive tour. Click it to begin exploring the page.',
side: 'bottom',
align: 'start',
},
},
{
element: '#tour-card-1',
popover: {
title: 'Welcome Section',
description: 'This is the first card in our tour. It demonstrates how Driver.js highlights elements on the page.',
side: 'right',
align: 'start',
},
},
{
element: '#tour-card-2',
popover: {
title: 'Features Section',
description: "This card shows additional features and demonstrates the tour's ability to navigate between different elements.",
side: 'left',
align: 'start',
},
},
{
element: '#tour-card-3',
popover: {
title: 'Data Table',
description: 'This table shows how Driver.js works with larger elements. You can highlight entire sections of your page.',
side: 'top',
align: 'center',
},
},
{
element: '#tour-card-4',
popover: {
title: 'Settings Card',
description: 'This card demonstrates how the tour works with smaller, centered elements.',
side: 'top',
align: 'center',
},
},
{
element: '#tour-card-5',
popover: {
title: 'Users Card',
description: 'Another example card showing user management features.',
side: 'top',
align: 'center',
},
},
{
element: '#tour-card-6',
popover: {
title: 'Analytics Card',
description: 'The final step of the tour. This card shows analytics features.',
side: 'top',
align: 'center',
},
},
],
})
const startButton = document.getElementById('start-tour');
if (startButton) {
startButton.addEventListener('click', function () {
driverObj.drive();
});
}
});
</script>
<!-- END TOUR SCRIPT -->
</CaptureScript>
const startButton = document.getElementById('start-tour')
if (startButton) {
startButton.addEventListener('click', function () {
driverObj.drive()
})
}
})
</script>
<!-- END TOUR SCRIPT -->
</CaptureScript>
</DefaultLayout>
+45 -57
View File
@@ -1,69 +1,57 @@
---
// progress and online_counter assigned per person but never rendered — omitted.
// rounded=true on avatar has no effect — Avatar has no `rounded` prop.
import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
import Avatar from '@ui/Avatar.astro';
import Card from '@ui/Card.astro';
import CardBody from '@ui/CardBody.astro';
import Icon from '@ui/Icon.astro';
import Pagination from '@ui/Pagination.astro';
import people from '@data/people.json';
import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
import Avatar from '@ui/Avatar.astro'
import Card from '@ui/Card.astro'
import CardBody from '@ui/CardBody.astro'
import Icon from '@ui/Icon.astro'
import Pagination from '@ui/Pagination.astro'
import people from '@data/people.json'
interface Person {
full_name?: string;
job_title?: string;
photo?: string;
[key: string]: unknown;
full_name?: string
job_title?: string
photo?: string
[key: string]: unknown
}
const users = (people as Person[]).slice(0, 18);
const users = (people as Person[]).slice(0, 18)
---
<DefaultLayout
title="Users list"
pageHeader="Users"
actions="users"
description="1-18 of 413 people"
pageMenu="extra.users"
>
<div class="row row-cards">
{
users.map((person, idx) => {
const index = idx + 1;
return (
<div class="col-md-6 col-lg-3">
<Card>
<CardBody class="p-4 text-center">
<Avatar size="xl" person={person} class="mb-3" />
<h3 class="m-0 mb-1">
<a href="#">{person.full_name}</a>
</h3>
<div class="text-secondary">{person.job_title}</div>
<DefaultLayout title="Users list" pageHeader="Users" actions="users" description="1-18 of 413 people" pageMenu="extra.users">
<div class="row row-cards">
{
users.map((person, idx) => {
const index = idx + 1
return (
<div class="col-md-6 col-lg-3">
<Card>
<CardBody class="p-4 text-center">
<Avatar size="xl" person={person} class="mb-3" />
<h3 class="m-0 mb-1">
<a href="#">{person.full_name}</a>
</h3>
<div class="text-secondary">{person.job_title}</div>
<div class="mt-3">
{index === 1 ? (
<span class="badge bg-purple-lt">Owner</span>
) : index < 5 ? (
<span class="badge bg-green-lt">Admin</span>
) : null}
</div>
</CardBody>
<div class="d-flex">
<a href="#" class="card-btn">
<Icon name="mail" color="muted" class="me-2" /> Email
</a>
<a href="#" class="card-btn">
<Icon name="phone" color="muted" class="me-2" /> Call
</a>
</div>
</Card>
</div>
);
})
}
</div>
<div class="mt-3">{index === 1 ? <span class="badge bg-purple-lt">Owner</span> : index < 5 ? <span class="badge bg-green-lt">Admin</span> : null}</div>
</CardBody>
<div class="d-flex">
<a href="#" class="card-btn">
<Icon name="mail" color="muted" class="me-2" /> Email
</a>
<a href="#" class="card-btn">
<Icon name="phone" color="muted" class="me-2" /> Call
</a>
</div>
</Card>
</div>
)
})
}
</div>
<div class="d-flex mt-4">
<Pagination class="ms-auto" />
</div>
<div class="d-flex mt-4">
<Pagination class="ms-auto" />
</div>
</DefaultLayout>
+2 -2
View File
@@ -2,7 +2,7 @@
// Captures slot markup (typically a modal) into page-modals; BaseLayout emits
// it at the end of <body> via <PageModals />.
// Registration is synchronous (a promise) — see page-modals.ts.
import { addPageModal } from '@shared/lib/page-modals';
import { addPageModal } from '@shared/lib/page-modals'
addPageModal(Astro.slots.render('default'));
addPageModal(Astro.slots.render('default'))
---
+2 -2
View File
@@ -2,7 +2,7 @@
// Captures slot markup (typically <script>) into page-scripts; BaseLayout /
// DocsLayout emit it at the end of <body> via <PageScripts />.
// Registration is synchronous (a promise) — see page-scripts.ts.
import { addPageScript } from '@shared/lib/page-scripts';
import { addPageScript } from '@shared/lib/page-scripts'
addPageScript(Astro.slots.render('default'));
addPageScript(Astro.slots.render('default'))
---
+2 -2
View File
@@ -2,9 +2,9 @@
// Separate component so drainPageScripts() executes only at this point of the
// render stream (after the page content render has STARTED — registrations are
// synchronous, script content may be a promise).
import { drainPageScripts } from '@shared/lib/page-scripts';
import { drainPageScripts } from '@shared/lib/page-scripts'
const scripts = await Promise.all(drainPageScripts());
const scripts = await Promise.all(drainPageScripts())
---
{scripts.length > 0 && <Fragment set:html={scripts.join('\n')} />}
+21 -21
View File
@@ -1,30 +1,30 @@
---
import Button from '@ui/Button.astro';
import Avatar from '@ui/Avatar.astro';
import CardTitle from '@ui/CardTitle.astro';
import people from '@data/people.json';
import Button from '@ui/Button.astro'
import Avatar from '@ui/Avatar.astro'
import CardTitle from '@ui/CardTitle.astro'
import people from '@data/people.json'
const person = people[0];
const person = people[0]
---
<form class="card card-md" action="./" method="get" autocomplete="off" novalidate>
<div class="card-body text-center">
<div class="mb-4">
<CardTitle as="h2">Account Locked</CardTitle>
<p class="text-secondary">Please enter your password to unlock your account</p>
</div>
<div class="card-body text-center">
<div class="mb-4">
<CardTitle as="h2">Account Locked</CardTitle>
<p class="text-secondary">Please enter your password to unlock your account</p>
</div>
<div class="mb-4">
<Avatar person={person} size="xl" class="mb-3" />
<h3>{person.full_name}</h3>
</div>
<div class="mb-4">
<Avatar person={person} size="xl" class="mb-3" />
<h3>{person.full_name}</h3>
</div>
<div class="mb-4">
<input type="password" class="form-control" placeholder="Password&hellip;" />
</div>
<div class="mb-4">
<input type="password" class="form-control" placeholder="Password" />
</div>
<div>
<Button color="primary" block icon="lock-open" text="Unlock" />
</div>
</div>
<div>
<Button color="primary" block icon="lock-open" text="Unlock" />
</div>
</div>
</form>
@@ -1,15 +1,12 @@
---
import CardStamp from '@ui/CardStamp.astro';
import CardTitle from '@ui/CardTitle.astro';
import CardStamp from '@ui/CardStamp.astro'
import CardTitle from '@ui/CardTitle.astro'
---
<div class="card bg-primary text-primary-fg">
<CardStamp icon="star" color="white" textColor="primary" />
<div class="card-body">
<CardTitle>Card with background and icon</CardTitle>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto at consectetur culpa ducimus eum fuga fugiat,
ipsa iusto, modi nostrum recusandae reiciendis saepe.
</p>
</div>
<CardStamp icon="star" color="white" textColor="primary" />
<div class="card-body">
<CardTitle>Card with background and icon</CardTitle>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto at consectetur culpa ducimus eum fuga fugiat, ipsa iusto, modi nostrum recusandae reiciendis saepe.</p>
</div>
</div>
+19 -22
View File
@@ -1,32 +1,29 @@
---
import photos from '@data/photos.json';
import photos from '@data/photos.json'
interface Props {
title?: string;
right?: boolean;
imgId?: number;
title?: string
right?: boolean
imgId?: number
}
const { title, right, imgId = 1 } = Astro.props;
const { title, right, imgId = 1 } = Astro.props
const photo = (photos as { file: string; title: string }[])[imgId];
const imgClass = `w-100 h-100 object-cover ${right ? 'card-img-end' : 'card-img-start'}`;
const photo = (photos as { file: string; title: string }[])[imgId]
const imgClass = `w-100 h-100 object-cover ${right ? 'card-img-end' : 'card-img-start'}`
---
<div class="card">
<div class="row row-0">
<div class={`col-3${right ? ' order-md-last' : ''}`}>
<!-- Photo -->
<img src={`./static/photos/${photo.file}`} class={imgClass} alt={photo.title} />
</div>
<div class="col">
<div class="card-body">
{title && <h3 class="card-title" set:html={title} />}
<p class="text-secondary">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam deleniti fugit incidunt, iste, itaque minima
neque pariatur perferendis sed suscipit velit vitae voluptatem.
</p>
</div>
</div>
</div>
<div class="row row-0">
<div class={`col-3${right ? ' order-md-last' : ''}`}>
<!-- Photo -->
<img src={`./static/photos/${photo.file}`} class={imgClass} alt={photo.title} />
</div>
<div class="col">
<div class="card-body">
{title && <h3 class="card-title" set:html={title} />}
<p class="text-secondary">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam deleniti fugit incidunt, iste, itaque minima neque pariatur perferendis sed suscipit velit vitae voluptatem.</p>
</div>
</div>
</div>
</div>
+6 -9
View File
@@ -1,14 +1,11 @@
---
import CardTitle from '@ui/CardTitle.astro';
import CardTitle from '@ui/CardTitle.astro'
---
<div class="card">
<div class="ribbon bg-red">NEW</div>
<div class="card-body">
<CardTitle>Card with text ribbon</CardTitle>
<p class="text-secondary">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto at consectetur culpa ducimus eum fuga fugiat,
ipsa iusto, modi nostrum recusandae reiciendis saepe.
</p>
</div>
<div class="ribbon bg-red">NEW</div>
<div class="card-body">
<CardTitle>Card with text ribbon</CardTitle>
<p class="text-secondary">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto at consectetur culpa ducimus eum fuga fugiat, ipsa iusto, modi nostrum recusandae reiciendis saepe.</p>
</div>
</div>
+7 -10
View File
@@ -1,15 +1,12 @@
---
import Icon from '@ui/Icon.astro';
import CardTitle from '@ui/CardTitle.astro';
import Icon from '@ui/Icon.astro'
import CardTitle from '@ui/CardTitle.astro'
---
<div class="card">
<div class="ribbon ribbon-top bg-yellow"><Icon name="star" /></div>
<div class="card-body">
<CardTitle>Card with top ribbon</CardTitle>
<p class="text-secondary">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto at consectetur culpa ducimus eum fuga fugiat,
ipsa iusto, modi nostrum recusandae reiciendis saepe.
</p>
</div>
<div class="ribbon ribbon-top bg-yellow"><Icon name="star" /></div>
<div class="card-body">
<CardTitle>Card with top ribbon</CardTitle>
<p class="text-secondary">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto at consectetur culpa ducimus eum fuga fugiat, ipsa iusto, modi nostrum recusandae reiciendis saepe.</p>
</div>
</div>
+27 -32
View File
@@ -1,33 +1,28 @@
---
// Tabs/content blocks are HTML strings (different order when `bottom`).
interface Props {
/** tabs-count — present but unused */
count?: number;
id?: string;
bottom?: boolean;
borderless?: boolean;
/** tabs-count — present but unused */
count?: number
id?: string
bottom?: boolean
borderless?: boolean
}
const { id = 'top', bottom, borderless } = Astro.props;
const { id = 'top', bottom, borderless } = Astro.props
const tabs = ['Activity', 'Profile', 'Settings'];
const tabs = ['Activity', 'Profile', 'Settings']
const tabsHtml = `
<!-- Cards navigation -->
<ul class="nav nav-tabs${bottom ? ' nav-tabs-bottom' : ''}">
${tabs
.map(
(tab, i) =>
` <li class="nav-item"><a href="#tab-${id}-${i + 1}" class="nav-link${i === 0 ? ' active' : ''}" data-bs-toggle="tab">${tab}</a></li>`,
)
.join('\n')}
</ul>`;
${tabs.map((tab, i) => ` <li class="nav-item"><a href="#tab-${id}-${i + 1}" class="nav-link${i === 0 ? ' active' : ''}" data-bs-toggle="tab">${tab}</a></li>`).join('\n')}
</ul>`
const tabsContentHtml = `
<div class="tab-content">
${tabs
.map(
(tab, i) => ` <!-- Content of card #${tab} -->
.map(
(tab, i) => ` <!-- Content of card #${tab} -->
<div id="tab-${id}-${i + 1}" class="card tab-pane${i === 0 ? ' active show' : ''}">
<div class="card-body">
<div class="card-title">${tab}</div>
@@ -36,24 +31,24 @@ ${tabs
</p>
</div>
</div>`,
)
.join('\n')}
</div>`;
)
.join('\n')}
</div>`
---
<!-- Cards with tabs component -->
<div class={`card-tabs${borderless ? ' border-0' : ''}`}>
{
bottom ? (
<Fragment>
<Fragment set:html={tabsContentHtml} />
<Fragment set:html={tabsHtml} />
</Fragment>
) : (
<Fragment>
<Fragment set:html={tabsHtml} />
<Fragment set:html={tabsContentHtml} />
</Fragment>
)
}
{
bottom ? (
<Fragment>
<Fragment set:html={tabsContentHtml} />
<Fragment set:html={tabsHtml} />
</Fragment>
) : (
<Fragment>
<Fragment set:html={tabsHtml} />
<Fragment set:html={tabsContentHtml} />
</Fragment>
)
}
</div>
+66 -89
View File
@@ -1,103 +1,80 @@
---
// Photos filtered to horizontal=true, then sliced by offset/limit.
import photos from '@data/photos.json';
import CardTitle from '@ui/CardTitle.astro';
import photos from '@data/photos.json'
import CardTitle from '@ui/CardTitle.astro'
interface Props {
id?: string;
title?: string;
indicators?: boolean;
indicatorsThumb?: boolean;
indicatorsThumbRatio?: boolean;
indicatorsDot?: boolean;
indicatorsVertical?: boolean;
controls?: boolean;
captions?: boolean;
offset?: number;
limit?: number;
fade?: boolean;
id?: string
title?: string
indicators?: boolean
indicatorsThumb?: boolean
indicatorsThumbRatio?: boolean
indicatorsDot?: boolean
indicatorsVertical?: boolean
controls?: boolean
captions?: boolean
offset?: number
limit?: number
fade?: boolean
}
const {
id: idProp,
title = 'Carousel',
indicators,
indicatorsThumb,
indicatorsThumbRatio,
indicatorsDot,
indicatorsVertical,
controls,
captions,
offset = 0,
limit = 5,
fade,
} = Astro.props;
const { id: idProp, title = 'Carousel', indicators, indicatorsThumb, indicatorsThumbRatio, indicatorsDot, indicatorsVertical, controls, captions, offset = 0, limit = 5, fade } = Astro.props
const carouselId = idProp ?? 'carousel';
const filteredPhotos = (photos as { file: string; horizontal?: boolean }[]).filter((p) => p.horizontal);
const slides = filteredPhotos.slice(offset, offset + limit);
const carouselId = idProp ?? 'carousel'
const filteredPhotos = (photos as { file: string; horizontal?: boolean }[]).filter((p) => p.horizontal)
const slides = filteredPhotos.slice(offset, offset + limit)
---
<div class="card">
<div class="card-header">
<CardTitle>{title}</CardTitle>
</div>
<div class="card-body">
<div id={`carousel-${carouselId}`} class={`carousel slide${fade ? ' carousel-fade' : ''}`} data-bs-ride="carousel">
{
indicators && (
<div
class={`carousel-indicators${indicatorsVertical ? ' carousel-indicators-vertical' : ''}${
indicatorsDot ? ' carousel-indicators-dot' : indicatorsThumb ? ' carousel-indicators-thumb' : ''
}`}
>
{slides.map((photo, i) => (
<button
type="button"
data-bs-target={`#carousel-${carouselId}`}
data-bs-slide-to={`${i}`}
class={`${indicatorsThumbRatio ? ' ratio ratio-4x3' : ''}${i === 0 ? ' active' : ''}`}
style={indicatorsThumb ? `background-image: url(./static/photos/${photo.file})` : undefined}
/>
))}
</div>
)
}
<div class="card-header">
<CardTitle>{title}</CardTitle>
</div>
<div class="card-body">
<div id={`carousel-${carouselId}`} class={`carousel slide${fade ? ' carousel-fade' : ''}`} data-bs-ride="carousel">
{
indicators && (
<div class={`carousel-indicators${indicatorsVertical ? ' carousel-indicators-vertical' : ''}${indicatorsDot ? ' carousel-indicators-dot' : indicatorsThumb ? ' carousel-indicators-thumb' : ''}`}>
{slides.map((photo, i) => (
<button type="button" data-bs-target={`#carousel-${carouselId}`} data-bs-slide-to={`${i}`} class={`${indicatorsThumbRatio ? ' ratio ratio-4x3' : ''}${i === 0 ? ' active' : ''}`} style={indicatorsThumb ? `background-image: url(./static/photos/${photo.file})` : undefined} />
))}
</div>
)
}
<div class="carousel-inner">
{
slides.map((photo, i) => (
<div class={`carousel-item${i === 0 ? ' active' : ''}`}>
<img class="d-block w-100" alt="" src={`./static/photos/${photo.file}`} />
<div class="carousel-inner">
{
slides.map((photo, i) => (
<div class={`carousel-item${i === 0 ? ' active' : ''}`}>
<img class="d-block w-100" alt="" src={`./static/photos/${photo.file}`} />
{captions && (
<Fragment>
<div class="carousel-caption-background d-none d-md-block" />
<div class="carousel-caption d-none d-md-block">
<h3>Slide label</h3>
<p>Nulla vitae elit libero, a pharetra augue mollis interdum.</p>
</div>
</Fragment>
)}
</div>
))
}
</div>
{captions && (
<Fragment>
<div class="carousel-caption-background d-none d-md-block" />
<div class="carousel-caption d-none d-md-block">
<h3>Slide label</h3>
<p>Nulla vitae elit libero, a pharetra augue mollis interdum.</p>
</div>
</Fragment>
)}
</div>
))
}
</div>
{
controls && (
<Fragment>
<a class="carousel-control-prev" href={`#carousel-${carouselId}`} role="button" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true" />
<span class="visually-hidden">Previous</span>
</a>
<a class="carousel-control-next" href={`#carousel-${carouselId}`} role="button" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true" />
<span class="visually-hidden">Next</span>
</a>
</Fragment>
)
}
</div>
</div>
{
controls && (
<Fragment>
<a class="carousel-control-prev" href={`#carousel-${carouselId}`} role="button" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true" />
<span class="visually-hidden">Previous</span>
</a>
<a class="carousel-control-next" href={`#carousel-${carouselId}`} role="button" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true" />
<span class="visually-hidden">Next</span>
</a>
</Fragment>
)
}
</div>
</div>
</div>
+6 -6
View File
@@ -1,6 +1,6 @@
---
// Raw CSS text inside .card-code (no highlight markup).
import CardTitle from '@ui/CardTitle.astro';
import CardTitle from '@ui/CardTitle.astro'
const code = `
.card-footer {
@@ -10,12 +10,12 @@ const code = `
border-radius: 0 0 1 2;
}
}
`;
`
---
<div class="card">
<div class="card-header">
<CardTitle>Card with code</CardTitle>
</div>
<div class="card-code">{code}</div>
<div class="card-header">
<CardTitle>Card with code</CardTitle>
</div>
<div class="card-code">{code}</div>
</div>
+41 -49
View File
@@ -1,58 +1,50 @@
---
import Button from '@ui/Button.astro';
import FormGroup from '@ui/FormGroup.astro';
import Button from '@ui/Button.astro'
import FormGroup from '@ui/FormGroup.astro'
const months = Array.from({ length: 12 }, (_, i) => i + 1);
const years = Array.from({ length: 11 }, (_, i) => 2020 + i);
const months = Array.from({ length: 12 }, (_, i) => i + 1)
const years = Array.from({ length: 11 }, (_, i) => 2020 + i)
---
<div class="card">
<div class="card-body">
<div class="mb-3">
<div class="form-label">Card number</div>
<input
type="text"
name="input-mask"
class="form-control"
data-mask="0000 0000 0000 0000"
data-mask-visible="true"
placeholder="0000 0000 0000 0000"
autocomplete="off"
/>
</div>
<div class="card-body">
<div class="mb-3">
<div class="form-label">Card number</div>
<input type="text" name="input-mask" class="form-control" data-mask="0000 0000 0000 0000" data-mask-visible="true" placeholder="0000 0000 0000 0000" autocomplete="off" />
</div>
<div class="mb-3">
<div class="form-label">Card name</div>
<input type="text" class="form-control" />
</div>
<div class="mb-3">
<div class="form-label">Card name</div>
<input type="text" class="form-control" />
</div>
<div class="row">
<div class="col-8">
<FormGroup label="Expiration date">
<div class="row g-2">
<div class="col">
<select class="form-select">
{months.map((month) => <option value={month}>{month}</option>)}
</select>
</div>
<div class="col">
<select class="form-select">
{years.map((year) => <option value={year}>{year}</option>)}
</select>
</div>
</div>
</FormGroup>
</div>
<div class="col">
<div class="mb-3">
<div class="form-label">CVV</div>
<input type="number" class="form-control" />
</div>
</div>
</div>
<div class="row">
<div class="col-8">
<FormGroup label="Expiration date">
<div class="row g-2">
<div class="col">
<select class="form-select">
{months.map((month) => <option value={month}>{month}</option>)}
</select>
</div>
<div class="col">
<select class="form-select">
{years.map((year) => <option value={year}>{year}</option>)}
</select>
</div>
</div>
</FormGroup>
</div>
<div class="col">
<div class="mb-3">
<div class="form-label">CVV</div>
<input type="number" class="form-control" />
</div>
</div>
</div>
<div class="mt-2">
<Button text="Pay now" color="primary" block />
</div>
</div>
<div class="mt-2">
<Button text="Pay now" color="primary" block />
</div>
</div>
</div>
+40 -47
View File
@@ -1,59 +1,52 @@
---
import Subheader from '@ui/Subheader.astro';
import Icon from '@ui/Icon.astro';
import Trending from '@ui/Trending.astro';
import Subheader from '@ui/Subheader.astro'
import Icon from '@ui/Icon.astro'
import Trending from '@ui/Trending.astro'
interface Props {
color?: string;
icon?: string;
/** lt — light avatar background (bg-{color}-lt) instead of bg-{color} text-white */
lt?: boolean;
title?: string;
description?: string;
/** change-value — passed to Trending (keeps its +/- sign) */
changeValue?: string;
/** change-value-unit — trending unit; defaults to '%' in Trending when unset */
changeValueUnit?: string;
class?: string;
color?: string
icon?: string
/** lt — light avatar background (bg-{color}-lt) instead of bg-{color} text-white */
lt?: boolean
title?: string
description?: string
/** change-value — passed to Trending (keeps its +/- sign) */
changeValue?: string
/** change-value-unit — trending unit; defaults to '%' in Trending when unset */
changeValueUnit?: string
class?: string
}
const {
color,
icon,
lt,
title = '1700',
description = 'Users',
changeValue,
changeValueUnit,
class: className,
} = Astro.props;
const { color, icon, lt, title = '1700', description = 'Users', changeValue, changeValueUnit, class: className } = Astro.props
const avatarClass = [color ? `bg-${color}${lt ? '-lt' : ' text-white'}` : '', 'avatar', 'avatar-square'];
const avatarClass = [color ? `bg-${color}${lt ? '-lt' : ' text-white'}` : '', 'avatar', 'avatar-square']
---
<div class:list={['card card-sm', className]}>
<div class="card-body p-3">
<div class="row align-items-center">
{
icon && (
<div class="col-auto">
<span class:list={avatarClass}><Icon name={icon} /></span>
</div>
)
}
<div class="card-body p-3">
<div class="row align-items-center">
{
icon && (
<div class="col-auto">
<span class:list={avatarClass}>
<Icon name={icon} />
</span>
</div>
)
}
<div class="col">
<Subheader>
{title}
</Subheader>
<div class="h3 m-0 p-0">
{description}
</div>
</div>
<div class="col">
<Subheader>
{title}
</Subheader>
<div class="h3 m-0 p-0">
{description}
</div>
</div>
<div class="col-auto">
<Trending value={changeValue as unknown as number} unit={changeValueUnit} />
</div>
</div>
</div>
<div class="col-auto">
<Trending value={changeValue as unknown as number} unit={changeValueUnit} />
</div>
</div>
</div>
</div>
+13 -13
View File
@@ -1,18 +1,18 @@
---
import AvatarList from '@ui/AvatarList.astro';
import Button from '@ui/Button.astro';
import CardTitle from '@ui/CardTitle.astro';
import AvatarList from '@ui/AvatarList.astro'
import Button from '@ui/Button.astro'
import CardTitle from '@ui/CardTitle.astro'
---
<div class="card card-lg card-dashed card-transparent">
<div class="card-body text-center">
<div class="mb-3">
<AvatarList stacked={true} />
</div>
<CardTitle>No Team Members</CardTitle>
<p class="text-secondary">Invite your team to<br />collaborate on this project.</p>
<div class="mt-4">
<Button text="Invite Members" color="primary" icon="plus" />
</div>
</div>
<div class="card-body text-center">
<div class="mb-3">
<AvatarList stacked={true} />
</div>
<CardTitle>No Team Members</CardTitle>
<p class="text-secondary">Invite your team to<br />collaborate on this project.</p>
<div class="mt-4">
<Button text="Invite Members" color="primary" icon="plus" />
</div>
</div>
</div>
@@ -1,5 +1,4 @@
---
import FormFooter from '@ui/FormFooter.astro'
import Button from '@ui/Button.astro'
import FormGroup from '@ui/FormGroup.astro'
+39 -39
View File
@@ -1,57 +1,57 @@
---
// `photo` and `index` are passed explicitly from the parent loop.
import Avatar from '@ui/Avatar.astro';
import Icon from '@ui/Icon.astro';
import { randomNumber, timeagoLabel } from '@shared/lib/pseudo-random';
import Avatar from '@ui/Avatar.astro'
import Icon from '@ui/Icon.astro'
import { randomNumber, timeagoLabel } from '@shared/lib/pseudo-random'
interface Photo {
file: string;
[key: string]: unknown;
file: string
[key: string]: unknown
}
interface Person {
full_name?: string;
photo?: string;
[key: string]: unknown;
full_name?: string
photo?: string
[key: string]: unknown
}
interface Props {
photo: Photo;
person: Person;
/** 1-based loop index — seeds the deterministic pseudo-random values */
index: number;
hideLikes?: boolean;
photo: Photo
person: Person
/** 1-based loop index — seeds the deterministic pseudo-random values */
index: number
hideLikes?: boolean
}
const { photo, person, index, hideLikes } = Astro.props;
const { photo, person, index, hideLikes } = Astro.props
// Heart filled when (index > 2 && index < 9) || index === 10.
const heartClass = (index > 2 && index < 9) || index === 10 ? 'icon-filled text-red' : undefined;
const heartClass = (index > 2 && index < 9) || index === 10 ? 'icon-filled text-red' : undefined
---
<div class="card card-sm">
<a href="#" class="d-block"><img src={`./static/photos/${photo.file}`} class="card-img-top" /></a>
<div class="card-body">
<div class="d-flex align-items-center">
<Avatar person={person} class="me-3 rounded" />
<div>
<div>{person.full_name}</div>
<div class="text-secondary">{timeagoLabel(index, 10)}</div>
</div>
{
!hideLikes && (
<div class="ms-auto">
<a href="#" class="text-secondary">
<Icon name="eye" />
{randomNumber(index, 300, 600)}
</a>
<a href="#" class="ms-3 text-secondary">
<Icon name="heart" class={heartClass} />
{randomNumber(index, 20, 100)}
</a>
</div>
)
}
</div>
</div>
<a href="#" class="d-block"><img src={`./static/photos/${photo.file}`} class="card-img-top" /></a>
<div class="card-body">
<div class="d-flex align-items-center">
<Avatar person={person} class="me-3 rounded" />
<div>
<div>{person.full_name}</div>
<div class="text-secondary">{timeagoLabel(index, 10)}</div>
</div>
{
!hideLikes && (
<div class="ms-auto">
<a href="#" class="text-secondary">
<Icon name="eye" />
{randomNumber(index, 300, 600)}
</a>
<a href="#" class="ms-3 text-secondary">
<Icon name="heart" class={heartClass} />
{randomNumber(index, 20, 100)}
</a>
</div>
)
}
</div>
</div>
</div>
+28 -39
View File
@@ -1,52 +1,41 @@
---
import Icon from '@ui/Icon.astro';
import icons from '@data/icons.json';
import Icon from '@ui/Icon.astro'
import icons from '@data/icons.json'
interface Props {
title?: string;
type?: 'outline' | 'filled';
title?: string
type?: 'outline' | 'filled'
}
const { title, type = 'outline' } = Astro.props;
const { title, type = 'outline' } = Astro.props
// Dev preview limits icons to 20 (see BaseLayout).
// The limit applies before the svg[type] filter — the "filled" card shows only filled icons among the first 20.
const limit = 20;
const limit = 20
const entries = Object.entries(icons as Record<string, { svg?: Record<string, string> }>).slice(
0,
limit,
);
const entries = Object.entries(icons as Record<string, { svg?: Record<string, string | null> }>).slice(0, limit)
---
<div class="card">
<div class="card-header">
<div class="card-title">{title}</div>
</div>
<div class="card-body p-0">
<div class="demo-icons-list-wrap">
<div class="demo-icons-list">
{
entries.map(
([iconName, icon]) =>
icon.svg?.[type] && (
<a
href={`https://tabler.io/icons/icon/${iconName}`}
target="_blank"
rel="noopener"
class="demo-icons-list-item"
title={iconName}
data-bs-toggle="tooltip"
data-bs-placement="top"
>
<Icon name={iconName} type={type} />
</a>
),
)
}
{/* 21 empty divs (flexbox filler) */}
{Array.from({ length: 21 }).map(() => <div />)}
</div>
</div>
</div>
<div class="card-header">
<div class="card-title">{title}</div>
</div>
<div class="card-body p-0">
<div class="demo-icons-list-wrap">
<div class="demo-icons-list">
{
entries.map(
([iconName, icon]) =>
icon.svg?.[type] && (
<a href={`https://tabler.io/icons/icon/${iconName}`} target="_blank" rel="noopener" class="demo-icons-list-item" title={iconName} data-bs-toggle="tooltip" data-bs-placement="top">
<Icon name={iconName} type={type} />
</a>
),
)
}
{/* 21 empty divs (flexbox filler) */}
{Array.from({ length: 21 }).map(() => <div />)}
</div>
</div>
</div>
</div>
@@ -1,4 +1,5 @@
---
---
<div class="card card-lg">
+35 -39
View File
@@ -1,54 +1,50 @@
---
// Feature names are a fixed list; the `features` string is split per-character into a 1/0 availability mask.
import Icon from '@ui/Icon.astro';
import Icon from '@ui/Icon.astro'
interface Props {
price?: string;
users?: string | number;
category?: string;
features?: string;
/** featured-color — ribbon + button color (e.g. "green") */
featuredColor?: string;
price?: string
users?: string | number
category?: string
features?: string
/** featured-color — ribbon + button color (e.g. "green") */
featuredColor?: string
}
const { price = '79', users = 10, category = 'Enterprise', features = '1000', featuredColor } = Astro.props;
const { price = '79', users = 10, category = 'Enterprise', features = '1000', featuredColor } = Astro.props
const featureNames = ['Sharing Tools', 'Design Tools', 'Private Messages', 'Twitter API'];
const availableFeatures = features.split('');
const featureNames = ['Sharing Tools', 'Design Tools', 'Private Messages', 'Twitter API']
const availableFeatures = features.split('')
---
<div class="card card-md">
{
featuredColor && (
<div class={`ribbon ribbon-top ribbon-bookmark bg-${featuredColor}`}>
<Icon name="star" />
</div>
)
}
{
featuredColor && (
<div class={`ribbon ribbon-top ribbon-bookmark bg-${featuredColor}`}>
<Icon name="star" />
</div>
)
}
<div class="card-body text-center">
<div class="text-uppercase text-secondary fw-medium">{category}</div>
<div class="card-body text-center">
<div class="text-uppercase text-secondary fw-medium">{category}</div>
<div class="display-5 fw-bold my-3">${price}</div>
<div class="display-5 fw-bold my-3">${price}</div>
<ul class="list-unstyled lh-lg">
<li><strong>{users}</strong> Users</li>
{
featureNames.map((feature, i) => (
<li>
{availableFeatures[i] === '1' ? (
<Icon name="check" class="me-1 text-success" />
) : (
<Icon name="x" class="me-1 text-danger" />
)}
{feature}
</li>
))
}
</ul>
<ul class="list-unstyled lh-lg">
<li><strong>{users}</strong> Users</li>
{
featureNames.map((feature, i) => (
<li>
{availableFeatures[i] === '1' ? <Icon name="check" class="me-1 text-success" /> : <Icon name="x" class="me-1 text-danger" />}
{feature}
</li>
))
}
</ul>
<div class="text-center mt-4">
<a href="#" class={`btn${featuredColor ? ` btn-${featuredColor}` : ''} w-100`}>Choose plan</a>
</div>
</div>
<div class="text-center mt-4">
<a href="#" class={`btn${featuredColor ? ` btn-${featuredColor}` : ''} w-100`}>Choose plan</a>
</div>
</div>
</div>
+24 -24
View File
@@ -1,38 +1,38 @@
---
// TODO: fallback to people[1] when person omitted — unused on card-gradients, so `person` is required here.
import Icon from '@ui/Icon.astro';
import Avatar from '@ui/Avatar.astro';
import ButtonList from '@ui/ButtonList.astro';
import Icon from '@ui/Icon.astro'
import Avatar from '@ui/Avatar.astro'
import ButtonList from '@ui/ButtonList.astro'
interface Person {
full_name?: string;
job_title?: string;
[key: string]: unknown;
full_name?: string
job_title?: string
[key: string]: unknown
}
interface Props {
person: Person;
color?: string;
person: Person
color?: string
}
const { person, color = 'yellow' } = Astro.props;
const { person, color = 'yellow' } = Astro.props
---
<div class={`card card-gradient card-gradient-${color}`}>
<div class="card-body text-center py-6">
<div class="position-absolute top-0 end-0 p-1">
<div class="btn btn-action"><Icon name="star" color="yellow" type="filled" /></div>
</div>
<div>
<Avatar size="2xl" person={person} shape="rounded-circle" />
</div>
<div class="h1 mt-4 mb-1">{person.full_name}</div>
<div class="text-secondary">{person.job_title}</div>
<div class="card-body text-center py-6">
<div class="position-absolute top-0 end-0 p-1">
<div class="btn btn-action"><Icon name="star" color="yellow" type="filled" /></div>
</div>
<div>
<Avatar size="2xl" person={person} shape="rounded-circle" />
</div>
<div class="h1 mt-4 mb-1">{person.full_name}</div>
<div class="text-secondary">{person.job_title}</div>
<ButtonList class="justify-content-center mt-3">
<a href="#" class="btn btn-icon btn-pill" title="Message" data-bs-toggle="tooltip" data-bs-placement="top"><Icon name="message" /></a>
<a href="#" class="btn btn-icon btn-pill" title="Phone" data-bs-toggle="tooltip" data-bs-placement="top"><Icon name="phone" /></a>
<a href="#" class="btn btn-icon btn-pill" title="Email" data-bs-toggle="tooltip" data-bs-placement="top"><Icon name="mail" /></a>
</ButtonList>
</div>
<ButtonList class="justify-content-center mt-3">
<a href="#" class="btn btn-icon btn-pill" title="Message" data-bs-toggle="tooltip" data-bs-placement="top"><Icon name="message" /></a>
<a href="#" class="btn btn-icon btn-pill" title="Phone" data-bs-toggle="tooltip" data-bs-placement="top"><Icon name="phone" /></a>
<a href="#" class="btn btn-icon btn-pill" title="Email" data-bs-toggle="tooltip" data-bs-placement="top"><Icon name="mail" /></a>
</ButtonList>
</div>
</div>
+36 -37
View File
@@ -1,49 +1,48 @@
---
// NOTE: widgets page passes `value=` but only `percentage` (default 20) drives the bar — `value` is accepted but ignored.
import Progress from '@ui/Progress.astro';
import AvatarList from '@ui/AvatarList.astro';
import Icon from '@ui/Icon.astro';
import CardTitle from '@ui/CardTitle.astro';
import Progress from '@ui/Progress.astro'
import AvatarList from '@ui/AvatarList.astro'
import Icon from '@ui/Icon.astro'
import CardTitle from '@ui/CardTitle.astro'
interface Props {
title?: string;
badge?: string;
offset?: number;
limit?: number;
percentage?: number | string;
percentageColor?: string;
due?: string;
/** unused — see note above */
value?: number | string;
title?: string
badge?: string
offset?: number
limit?: number
percentage?: number | string
percentageColor?: string
due?: string
/** unused — see note above */
value?: number | string
}
const {
title = 'Task Title',
badge,
offset = 40,
limit = 7,
percentage = 20,
percentageColor = 'green',
due = '2 days',
} = Astro.props;
const { title = 'Task Title', badge, offset = 40, limit = 7, percentage = 20, percentageColor = 'green', due = '2 days' } = Astro.props
---
<div class="card">
<Progress value={percentage} class="card-progress" color={percentageColor} />
<div class="card-body">
<CardTitle>
<a href="#">{title}</a>
{badge && <Fragment> <span class="badge ms-2">{badge}</span></Fragment>}
</CardTitle>
<Progress value={percentage} class="card-progress" color={percentageColor} />
<div class="card-body">
<CardTitle>
<a href="#">{title}</a>
{
badge && (
<Fragment>
{' '}
<span class="badge ms-2">{badge}</span>
</Fragment>
)
}
</CardTitle>
<AvatarList offset={offset} limit={limit} stacked class="mb-3" />
<AvatarList offset={offset} limit={limit} stacked class="mb-3" />
<div class="card-meta d-flex justify-content-between">
<div class="d-flex align-items-center">
<Icon name="check" class="me-2" />
<span>5/10</span>
</div>
<span>Due {due}</span>
</div>
</div>
<div class="card-meta d-flex justify-content-between">
<div class="d-flex align-items-center">
<Icon name="check" class="me-2" />
<span>5/10</span>
</div>
<span>Due {due}</span>
</div>
</div>
</div>
+37 -39
View File
@@ -1,53 +1,51 @@
---
// NOTE: `days-ago` param accepted but unused ("Updated 2 hours ago" is hardcoded).
import Progress from '@ui/Progress.astro';
import CardDropdown from '@ui/CardDropdown.astro';
import projects from '@data/projects.json';
import Progress from '@ui/Progress.astro'
import CardDropdown from '@ui/CardDropdown.astro'
import projects from '@data/projects.json'
interface Project {
title?: string;
image?: string;
[key: string]: unknown;
title?: string
image?: string
[key: string]: unknown
}
interface Props {
projectId?: number;
progress?: number;
daysAgo?: number;
projectId?: number
progress?: number
daysAgo?: number
}
const { projectId = 0, progress = 25 } = Astro.props;
const project = (projects as Project[])[projectId];
const { projectId = 0, progress = 25 } = Astro.props
const project = (projects as Project[])[projectId]
---
<div class="card">
<div class="card-body">
<div class="row align-items-center">
<div class="col-3">
<img src={project.image} alt={project.title} class="rounded" />
</div>
<div class="col">
<h3 class="card-title mb-1">
<a href="#" class="text-reset">{project.title}</a>
</h3>
<div class="text-secondary">
Updated 2 hours ago
</div>
<div class="card-body">
<div class="row align-items-center">
<div class="col-3">
<img src={project.image} alt={project.title} class="rounded" />
</div>
<div class="col">
<h3 class="card-title mb-1">
<a href="#" class="text-reset">{project.title}</a>
</h3>
<div class="text-secondary">Updated 2 hours ago</div>
<div class="mt-3">
<div class="row g-2 align-items-center">
<div class="col-auto">
{progress}%
</div>
<div class="col">
<Progress value={progress} size="sm" />
</div>
</div>
</div>
</div>
<div class="col-auto">
<CardDropdown />
</div>
</div>
</div>
<div class="mt-3">
<div class="row g-2 align-items-center">
<div class="col-auto">
{progress}%
</div>
<div class="col">
<Progress value={progress} size="sm" />
</div>
</div>
</div>
</div>
<div class="col-auto">
<CardDropdown />
</div>
</div>
</div>
</div>
+32 -32
View File
@@ -1,39 +1,39 @@
---
// Icon class is a literal template string (color token is not interpolated).
import CardActions from '@ui/CardActions.astro';
import Icon from '@ui/Icon.astro';
import CardTitle from '@ui/CardTitle.astro';
import crmDashboard from '@data/crm-dashboard.json';
import CardActions from '@ui/CardActions.astro'
import Icon from '@ui/Icon.astro'
import CardTitle from '@ui/CardTitle.astro'
import crmDashboard from '@data/crm-dashboard.json'
const activity = crmDashboard.recent_activity;
const activity = crmDashboard.recent_activity
---
<div class="card" style="height: 28rem">
<div class="card-header">
<CardTitle>{activity.title}</CardTitle>
<CardActions>
<a href="#" class="small fw-medium text-primary">{activity.view_all_text}</a>
</CardActions>
</div>
<div class="card-body card-body-scrollable card-body-scrollable-shadow">
<div class="divide-y">
{
activity.items.map((item) => (
<div class="py-3">
<div class="row align-items-center">
<div class="col-auto">
<span class={`avatar bg-${item.icon_color}-lt`}>
<Icon name={item.icon} class={'icon text-{{ item.icon_color }}'} />
</span>
</div>
<div class="col">
<div class="fw-medium">{item.text}</div>
<div class="small text-secondary">{item.time}</div>
</div>
</div>
</div>
))
}
</div>
</div>
<div class="card-header">
<CardTitle>{activity.title}</CardTitle>
<CardActions>
<a href="#" class="small fw-medium text-primary">{activity.view_all_text}</a>
</CardActions>
</div>
<div class="card-body card-body-scrollable card-body-scrollable-shadow">
<div class="divide-y">
{
activity.items.map((item) => (
<div class="py-3">
<div class="row align-items-center">
<div class="col-auto">
<span class={`avatar bg-${item.icon_color}-lt`}>
<Icon name={item.icon} class={'icon text-{{ item.icon_color }}'} />
</span>
</div>
<div class="col">
<div class="fw-medium">{item.text}</div>
<div class="small text-secondary">{item.time}</div>
</div>
</div>
</div>
))
}
</div>
</div>
</div>
-1
View File
@@ -1,5 +1,4 @@
---
import FormFooter from '@ui/FormFooter.astro'
import InputGroup from '@ui/InputGroup.astro'
import FormGroup from '@ui/FormGroup.astro'
+32 -33
View File
@@ -1,39 +1,38 @@
---
import { formatNumber } from '@shared/lib/string-format';
import CardTitle from '@ui/CardTitle.astro';
import { formatNumber } from '@shared/lib/string-format'
import CardTitle from '@ui/CardTitle.astro'
const services = 'Instagram:3550,Twitter:1798,Facebook:1245,TikTok:986,Pinterest:854,VK:650,Pinterest:420'
.split(',')
.map((service) => {
const [name, visitors] = service.split(':');
return { name, visitors: Number(visitors) };
});
const services = 'Instagram:3550,Twitter:1798,Facebook:1245,TikTok:986,Pinterest:854,VK:650,Pinterest:420'.split(',').map((service) => {
const [name, visitors] = service.split(':')
return { name, visitors: Number(visitors) }
})
---
<div class="card">
<div class="card-header">
<CardTitle>Social Media Traffic</CardTitle>
</div>
<table class="table card-table table-vcenter">
<thead>
<tr>
<th>Network</th>
<th colspan="2">Visitors</th>
</tr>
</thead>
<tbody>
{services.map((service) => (
<tr>
<td>{service.name}</td>
<td>{formatNumber(service.visitors)}</td>
<td class="w-50">
<div class="progress progress-xs">
<div class="progress-bar bg-primary" style={`width: ${(service.visitors / 5000.0) * 100}%`}></div>
</div>
</td>
</tr>
))}
</tbody>
</table>
<div class="card-header">
<CardTitle>Social Media Traffic</CardTitle>
</div>
<table class="table card-table table-vcenter">
<thead>
<tr>
<th>Network</th>
<th colspan="2">Visitors</th>
</tr>
</thead>
<tbody>
{
services.map((service) => (
<tr>
<td>{service.name}</td>
<td>{formatNumber(service.visitors)}</td>
<td class="w-50">
<div class="progress progress-xs">
<div class="progress-bar bg-primary" style={`width: ${(service.visitors / 5000.0) * 100}%`} />
</div>
</td>
</tr>
))
}
</tbody>
</table>
</div>
+94 -83
View File
@@ -1,96 +1,107 @@
---
import Icon from '@ui/Icon.astro';
import DropdownMenu from '@ui/DropdownMenu.astro';
import Icon from '@ui/Icon.astro'
import DropdownMenu from '@ui/DropdownMenu.astro'
interface Props {
id: string;
icons?: boolean;
/** hide-text param; not used by current call sites */
hideText?: boolean;
reverse?: boolean;
justified?: boolean;
activity?: boolean;
disabled?: boolean;
dropdown?: boolean;
settings?: boolean;
animation?: boolean;
id: string
icons?: boolean
/** hide-text param; not used by current call sites */
hideText?: boolean
reverse?: boolean
justified?: boolean
activity?: boolean
disabled?: boolean
dropdown?: boolean
settings?: boolean
animation?: boolean
}
const {
id,
icons,
hideText,
reverse = false,
justified,
activity,
disabled,
dropdown,
settings,
animation,
} = Astro.props;
const { id, icons, hideText, reverse = false, justified, activity, disabled, dropdown, settings, animation } = Astro.props
const iconClass = hideText ? undefined : 'me-2';
const navClass = `nav nav-tabs card-header-tabs${reverse ? ' flex-row-reverse' : ''}${justified ? ' nav-fill' : ''}`;
const paneClass = `tab-pane${animation ? ' fade' : ''}`;
const iconClass = hideText ? undefined : 'me-2'
const navClass = `nav nav-tabs card-header-tabs${reverse ? ' flex-row-reverse' : ''}${justified ? ' nav-fill' : ''}`
const paneClass = `tab-pane${animation ? ' fade' : ''}`
---
<div class="card">
<div class="card-header">
<ul class={navClass} data-bs-toggle="tabs">
<li class="nav-item">
<a href={`#tabs-home-${id}`} class="nav-link active" data-bs-toggle="tab">{icons && <Icon name="home" class={iconClass} />}{!hideText && 'Home'}</a>
</li>
<li class="nav-item">
<a href={`#tabs-profile-${id}`} class="nav-link" data-bs-toggle="tab">{icons && <Icon name="user" class={iconClass} />}{!hideText && 'Profile'}</a>
</li>
<div class="card-header">
<ul class={navClass} data-bs-toggle="tabs">
<li class="nav-item">
<a href={`#tabs-home-${id}`} class="nav-link active" data-bs-toggle="tab">{icons && <Icon name="home" class={iconClass} />}{!hideText && 'Home'}</a>
</li>
<li class="nav-item">
<a href={`#tabs-profile-${id}`} class="nav-link" data-bs-toggle="tab">{icons && <Icon name="user" class={iconClass} />}{!hideText && 'Profile'}</a>
</li>
{activity && (
<li class="nav-item">
<a href={`#tabs-activity-${id}`} class="nav-link" data-bs-toggle="tab">{icons && <Icon name="activity" class={iconClass} />}{!hideText && 'Activity'}</a>
</li>
)}
{
activity && (
<li class="nav-item">
<a href={`#tabs-activity-${id}`} class="nav-link" data-bs-toggle="tab">
{icons && <Icon name="activity" class={iconClass} />}
{!hideText && 'Activity'}
</a>
</li>
)
}
{disabled && (
<li class="nav-item">
<a href={`#tabs-activity-${id}`} class="nav-link disabled" data-bs-toggle="tab">{icons && <Icon name="x" class={iconClass} />}{!hideText && 'Disabled'}</a>
</li>
)}
{
disabled && (
<li class="nav-item">
<a href={`#tabs-activity-${id}`} class="nav-link disabled" data-bs-toggle="tab">
{icons && <Icon name="x" class={iconClass} />}
{!hideText && 'Disabled'}
</a>
</li>
)
}
{dropdown && (
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown</a>
<DropdownMenu />
</li>
)}
{settings && (
<li class={`nav-item ${reverse ? 'me-auto' : 'ms-auto'}`}>
<a href={`#tabs-settings-${id}`} class="nav-link" title="Settings" data-bs-toggle="tab"><Icon name="settings" /></a>
</li>
)}
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<div class={`${paneClass} active show`} id={`tabs-home-${id}`}>
<h4>Home tab</h4>
<div>Cursus turpis vestibulum, dui in pharetra vulputate id sed non turpis ultricies fringilla at sed facilisis lacus pellentesque purus nibh</div>
</div>
<div class={paneClass} id={`tabs-profile-${id}`}>
<h4>Profile tab</h4>
<div>Fringilla egestas nunc quis tellus diam rhoncus ultricies tristique enim at diam, sem nunc amet, pellentesque id egestas velit sed</div>
</div>
{settings && (
<div class={paneClass} id={`tabs-settings-${id}`}>
<h4>Settings tab</h4>
<div>Donec ac vitae diam amet vel leo egestas consequat rhoncus in luctus amet, facilisi sit mauris accumsan nibh habitant senectus</div>
</div>
)}
{activity && (
<div class={paneClass} id={`tabs-activity-${id}`}>
<h4>Activity tab</h4>
<div>Donec ac vitae diam amet vel leo egestas consequat rhoncus in luctus amet, facilisi sit mauris accumsan nibh habitant senectus</div>
</div>
)}
</div>
</div>
{
dropdown && (
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
Dropdown
</a>
<DropdownMenu />
</li>
)
}
{
settings && (
<li class={`nav-item ${reverse ? 'me-auto' : 'ms-auto'}`}>
<a href={`#tabs-settings-${id}`} class="nav-link" title="Settings" data-bs-toggle="tab">
<Icon name="settings" />
</a>
</li>
)
}
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<div class={`${paneClass} active show`} id={`tabs-home-${id}`}>
<h4>Home tab</h4>
<div>Cursus turpis vestibulum, dui in pharetra vulputate id sed non turpis ultricies fringilla at sed facilisis lacus pellentesque purus nibh</div>
</div>
<div class={paneClass} id={`tabs-profile-${id}`}>
<h4>Profile tab</h4>
<div>Fringilla egestas nunc quis tellus diam rhoncus ultricies tristique enim at diam, sem nunc amet, pellentesque id egestas velit sed</div>
</div>
{
settings && (
<div class={paneClass} id={`tabs-settings-${id}`}>
<h4>Settings tab</h4>
<div>Donec ac vitae diam amet vel leo egestas consequat rhoncus in luctus amet, facilisi sit mauris accumsan nibh habitant senectus</div>
</div>
)
}
{
activity && (
<div class={paneClass} id={`tabs-activity-${id}`}>
<h4>Activity tab</h4>
<div>Donec ac vitae diam amet vel leo egestas consequat rhoncus in luctus amet, facilisi sit mauris accumsan nibh habitant senectus</div>
</div>
)
}
</div>
</div>
</div>
+21 -24
View File
@@ -2,41 +2,38 @@
// person = people[person-id]; the cover photo is photos[person-id].file.
// Base card-cover already carries `card-cover-blurred`; `blurred` adds it a 2nd time
// Base card-cover already carries `card-cover-blurred`; `blurred` adds it a second time.
import Avatar from '@ui/Avatar.astro';
import people from '@data/people.json';
import photos from '@data/photos.json';
import Avatar from '@ui/Avatar.astro'
import people from '@data/people.json'
import photos from '@data/photos.json'
interface Person {
full_name?: string;
job_title?: string;
photo?: string;
[key: string]: unknown;
full_name?: string
job_title?: string
photo?: string
[key: string]: unknown
}
interface Photo {
file?: string;
[key: string]: unknown;
file?: string
[key: string]: unknown
}
interface Props {
personId?: number;
blurred?: boolean;
personId?: number
blurred?: boolean
}
const { personId = 0, blurred } = Astro.props;
const person = (people as Person[])[personId];
const photo = (photos as Photo[])[personId];
const { personId = 0, blurred } = Astro.props
const person = (people as Person[])[personId]
const photo = (photos as Photo[])[personId]
---
<a class="card card-link" href="#">
<div
class={`card-cover card-cover-blurred text-center${blurred ? ' card-cover-blurred' : ''}`}
style={`background-image: url(./static/photos/${photo.file})`}
>
<Avatar size="xl" person={person} thumb />
</div>
<div class="card-body text-center">
<div class="card-title mb-1">{person.full_name}</div>
<div class="text-secondary">{person.job_title}</div>
</div>
<div class={`card-cover card-cover-blurred text-center${blurred ? ' card-cover-blurred' : ''}`} style={`background-image: url(./static/photos/${photo.file})`}>
<Avatar size="xl" person={person} thumb />
</div>
<div class="card-body text-center">
<div class="card-title mb-1">{person.full_name}</div>
<div class="text-secondary">{person.job_title}</div>
</div>
</a>
+17 -17
View File
@@ -1,29 +1,29 @@
---
import Avatar from '@ui/Avatar.astro';
import people from '@data/people.json';
import Avatar from '@ui/Avatar.astro'
import people from '@data/people.json'
interface Person {
full_name?: string;
job_title?: string;
photo?: string;
[key: string]: unknown;
full_name?: string
job_title?: string
photo?: string
[key: string]: unknown
}
interface Props {
personId?: number;
personId?: number
}
const { personId = 0 } = Astro.props;
const person = (people as Person[])[personId];
const { personId = 0 } = Astro.props
const person = (people as Person[])[personId]
---
<div class="card">
<div class="card-body text-center">
<div class="mb-3">
<Avatar size="xl" person={person} />
</div>
<div class="card-title mb-1">{person.full_name}</div>
<div class="text-secondary">{person.job_title}</div>
</div>
<a href="#" class="card-btn">View full profile</a>
<div class="card-body text-center">
<div class="mb-3">
<Avatar size="xl" person={person} />
</div>
<div class="card-title mb-1">{person.full_name}</div>
<div class="text-secondary">{person.job_title}</div>
</div>
<a href="#" class="card-btn">View full profile</a>
</div>
+42 -42
View File
@@ -1,55 +1,55 @@
---
// person = people[person-id - 1] (default person-id = 25 → people[24]).
import Icon from '@ui/Icon.astro';
import Flag from '@ui/Flag.astro';
import people from '@data/people.json';
import Icon from '@ui/Icon.astro'
import Flag from '@ui/Flag.astro'
import people from '@data/people.json'
interface Person {
university?: string;
company?: string;
city?: string;
country?: string;
country_code?: string;
birth_date?: string;
time_zone?: string;
[key: string]: unknown;
university?: string
company?: string
city?: string
country?: string
country_code?: string
birth_date?: string
time_zone?: string
[key: string]: unknown
}
interface Props {
title?: string;
personId?: number;
title?: string
personId?: number
}
const { title = 'Basic info', personId = 25 } = Astro.props;
const person = (people as Person[])[personId - 1];
const { title = 'Basic info', personId = 25 } = Astro.props
const person = (people as Person[])[personId - 1]
---
<div class="card">
<div class="card-body">
<div class="card-title">{title}</div>
<div class="mb-2">
<Icon name="book" class="me-2 text-secondary" />
Went to: <strong>{person.university}</strong>
</div>
<div class="mb-2">
<Icon name="briefcase" class="me-2 text-secondary" />
Worked at: <strong>{person.company}</strong>
</div>
<div class="mb-2">
<Icon name="home" class="me-2 text-secondary" />
Lives in: <strong>{person.city}, {person.country}</strong>
</div>
<div class="mb-2">
<Icon name="map-pin" class="me-2 text-secondary" />
From: <strong><Flag size="xs" flag={person.country_code} /> {person.country}</strong>
</div>
<div class="mb-2">
<Icon name="calendar" class="me-2 text-secondary" />
Birth date: <strong>{person.birth_date}</strong>
</div>
<div>
<Icon name="clock" class="me-2 text-secondary" />
Time zone: <strong>{person.time_zone}</strong>
</div>
</div>
<div class="card-body">
<div class="card-title">{title}</div>
<div class="mb-2">
<Icon name="book" class="me-2 text-secondary" />
Went to: <strong>{person.university}</strong>
</div>
<div class="mb-2">
<Icon name="briefcase" class="me-2 text-secondary" />
Worked at: <strong>{person.company}</strong>
</div>
<div class="mb-2">
<Icon name="home" class="me-2 text-secondary" />
Lives in: <strong>{person.city}, {person.country}</strong>
</div>
<div class="mb-2">
<Icon name="map-pin" class="me-2 text-secondary" />
From: <strong><Flag size="xs" flag={person.country_code} /> {person.country}</strong>
</div>
<div class="mb-2">
<Icon name="calendar" class="me-2 text-secondary" />
Birth date: <strong>{person.birth_date}</strong>
</div>
<div>
<Icon name="clock" class="me-2 text-secondary" />
Time zone: <strong>{person.time_zone}</strong>
</div>
</div>
</div>
+81 -87
View File
@@ -1,103 +1,97 @@
---
import Avatar from '@ui/Avatar.astro';
import Icon from '@ui/Icon.astro';
import ListGroup from '@ui/ListGroup.astro';
import ListGroupItem from '@ui/ListGroupItem.astro';
import CardTitle from '@ui/CardTitle.astro';
import people from '@data/people.json';
import commits from '@data/commits.json';
import { randomNumber } from '@shared/lib/pseudo-random';
import Avatar from '@ui/Avatar.astro'
import Icon from '@ui/Icon.astro'
import ListGroup from '@ui/ListGroup.astro'
import ListGroupItem from '@ui/ListGroupItem.astro'
import CardTitle from '@ui/CardTitle.astro'
import people from '@data/people.json'
import commits from '@data/commits.json'
import { randomNumber } from '@shared/lib/pseudo-random'
interface Person {
full_name?: string;
photo?: string;
last_name?: string;
[key: string]: unknown;
full_name?: string
photo?: string
last_name?: string
[key: string]: unknown
}
interface Props {
limit?: number;
offset?: number;
hoverable?: boolean;
checkbox?: boolean;
/** checked-ids — 1-based indices, e.g. [2, 5, 8] */
checkedIds?: number[];
title?: string;
class?: string;
/**
* Inert. The lists.html Contacts card passes `hover=true`, but
* users-list.html never reads it — only `hoverable` toggles the star column.
* Declared for call-site compatibility.
*/
hover?: boolean;
limit?: number
offset?: number
hoverable?: boolean
checkbox?: boolean
/** checked-ids — 1-based indices, e.g. [2, 5, 8] */
checkedIds?: number[]
title?: string
class?: string
/**
* Inert. The lists.html Contacts card passes `hover=true`, but
* users-list.html never reads it — only `hoverable` toggles the star column.
* Declared for call-site compatibility.
*/
hover?: boolean
}
const {
limit = 8,
offset = 0,
hoverable = false,
checkbox,
checkedIds,
title = 'Last commits',
class: className,
} = Astro.props;
const { limit = 8, offset = 0, hoverable = false, checkbox, checkedIds, title = 'Last commits', class: className } = Astro.props
const colors = ['green', 'red', 'yellow', 'x', 'x'];
const commitList = commits as { description: string }[];
const colors = ['green', 'red', 'yellow', 'x', 'x']
const commitList = commits as { description: string }[]
const rows = (people as Person[]).slice(offset, offset + limit).map((person, idx) => {
const index = idx + 1; // forloop.index (1-based)
const color = colors[randomNumber(index + 5, 0, colors.length - 1)];
const checked = checkedIds?.includes(index) ?? false;
const i = index + offset;
return {
person,
color,
checked,
description: commitList[i]?.description,
starColor: checked ? 'text-yellow' : 'text-secondary',
};
});
const index = idx + 1 // forloop.index (1-based)
const color = colors[randomNumber(index + 5, 0, colors.length - 1)]
const checked = checkedIds?.includes(index) ?? false
const i = index + offset
return {
person,
color,
checked,
description: commitList[i]?.description,
starColor: checked ? 'text-yellow' : 'text-secondary',
}
})
---
<div class={`card${className ? ` ${className}` : ''}`}>
<div class="card-header">
<CardTitle>{title}</CardTitle>
</div>
<ListGroup flush hoverable={hoverable}>
{
rows.map((row) => (
<ListGroupItem active={row.checked}>
<div class="row align-items-center">
{checkbox ? (
<div class="col-auto">
<input type="checkbox" class="form-check-input" checked={row.checked} />
</div>
) : (
<div class="col-auto">
<span class={`badge${row.color !== 'x' ? ` bg-${row.color}` : ''}`} />
</div>
)}
<div class="col-auto">
<a href="#">
<Avatar person={row.person} />
</a>
</div>
<div class="card-header">
<CardTitle>{title}</CardTitle>
</div>
<ListGroup flush hoverable={hoverable}>
{
rows.map((row) => (
<ListGroupItem active={row.checked}>
<div class="row align-items-center">
{checkbox ? (
<div class="col-auto">
<input type="checkbox" class="form-check-input" checked={row.checked} />
</div>
) : (
<div class="col-auto">
<span class={`badge${row.color !== 'x' ? ` bg-${row.color}` : ''}`} />
</div>
)}
<div class="col-auto">
<a href="#">
<Avatar person={row.person} />
</a>
</div>
<div class="col text-truncate">
<a href="#" class="text-reset d-block">{row.person.full_name}</a>
<div class="d-block text-secondary text-truncate mt-n1">{row.description}</div>
</div>
{hoverable && (
<div class="col-auto">
<a href="#" class={`list-group-item-actions${row.checked ? ' show' : ''}`}>
<Icon name="star" class={row.starColor} />
</a>
</div>
)}
</div>
</ListGroupItem>
))
}
</ListGroup>
<div class="col text-truncate">
<a href="#" class="text-reset d-block">
{row.person.full_name}
</a>
<div class="d-block text-secondary text-truncate mt-n1">{row.description}</div>
</div>
{hoverable && (
<div class="col-auto">
<a href="#" class={`list-group-item-actions${row.checked ? ' show' : ''}`}>
<Icon name="star" class={row.starColor} />
</a>
</div>
)}
</div>
</ListGroupItem>
))
}
</ListGroup>
</div>
+44 -42
View File
@@ -1,57 +1,59 @@
---
import Avatar from '@ui/Avatar.astro';
import CardTitle from '@ui/CardTitle.astro';
import people from '@data/people.json';
import { randomNumber, timeagoLabel } from '@shared/lib/pseudo-random';
import Avatar from '@ui/Avatar.astro'
import CardTitle from '@ui/CardTitle.astro'
import people from '@data/people.json'
import { randomNumber, timeagoLabel } from '@shared/lib/pseudo-random'
interface Person {
full_name?: string;
photo?: string;
[key: string]: unknown;
full_name?: string
photo?: string
[key: string]: unknown
}
interface Props {
limit?: number;
offset?: number;
title?: string;
class?: string;
limit?: number
offset?: number
title?: string
class?: string
}
const { limit = 10, offset = 0, title = 'Top users', class: className } = Astro.props;
const { limit = 10, offset = 0, title = 'Top users', class: className } = Astro.props
const colors = 'green,red,yellow,x,x'.split(',');
const colors = 'green,red,yellow,x,x'.split(',')
const rows = (people as Person[]).slice(offset, offset + limit).map((person, idx) => {
const index = idx + 1; // forloop.index (1-based)
return {
person,
status: colors[randomNumber(index + 5, 0, colors.length - 1)],
timeago: timeagoLabel(index, 6),
};
});
const index = idx + 1 // forloop.index (1-based)
return {
person,
status: colors[randomNumber(index + 5, 0, colors.length - 1)],
timeago: timeagoLabel(index, 6),
}
})
---
<div class={`card${className ? ` ${className}` : ''}`}>
<div class="card-header">
<CardTitle>{title}</CardTitle>
</div>
<div class="card-body">
<div class="row g-3">
{
rows.map((row) => (
<div class="col-6">
<div class="row g-3 align-items-center">
<a href="#" class="col-auto">
<Avatar person={row.person} status={row.status} />
</a>
<div class="col text-truncate">
<a href="#" class="text-reset d-block text-truncate">{row.person.full_name}</a>
<div class="text-secondary text-truncate mt-n1">{row.timeago}</div>
</div>
</div>
</div>
))
}
</div>
</div>
<div class="card-header">
<CardTitle>{title}</CardTitle>
</div>
<div class="card-body">
<div class="row g-3">
{
rows.map((row) => (
<div class="col-6">
<div class="row g-3 align-items-center">
<a href="#" class="col-auto">
<Avatar person={row.person} status={row.status} />
</a>
<div class="col text-truncate">
<a href="#" class="text-reset d-block text-truncate">
{row.person.full_name}
</a>
<div class="text-secondary text-truncate mt-n1">{row.timeago}</div>
</div>
</div>
</div>
))
}
</div>
</div>
</div>
+53 -51
View File
@@ -1,70 +1,72 @@
---
import Avatar from '@ui/Avatar.astro';
import ListGroup from '@ui/ListGroup.astro';
import ListGroupItem from '@ui/ListGroupItem.astro';
import ListGroupHeader from '@ui/ListGroupHeader.astro';
import CardTitle from '@ui/CardTitle.astro';
import people from '@data/people.json';
import commits from '@data/commits.json';
import { sortBy } from '@shared/lib/string-format';
import Avatar from '@ui/Avatar.astro'
import ListGroup from '@ui/ListGroup.astro'
import ListGroupItem from '@ui/ListGroupItem.astro'
import ListGroupHeader from '@ui/ListGroupHeader.astro'
import CardTitle from '@ui/CardTitle.astro'
import people from '@data/people.json'
import commits from '@data/commits.json'
import { sortBy } from '@shared/lib/string-format'
interface Person {
full_name?: string;
photo?: string;
last_name?: string;
[key: string]: unknown;
full_name?: string
photo?: string
last_name?: string
[key: string]: unknown
}
interface Props {
title?: string;
title?: string
}
const { title = 'People' } = Astro.props;
const { title = 'People' } = Astro.props
const commitList = commits as { description: string }[];
const commitList = commits as { description: string }[]
// Sort by last_name (case-sensitive).
const sorted = sortBy(people as Person[], (p) => p.last_name ?? '');
const sorted = sortBy(people as Person[], (p) => p.last_name ?? '')
// offset is unset in the include, so `forloop.index | plus: offset` == forloop.index.
let prevLetter = '';
let prevLetter = ''
const rows = sorted.map((person, idx) => {
const index = idx + 1; // forloop.index (1-based)
const firstLetter = (person.last_name ?? '').slice(0, 1);
let header: string | null = null;
if (prevLetter !== firstLetter) {
prevLetter = firstLetter;
header = firstLetter;
}
return { person, header, description: commitList[index]?.description };
});
const index = idx + 1 // forloop.index (1-based)
const firstLetter = (person.last_name ?? '').slice(0, 1)
let header: string | null = null
if (prevLetter !== firstLetter) {
prevLetter = firstLetter
header = firstLetter
}
return { person, header, description: commitList[index]?.description }
})
---
<div class="card">
<div class="card-header">
<CardTitle>{title}</CardTitle>
</div>
<div class="card-header">
<CardTitle>{title}</CardTitle>
</div>
<ListGroup flush class="overflow-auto" style="max-height: 35rem">
{
rows.map((row) => (
<>
{row.header && <ListGroupHeader class="sticky-top">{row.header}</ListGroupHeader>}
<ListGroupItem>
<div class="row">
<div class="col-auto">
<a href="#">
<Avatar person={row.person} />
</a>
</div>
<div class="col text-truncate">
<a href="#" class="text-body d-block">{row.person.full_name}</a>
<div class="text-secondary text-truncate mt-n1">{row.description}</div>
</div>
</div>
</ListGroupItem>
</>
))
}
</ListGroup>
<ListGroup flush class="overflow-auto" style="max-height: 35rem">
{
rows.map((row) => (
<>
{row.header && <ListGroupHeader class="sticky-top">{row.header}</ListGroupHeader>}
<ListGroupItem>
<div class="row">
<div class="col-auto">
<a href="#">
<Avatar person={row.person} />
</a>
</div>
<div class="col text-truncate">
<a href="#" class="text-body d-block">
{row.person.full_name}
</a>
<div class="text-secondary text-truncate mt-n1">{row.description}</div>
</div>
</div>
</ListGroupItem>
</>
))
}
</ListGroup>
</div>
@@ -1,18 +1,18 @@
---
// legend=true at call site is inert — Chart reads legend from chart data, not props.
import CardTitle from '@ui/CardTitle.astro';
import Chart from '@ui/Chart.astro';
import DropdownDays from '@ui/DropdownDays.astro';
import CardTitle from '@ui/CardTitle.astro'
import Chart from '@ui/Chart.astro'
import DropdownDays from '@ui/DropdownDays.astro'
---
<div class="card">
<div class="card-body">
<div class="d-flex">
<CardTitle>Social referrals</CardTitle>
<div class="ms-auto">
<DropdownDays id="social-referrals" label="Select time range for sales data" />
</div>
</div>
<Chart chartId="social-referrals" />
</div>
<div class="card-body">
<div class="d-flex">
<CardTitle>Social referrals</CardTitle>
<div class="ms-auto">
<DropdownDays id="social-referrals" label="Select time range for sales data" />
</div>
</div>
<Chart chartId="social-referrals" />
</div>
</div>
+46 -48
View File
@@ -1,55 +1,53 @@
---
// Switch-icon heart variant inlined: icon-b="heart", icon-b-class="icon-filled", icon-a-color="muted", icon-b-color="red".
import Icon from '@ui/Icon.astro';
import DropdownMenu from '@ui/DropdownMenu.astro';
import ListGroup from '@ui/ListGroup.astro';
import ListGroupItem from '@ui/ListGroupItem.astro';
import tracks from '@data/tracks.json';
import { millisecondsToMinutes } from '@shared/lib/string-format';
import Icon from '@ui/Icon.astro'
import DropdownMenu from '@ui/DropdownMenu.astro'
import ListGroup from '@ui/ListGroup.astro'
import ListGroupItem from '@ui/ListGroupItem.astro'
import tracks from '@data/tracks.json'
import { millisecondsToMinutes } from '@shared/lib/string-format'
const items = tracks.slice(0, 12);
const items = tracks.slice(0, 12)
---
<div class="card">
<ListGroup class="card-list-group">
{items.map((track, index) => (
<ListGroupItem>
<div class="row g-2 align-items-center">
<div class="col-auto fs-3">
{index + 1}
</div>
<div class="col-auto">
<img src={`./static/tracks/${track.album.images[1].path}`} class="rounded" alt={track.name} width="40" height="40" />
</div>
<div class="col">
{track.name}
<div class="text-secondary">
{track.artists.map((artist) => artist.name).join(', ')}
</div>
</div>
<div class="col-auto text-secondary">
{millisecondsToMinutes(track.duration_ms)}
</div>
<div class="col-auto">
<a href="#" class="link-secondary">
<button class="switch-icon" data-bs-toggle="switch-icon">
<span class="switch-icon-a text-muted">
<Icon name="heart" />
</span>
<span class="switch-icon-b text-red">
<Icon name="heart" class="icon-filled" />
</span>
</button>
</a>
</div>
<div class="col-auto lh-1">
<div class="dropdown">
<a href="#" class="link-secondary" data-bs-toggle="dropdown"><Icon name="dots" /></a>
<DropdownMenu right />
</div>
</div>
</div>
</ListGroupItem>
))}
</ListGroup>
<ListGroup class="card-list-group">
{
items.map((track, index) => (
<ListGroupItem>
<div class="row g-2 align-items-center">
<div class="col-auto fs-3">{index + 1}</div>
<div class="col-auto">
<img src={`./static/tracks/${track.album.images[1].path}`} class="rounded" alt={track.name} width="40" height="40" />
</div>
<div class="col">
{track.name}
<div class="text-secondary">{track.artists.map((artist) => artist.name).join(', ')}</div>
</div>
<div class="col-auto text-secondary">{millisecondsToMinutes(track.duration_ms)}</div>
<div class="col-auto">
<a href="#" class="link-secondary">
<button class="switch-icon" data-bs-toggle="switch-icon">
<span class="switch-icon-a text-muted">
<Icon name="heart" />
</span>
<span class="switch-icon-b text-red">
<Icon name="heart" class="icon-filled" />
</span>
</button>
</a>
</div>
<div class="col-auto lh-1">
<div class="dropdown">
<a href="#" class="link-secondary" data-bs-toggle="dropdown">
<Icon name="dots" />
</a>
<DropdownMenu right />
</div>
</div>
</div>
</ListGroupItem>
))
}
</ListGroup>
</div>
@@ -1,4 +1,5 @@
---
---
<div class="card placeholder-glow">
@@ -1,4 +1,5 @@
---
---
<div class="card">
@@ -1,4 +1,5 @@
---
---
<div class="card">
@@ -1,4 +1,5 @@
---
---
<div class="card">
@@ -1,4 +1,5 @@
---
---
<div class="card">
+24 -24
View File
@@ -1,30 +1,30 @@
---
import ListGroup from '@ui/ListGroup.astro';
import ListGroupItem from '@ui/ListGroupItem.astro';
import ListGroup from '@ui/ListGroup.astro'
import ListGroupItem from '@ui/ListGroupItem.astro'
const items = [1, 2, 3, 4];
const items = [1, 2, 3, 4]
---
<div class="card">
<ListGroup as="ul" flush class="placeholder-glow">
{
items.map(() => (
<ListGroupItem as="li">
<div class="row align-items-center">
<div class="col-auto">
<div class="avatar avatar-rounded placeholder" />
</div>
<div class="col-7">
<div class="placeholder placeholder-xs col-9" />
<div class="placeholder placeholder-xs col-7" />
</div>
<div class="col-2 ms-auto text-end">
<div class="placeholder placeholder-xs col-8" />
<div class="placeholder placeholder-xs col-10" />
</div>
</div>
</ListGroupItem>
))
}
</ListGroup>
<ListGroup as="ul" flush class="placeholder-glow">
{
items.map(() => (
<ListGroupItem as="li">
<div class="row align-items-center">
<div class="col-auto">
<div class="avatar avatar-rounded placeholder" />
</div>
<div class="col-7">
<div class="placeholder placeholder-xs col-9" />
<div class="placeholder placeholder-xs col-7" />
</div>
<div class="col-2 ms-auto text-end">
<div class="placeholder placeholder-xs col-8" />
<div class="placeholder placeholder-xs col-10" />
</div>
</div>
</ListGroupItem>
))
}
</ListGroup>
</div>
+121 -129
View File
@@ -3,155 +3,147 @@
// with dropdowns, icon/loader/separated inputs, and a help-icon input.
// TomSelect init for #select-states — FIRST page script, matching the reference
// order (select-states precedes the FormElements6 scripts).
import Icon from '@ui/Icon.astro';
import FormGroup from '@ui/FormGroup.astro';
import selects from '@data/selects.json';
import CaptureScript from '../CaptureScript.astro';
import Icon from '@ui/Icon.astro'
import FormGroup from '@ui/FormGroup.astro'
import selects from '@data/selects.json'
import CaptureScript from '../CaptureScript.astro'
const states = (selects as { states: { options: Record<string, { name: string; selected?: boolean }> } }).states.options;
const states = (selects as { states: { options: Record<string, { name: string; selected?: boolean }> } }).states.options
---
<FormGroup label="Static">
<div class="form-control-plaintext">Input value</div>
<div class="form-control-plaintext">Input value</div>
</FormGroup>
<FormGroup label="Text">
<input type="text" class="form-control" name="example-text-input" placeholder="Input placeholder">
<input type="text" class="form-control" name="example-text-input" placeholder="Input placeholder" />
</FormGroup>
<FormGroup label="Password">
<input type="text" class="form-control" name="example-password-input" placeholder="Input placeholder">
<input type="text" class="form-control" name="example-password-input" placeholder="Input placeholder" />
</FormGroup>
<FormGroup label="Disabled">
<input type="text" class="form-control" name="example-disabled-input" placeholder="Disabled..." value="Well, she turned me into a newt." disabled>
<input type="text" class="form-control" name="example-disabled-input" placeholder="Disabled..." value="Well, she turned me into a newt." disabled />
</FormGroup>
<FormGroup label="Readonly">
<input type="text" class="form-control" name="example-disabled-input" placeholder="Readonly..." value="Well, how'd you become king, then?" readonly>
<input type="text" class="form-control" name="example-disabled-input" placeholder="Readonly..." value="Well, how'd you become king, then?" readonly />
</FormGroup>
<FormGroup label="Required" required>
<input type="text" class="form-control" name="example-required-input" placeholder="Required...">
<input type="text" class="form-control" name="example-required-input" placeholder="Required..." />
</FormGroup>
<FormGroup label="Textarea" description="56/100">
<textarea class="form-control" name="example-textarea-input" rows="6" placeholder="Content..">Oh! Come and see the violence inherent in the system! Help, help, I'm being repressed! We shall say 'Ni' again to you, if you do not appease us. I'm not a witch. I'm not a witch. Camelot!</textarea>
<textarea class="form-control" name="example-textarea-input" rows="6" placeholder="Content..">Oh! Come and see the violence inherent in the system! Help, help, I'm being repressed! We shall say 'Ni' again to you, if you do not appease us. I'm not a witch. I'm not a witch. Camelot!</textarea>
</FormGroup>
<div class="mb-3">
<div class="form-label">Select</div>
<select class="form-select">
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
<div class="form-label">Select</div>
<select class="form-select">
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
</div>
<div class="mb-3">
<div class="form-label">Select multiple</div>
<select class="form-select" multiple>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
<div class="form-label">Select multiple</div>
<select class="form-select" multiple>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
</div>
<div class="mb-3">
<div class="form-label">Select multiple states</div>
<select class="form-select" id="select-states" value="" multiple>
{
Object.entries(states).map(([code, { name, selected }]) => (
<option value={code} selected={selected}>{name}</option>
))
}
</select>
<div class="form-label">Select multiple states</div>
<select class="form-select" id="select-states" value="" multiple>
{
Object.entries(states).map(([code, { name, selected }]) => (
<option value={code} selected={selected}>
{name}
</option>
))
}
</select>
</div>
<FormGroup label="Input group">
<div class="input-group mb-2">
<input type="text" class="form-control" placeholder="Search for…">
<button class="btn" type="button">Go!</button>
</div>
<div class="input-group">
<button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Action
</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">
Action
</a>
<a class="dropdown-item" href="#">
Another action
</a>
</div>
<input type="text" class="form-control" aria-label="Text input with dropdown button">
</div>
<div class="input-group mb-2">
<input type="text" class="form-control" placeholder="Search for…" />
<button class="btn" type="button">Go!</button>
</div>
<div class="input-group">
<button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> Action </button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#"> Action </a>
<a class="dropdown-item" href="#"> Another action </a>
</div>
<input type="text" class="form-control" aria-label="Text input with dropdown button" />
</div>
</FormGroup>
<FormGroup label="Input group buttons">
<div class="input-group">
<input type="text" class="form-control">
<button type="button" class="btn">Action</button>
<button data-bs-toggle="dropdown" type="button" class="btn dropdown-toggle dropdown-toggle-split"></button>
<div class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item" href="#">
Action
</a>
<a class="dropdown-item" href="#">
Another action
</a>
</div>
</div>
<div class="input-group">
<input type="text" class="form-control" />
<button type="button" class="btn">Action</button>
<button data-bs-toggle="dropdown" type="button" class="btn dropdown-toggle dropdown-toggle-split"></button>
<div class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item" href="#"> Action </a>
<a class="dropdown-item" href="#"> Another action </a>
</div>
</div>
</FormGroup>
<FormGroup label="Icon input">
<div class="input-icon mb-3">
<input type="text" value="" class="form-control" placeholder="Search…">
<span class="input-icon-addon">
<Icon name="search" />
</span>
</div>
<div class="input-icon mb-3">
<span class="input-icon-addon">
<Icon name="user" />
</span>
<input type="text" value="" class="form-control" placeholder="Username">
</div>
<div class="input-icon mb-3">
<input type="text" value="" class="form-control" placeholder="Search…" />
<span class="input-icon-addon">
<Icon name="search" />
</span>
</div>
<div class="input-icon mb-3">
<span class="input-icon-addon">
<Icon name="user" />
</span>
<input type="text" value="" class="form-control" placeholder="Username" />
</div>
</FormGroup>
<FormGroup label="Loader input">
<div class="input-icon mb-3">
<input type="text" value="" class="form-control" placeholder="Loading…">
<span class="input-icon-addon">
<div class="spinner-border spinner-border-sm text-secondary" role="status"></div>
</span>
</div>
<div class="input-icon mb-3">
<span class="input-icon-addon">
<div class="spinner-border spinner-border-sm text-secondary" role="status"></div>
</span>
<input type="text" value="" class="form-control" placeholder="Loading…">
</div>
<div class="input-icon mb-3">
<input type="text" value="" class="form-control" placeholder="Loading…" />
<span class="input-icon-addon">
<div class="spinner-border spinner-border-sm text-secondary" role="status"></div>
</span>
</div>
<div class="input-icon mb-3">
<span class="input-icon-addon">
<div class="spinner-border spinner-border-sm text-secondary" role="status"></div>
</span>
<input type="text" value="" class="form-control" placeholder="Loading…" />
</div>
</FormGroup>
<FormGroup label="Separated inputs">
<div class="row g-2">
<div class="col">
<input type="text" class="form-control" placeholder="Search for…">
</div>
<div class="col-auto">
<a href="#" class="btn btn-icon" aria-label="Button">
<Icon name="search" />
</a>
</div>
</div>
<div class="row g-2">
<div class="col">
<input type="text" class="form-control" placeholder="Search for…" />
</div>
<div class="col-auto">
<a href="#" class="btn btn-icon" aria-label="Button">
<Icon name="search" />
</a>
</div>
</div>
</FormGroup>
<FormGroup label="Input with help icon">
<div class="row g-2">
<div class="col">
<input type="text" class="form-control" placeholder="Search for…">
</div>
<div class="col-auto align-self-center">
<span class="form-help" data-bs-toggle="popover" data-bs-placement="top" data-bs-content="<p>ZIP Code must be US or CDN format. You can use an extended ZIP+4 code to determine address more accurately.</p><p class='mb-0'><a href='#'>USP ZIP codes lookup tools</a></p>" data-bs-html="true">?</span>
</div>
</div>
<div class="row g-2">
<div class="col">
<input type="text" class="form-control" placeholder="Search for…" />
</div>
<div class="col-auto align-self-center">
<span class="form-help" data-bs-toggle="popover" data-bs-placement="top" data-bs-content="<p>ZIP Code must be US or CDN format. You can use an extended ZIP+4 code to determine address more accurately.</p><p class='mb-0'><a href='#'>USP ZIP codes lookup tools</a></p>" data-bs-html="true">?</span>
</div>
</div>
</FormGroup>
<!--
is:inline: TomSelect loads via a deferred page-lib <script>. A processed (module)
@@ -162,31 +154,31 @@ const states = (selects as { states: { options: Record<string, { name: string; s
actually delays until after the library has loaded.
-->
<CaptureScript>
<!-- BEGIN SELECT STATES -->
<script is:inline>
function initSelectStates() {
window.tabler_select ??= {};
<!-- BEGIN SELECT STATES -->
<script is:inline>
function initSelectStates() {
window.tabler_select ??= {}
const renderOption = (data, escape) => {
if (data.customProperties) {
return `<div><span class="dropdown-item-indicator">${data.customProperties}</span>${escape(data.text)}</div>`;
}
return `<div>${escape(data.text)}</div>`;
};
const renderOption = (data, escape) => {
if (data.customProperties) {
return `<div><span class="dropdown-item-indicator">${data.customProperties}</span>${escape(data.text)}</div>`
}
return `<div>${escape(data.text)}</div>`
}
window.TomSelect &&
(window.tabler_select['select-states'] = new TomSelect(document.getElementById('select-states'), {
copyClassesToDropdown: false,
dropdownParent: 'body',
controlInput: '<input>',
render: {
item: renderOption,
option: renderOption,
},
}));
}
window.TomSelect &&
(window.tabler_select['select-states'] = new TomSelect(document.getElementById('select-states'), {
copyClassesToDropdown: false,
dropdownParent: 'body',
controlInput: '<input>',
render: {
item: renderOption,
option: renderOption,
},
}))
}
document.readyState !== 'loading' ? initSelectStates() : document.addEventListener('DOMContentLoaded', initSelectStates, { once: true });
</script>
<!-- END SELECT STATES -->
document.readyState !== 'loading' ? initSelectStates() : document.addEventListener('DOMContentLoaded', initSelectStates, { once: true })
</script>
<!-- END SELECT STATES -->
</CaptureScript>
+14 -14
View File
@@ -1,5 +1,5 @@
---
import CaptureScript from '../CaptureScript.astro';
import CaptureScript from '../CaptureScript.astro'
interface Props {
id?: string
@@ -12,18 +12,18 @@ const listId = `table-${id}`
---
<CaptureScript>
<!-- BEGIN TABLER LIST -->
<script define:vars={{ listId, valueNames }}>
function initTablerList() {
window.tabler_list ??= {}
window.tabler_list[listId] = new List(listId, {
sortClass: 'table-sort',
listClass: 'table-tbody',
valueNames,
})
}
<!-- BEGIN TABLER LIST -->
<script define:vars={{ listId, valueNames }}>
function initTablerList() {
window.tabler_list ??= {}
window.tabler_list[listId] = new List(listId, {
sortClass: 'table-sort',
listClass: 'table-tbody',
valueNames,
})
}
document.readyState !== 'loading' ? initTablerList() : document.addEventListener('DOMContentLoaded', initTablerList, { once: true })
</script>
<!-- END TABLER LIST -->
document.readyState !== 'loading' ? initTablerList() : document.addEventListener('DOMContentLoaded', initTablerList, { once: true })
</script>
<!-- END TABLER LIST -->
</CaptureScript>
+51 -55
View File
@@ -1,65 +1,61 @@
---
import Icon from '@ui/Icon.astro';
import { site } from '@shared/lib/site';
import { formatUtcTimestamp } from '@shared/lib/date-format';
import Icon from '@ui/Icon.astro'
import { site } from '@shared/lib/site'
import { formatUtcTimestamp } from '@shared/lib/date-format'
// Development build (unminified assets), same as BaseLayout.
const environment: string = 'development';
const environment: string = 'development'
const now = new Date();
const generatedAt = formatUtcTimestamp(now);
const now = new Date()
const generatedAt = formatUtcTimestamp(now)
---
<!-- BEGIN FOOTER -->
<footer class="footer footer-transparent d-print-none">
<div class="container-xl">
<div class="row text-center align-items-center flex-row-reverse">
<div class="col-lg-auto ms-lg-auto">
<nav aria-label="Footer">
<ul class="list-inline list-inline-dots mb-0">
<li class="list-inline-item">
<a href={site.docsUrl} target="_blank" class="link-secondary" rel="noopener">
Documentation
</a>
</li>
<li class="list-inline-item">
<a href="./license.html" class="link-secondary">License</a>
</li>
<li class="list-inline-item">
<a href={site.githubUrl} target="_blank" class="link-secondary" rel="noopener">
Source code
</a>
</li>
<li class="list-inline-item">
<a href={site.githubSponsorsUrl} target="_blank" class="link-secondary" rel="noopener">
{/* filled prop ignored — only type matters, heart renders as outline */}
<Icon name="heart" inline color="pink" />
Sponsor
</a>
</li>
</ul>
</nav>
</div>
<div class="col-12 col-lg-auto mt-3 mt-lg-0">
<ul class="list-inline list-inline-dots mb-0">
<li class="list-inline-item">
Copyright &copy; {now.getUTCFullYear()}
<a href="." class="link-secondary">{site.title}</a>. All rights reserved.
</li>
<li class="list-inline-item">
{
environment === 'production' || environment === 'preview' ? (
<a href="./changelog.html" class="link-secondary" rel="noopener">
v{site.version}
</a>
) : (
`Generated ${generatedAt}`
)
}
</li>
</ul>
</div>
</div>
</div>
<div class="container-xl">
<div class="row text-center align-items-center flex-row-reverse">
<div class="col-lg-auto ms-lg-auto">
<nav aria-label="Footer">
<ul class="list-inline list-inline-dots mb-0">
<li class="list-inline-item">
<a href={site.docsUrl} target="_blank" class="link-secondary" rel="noopener"> Documentation </a>
</li>
<li class="list-inline-item">
<a href="./license.html" class="link-secondary">License</a>
</li>
<li class="list-inline-item">
<a href={site.githubUrl} target="_blank" class="link-secondary" rel="noopener"> Source code </a>
</li>
<li class="list-inline-item">
<a href={site.githubSponsorsUrl} target="_blank" class="link-secondary" rel="noopener">
{/* filled prop ignored — only type matters, heart renders as outline */}
<Icon name="heart" inline color="pink" />
Sponsor
</a>
</li>
</ul>
</nav>
</div>
<div class="col-12 col-lg-auto mt-3 mt-lg-0">
<ul class="list-inline list-inline-dots mb-0">
<li class="list-inline-item">
Copyright &copy; {now.getUTCFullYear()}
<a href="." class="link-secondary">{site.title}</a>. All rights reserved.
</li>
<li class="list-inline-item">
{
environment === 'production' || environment === 'preview' ? (
<a href="./changelog.html" class="link-secondary" rel="noopener">
v{site.version}
</a>
) : (
`Generated ${generatedAt}`
)
}
</li>
</ul>
</div>
</div>
</div>
</footer>
<!-- END FOOTER -->
@@ -1,10 +1,10 @@
---
// Breadcrumb is always "Tabler > Pages" — page-header append is empty in reference output.
import Breadcrumb from '@ui/Breadcrumb.astro';
import Breadcrumb from '@ui/Breadcrumb.astro'
---
<!-- BEGIN HEADER ACTIONS -->
<div class="d-flex">
<Breadcrumb pages={['Tabler', 'Pages']} class="breadcrumb-arrows" />
<Breadcrumb pages={['Tabler', 'Pages']} class="breadcrumb-arrows" />
</div>
<!-- END HEADER ACTIONS -->
@@ -1,48 +1,37 @@
---
import Button from '@ui/Button.astro';
import ButtonList from '@ui/ButtonList.astro';
import Icon from '@ui/Icon.astro';
import Modal from '../modals/Modal.astro';
import CaptureModal from '../CaptureModal.astro';
import ReportModalContent from '../modals/ReportModalContent.astro';
import Button from '@ui/Button.astro'
import ButtonList from '@ui/ButtonList.astro'
import Icon from '@ui/Icon.astro'
import Modal from '../modals/Modal.astro'
import CaptureModal from '../CaptureModal.astro'
import ReportModalContent from '../modals/ReportModalContent.astro'
interface Props {
/** layout-navbar-overlap && layout-navbar-dark → the "New view" button is color="dark" */
dark?: boolean;
/** layout-navbar-overlap && layout-navbar-dark → the "New view" button is color="dark" */
dark?: boolean
}
const { dark } = Astro.props;
const { dark } = Astro.props
---
<!-- BEGIN HEADER ACTIONS -->
<ButtonList>
<span class="d-none d-sm-inline">
<Button text="New view" color={dark ? 'dark' : undefined} />
</span>
{/* Button.astro doesn't support modal-id (data-bs-toggle/target). */}
<a
href="#"
class="btn btn-primary d-none d-sm-inline-block"
data-bs-toggle="modal"
data-bs-target="#modal-report"
>
<Icon name="plus" />
Create new report
</a>
<a
href="#"
class="btn btn-primary d-sm-none btn-icon"
data-bs-toggle="modal"
data-bs-target="#modal-report"
aria-label="Create new report"
>
<Icon name="plus" />
</a>
<span class="d-none d-sm-inline">
<Button text="New view" color={dark ? 'dark' : undefined} />
</span>
{/* Button.astro doesn't support modal-id (data-bs-toggle/target). */}
<a href="#" class="btn btn-primary d-none d-sm-inline-block" data-bs-toggle="modal" data-bs-target="#modal-report">
<Icon name="plus" />
Create new report
</a>
<a href="#" class="btn btn-primary d-sm-none btn-icon" data-bs-toggle="modal" data-bs-target="#modal-report" aria-label="Create new report">
<Icon name="plus" />
</a>
</ButtonList>
<!-- END HEADER ACTIONS -->
<CaptureModal>
<Modal modalId="report" size="lg" top>
<ReportModalContent />
</Modal>
<Modal modalId="report" size="lg" top>
<ReportModalContent />
</Modal>
</CaptureModal>

Some files were not shown because too many files have changed in this diff Show More