Add quality gates: Prettier for Astro, astro check for shared, unused SCSS variable lint (#2749)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
Paweł Kunaandcopilot-swe-agent[bot] authored and GitHub committed 2026-08-03 00:47:55 +02:00
1 parent 4d04c102d3
commit 8962710163
552 files changed
+18014 -19559

No files matched your search

+39 -50
View File
@@ -11,69 +11,58 @@ const __dirname = dirname(fileURLToPath(import.meta.url))
const docs: string[] = sync(join(__dirname, '..', 'docs', 'pages', '**', '*.mdx'))
async function formatHTML(htmlString: string): Promise<string> {
try {
const formattedHtml = await prettier.format(htmlString, {
parser: 'html',
printWidth: 100,
})
return formattedHtml
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
console.error('Error formatting HTML:', errorMessage)
return htmlString // Return original in case of an error
}
try {
const formattedHtml = await prettier.format(htmlString, {
parser: 'html',
printWidth: 100,
})
return formattedHtml
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
console.error('Error formatting HTML:', errorMessage)
return htmlString // Return original in case of an error
}
}
async function replaceAsync(
str: string,
regex: RegExp,
asyncFn: (...args: string[]) => Promise<string>
): Promise<string> {
const matches = [...str.matchAll(regex)]
async function replaceAsync(str: string, regex: RegExp, asyncFn: (...args: string[]) => Promise<string>): Promise<string> {
const matches = [...str.matchAll(regex)]
const replacements = await Promise.all(
matches.map(async (match: RegExpMatchArray) => asyncFn(...match))
)
const replacements = await Promise.all(matches.map(async (match: RegExpMatchArray) => asyncFn(...match)))
let result = str
matches.forEach((match: RegExpMatchArray, i: number) => {
result = result.replace(match[0], replacements[i])
})
let result = str
matches.forEach((match: RegExpMatchArray, i: number) => {
result = result.replace(match[0], replacements[i])
})
return result
return result
}
async function processFiles(): Promise<void> {
for (const file of docs) {
const oldContent = readFileSync(file, 'utf8')
for (const file of docs) {
const oldContent = readFileSync(file, 'utf8')
// get codeblocks from markdown
const content = await replaceAsync(
oldContent,
/(```([a-z0-9]+).*?\n)(.*?)(```)/gs,
async (m: string, m1: string, m2: string, m3: string, m4: string) => {
if (m2 === 'html') {
let formattedHtml = await formatHTML(m3)
// get codeblocks from markdown
const content = await replaceAsync(oldContent, /(```([a-z0-9]+).*?\n)(.*?)(```)/gs, async (m: string, m1: string, m2: string, m3: string, m4: string) => {
if (m2 === 'html') {
let formattedHtml = await formatHTML(m3)
// remove empty lines
formattedHtml = formattedHtml.replace(/^\s*[\r\n]/gm, '')
// remove empty lines
formattedHtml = formattedHtml.replace(/^\s*[\r\n]/gm, '')
return m1 + formattedHtml.trim() + '\n' + m4
}
return m.trim()
}
)
return m1 + formattedHtml.trim() + '\n' + m4
}
return m.trim()
})
if (content !== oldContent) {
writeFileSync(file, content, 'utf8')
console.log(`Reformatted ${file}`)
}
}
if (content !== oldContent) {
writeFileSync(file, content, 'utf8')
console.log(`Reformatted ${file}`)
}
}
}
processFiles().catch((error) => {
const errorMessage = error instanceof Error ? error.message : String(error)
console.error('Error processing files:', errorMessage)
process.exit(1)
const errorMessage = error instanceof Error ? error.message : String(error)
console.error('Error processing files:', errorMessage)
process.exit(1)
})
+51 -60
View File
@@ -3,72 +3,63 @@ import { fileURLToPath } from 'node:url'
import { defineConfig, type UserConfig } from 'vite'
interface CreateViteConfigOptions {
entry: string
name?: string
fileName: string | ((format: string) => string)
formats: ('es' | 'umd' | 'iife' | 'cjs')[]
outDir: string
banner?: string
minify?: boolean | 'esbuild'
entry: string
name?: string
fileName: string | ((format: string) => string)
formats: ('es' | 'umd' | 'iife' | 'cjs')[]
outDir: string
banner?: string
minify?: boolean | 'esbuild'
}
/**
* Creates a Vite configuration for building libraries
*/
export function createViteConfig({
entry,
name,
fileName,
formats,
outDir,
banner,
minify = false
}: CreateViteConfigOptions): UserConfig {
// Vite 8 (Rolldown) always emits const bindings and no longer accepts the
// Rollup-only generatedCode.constBindings option
const rollupOutput: { banner?: string } = {}
export function createViteConfig({ entry, name, fileName, formats, outDir, banner, minify = false }: CreateViteConfigOptions): UserConfig {
// Vite 8 (Rolldown) always emits const bindings and no longer accepts the
// Rollup-only generatedCode.constBindings option
const rollupOutput: { banner?: string } = {}
// Add banner if provided
if (banner) {
rollupOutput.banner = banner
}
// Add banner if provided
if (banner) {
rollupOutput.banner = banner
}
const config: UserConfig = {
// These are library (JS bundle) builds, not app builds — Vite's default
// behavior of copying <root>/public into outDir on every build is not wanted
// here (and in @tabler/preview, where public/ is itself generated from this
// same outDir, caused unbounded growth across repeated builds).
publicDir: false,
build: {
lib: {
entry: path.resolve(entry),
name: name,
fileName: typeof fileName === 'function' ? fileName : () => fileName,
formats: formats
},
outDir: path.resolve(outDir),
emptyOutDir: false,
sourcemap: true,
rollupOptions: {
output: rollupOutput
},
target: 'es2015',
minify: minify
},
define: {
'process.env.NODE_ENV': '"production"'
},
esbuild: {
target: 'es2015',
tsconfigRaw: {
compilerOptions: {
module: 'ES2020',
target: 'ES2015'
}
}
}
}
const config: UserConfig = {
// These are library (JS bundle) builds, not app builds — Vite's default
// behavior of copying <root>/public into outDir on every build is not wanted
// here (and in @tabler/preview, where public/ is itself generated from this
// same outDir, caused unbounded growth across repeated builds).
publicDir: false,
build: {
lib: {
entry: path.resolve(entry),
name: name,
fileName: typeof fileName === 'function' ? fileName : () => fileName,
formats: formats,
},
outDir: path.resolve(outDir),
emptyOutDir: false,
sourcemap: true,
rollupOptions: {
output: rollupOutput,
},
target: 'es2015',
minify: minify,
},
define: {
'process.env.NODE_ENV': '"production"',
},
esbuild: {
target: 'es2015',
tsconfigRaw: {
compilerOptions: {
module: 'ES2020',
target: 'ES2015',
},
},
},
}
return defineConfig(config)
return defineConfig(config)
}
+6 -20
View File
@@ -9,38 +9,24 @@ import { readFileSync } from 'node:fs'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
interface PackageJson {
version: string
[key: string]: unknown
version: string
[key: string]: unknown
}
const pkg: PackageJson = JSON.parse(
readFileSync(path.join(__dirname, '../core', 'package.json'), 'utf8')
)
const pkg: PackageJson = JSON.parse(readFileSync(path.join(__dirname, '../core', 'package.json'), 'utf8'))
// Create zip instance and add folder
const zip = new AdmZip()
zip.addLocalFolder(path.join(__dirname, '../preview/dist'), 'dashboard')
zip.addLocalFile(
path.join(__dirname, '../shared/static', 'og.png'),
'.',
'preview.png'
)
zip.addLocalFile(path.join(__dirname, '../shared/static', 'og.png'), '.', 'preview.png')
zip.addFile(
'documentation.url',
Buffer.from('[InternetShortcut]\nURL = https://tabler.io/docs')
)
zip.addFile('documentation.url', Buffer.from('[InternetShortcut]\nURL = https://tabler.io/docs'))
// Folder to zip and output path
const outputZipPath = path.join(
__dirname,
'../packages-zip',
`tabler-${pkg.version}.zip`
)
const outputZipPath = path.join(__dirname, '../packages-zip', `tabler-${pkg.version}.zip`)
// Write the zip file
zip.writeZip(outputZipPath)
console.log(`Zipped folder to ${outputZipPath}`)
@@ -0,0 +1,5 @@
---
'@tabler/core': major
---
Remove unused SCSS `!default` variables (deprecated Bootstrap leftovers such as `$variable-prefix`, `$alert-*-scale`, accordion icon/button variables, `$popover-arrow-*`, numbered `$font-size-*` / `$line-height-*` scales and others). Configuring any of the removed variables via `@use ... with (...)` now raises a Sass error — they had no effect on the compiled CSS before. The color shade ladders (`$blue-100`…`$cyan-900`) and per-color maps (`$blues`…`$cyans`) are kept as configurable API. Compiled CSS output is unchanged.
+26 -1
View File
@@ -2,4 +2,29 @@ dist
.tmp
.vscode
.cache
node_modules
node_modules
# generated / build outputs
.astro
coverage
demo
packages-zip
preview/public
preview/tmp-assets
docs/public
# prettier-plugin-astro cannot parse HTML comments or <script> tags inside
# JSX expressions ({cond && (<Fragment><!-- ... --><script>...)}) — these files
# use those constructs on purpose (BEGIN/END markers emitted into demo HTML,
# conditional inline scripts). Re-check when the plugin gains support.
docs/layouts/DocsLayout.astro
shared/components/cards/Card.astro
shared/components/layout/PageHeader.astro
shared/components/navbar/Navbar.astro
shared/ui/Chart.astro
shared/ui/ChartSparkline.astro
shared/ui/Datepicker.astro
shared/ui/InlinePlayer.astro
shared/ui/Map.astro
shared/ui/MapVector.astro
shared/ui/Range.astro
shared/ui/Select.astro
+7
View File
@@ -9,12 +9,19 @@
"tabWidth": 2,
"useTabs": false,
"trailingComma": "all",
"plugins": ["prettier-plugin-astro"],
"overrides": [
{
"files": "*.scss",
"options": {
"parser": "scss"
}
},
{
"files": "*.astro",
"options": {
"parser": "astro"
}
}
]
}
+26 -27
View File
@@ -11,40 +11,39 @@ const __dirname = dirname(fileURLToPath(import.meta.url))
const styles: string[] = sync(join(__dirname, '..', 'dist', 'css', '*.css'))
interface Plugins {
[key: string]: string
[key: string]: string
}
const plugins: Plugins = {
'tabler-flags': 'Flags',
'tabler-flags.rtl': 'Flags RTL',
'tabler-marketing': 'Marketing',
'tabler-marketing.rtl': 'Marketing RTL',
'tabler-payments': 'Payments',
'tabler-payments.rtl': 'Payments RTL',
'tabler-socials': 'Socials',
'tabler-socials.rtl': 'Socials RTL',
'tabler-vendors': 'Vendors',
'tabler-vendors.rtl': 'Vendors RTL',
'tabler-flags': 'Flags',
'tabler-flags.rtl': 'Flags RTL',
'tabler-marketing': 'Marketing',
'tabler-marketing.rtl': 'Marketing RTL',
'tabler-payments': 'Payments',
'tabler-payments.rtl': 'Payments RTL',
'tabler-socials': 'Socials',
'tabler-socials.rtl': 'Socials RTL',
'tabler-vendors': 'Vendors',
'tabler-vendors.rtl': 'Vendors RTL',
}
styles.forEach((file: string) => {
const content = readFileSync(file, 'utf8')
const filename = basename(file)
const pluginKey = Object.keys(plugins).find((plugin: string) => filename.includes(plugin))
const plugin = pluginKey ? plugins[pluginKey] : undefined
const regex = /^(@charset ['"][a-zA-Z0-9-]+['"];?)\n?/i
const content = readFileSync(file, 'utf8')
const filename = basename(file)
const pluginKey = Object.keys(plugins).find((plugin: string) => filename.includes(plugin))
const plugin = pluginKey ? plugins[pluginKey] : undefined
const regex = /^(@charset ['"][a-zA-Z0-9-]+['"];?)\n?/i
let newContent = ''
const bannerText = banner(plugin)
let newContent = ''
const bannerText = banner(plugin)
if (content.match(regex)) {
newContent = content.replace(regex, (m: string, m1: string) => {
return `${m1}\n${bannerText}\n`
})
} else {
newContent = `${bannerText}\n${content}`
}
if (content.match(regex)) {
newContent = content.replace(regex, (m: string, m1: string) => {
return `${m1}\n${bannerText}\n`
})
} else {
newContent = `${bannerText}\n${content}`
}
writeFileSync(file, newContent, 'utf8')
writeFileSync(file, newContent, 'utf8')
})
+50 -51
View File
@@ -12,73 +12,72 @@ const tablerPath = path.join(__dirname, '../scss/_variables.scss')
// Function to extract variable names from SCSS file
function extractVariables(filePath: string): Set<string> {
const content = readFileSync(filePath, 'utf8')
const variables = new Set<string>()
const content = readFileSync(filePath, 'utf8')
const variables = new Set<string>()
// Regex to find SCSS variables
// Looks for patterns like: $variable-name: value
// Includes variables in maps and lists
const variableRegex = /\$([a-zA-Z0-9_-]+)\s*[:=]/g
// Regex to find SCSS variables
// Looks for patterns like: $variable-name: value
// Includes variables in maps and lists
const variableRegex = /\$([a-zA-Z0-9_-]+)\s*[:=]/g
let match: RegExpExecArray | null
while ((match = variableRegex.exec(content)) !== null) {
const varName = match[1]
variables.add(varName)
}
let match: RegExpExecArray | null
while ((match = variableRegex.exec(content)) !== null) {
const varName = match[1]
variables.add(varName)
}
return variables
return variables
}
// Main function
function compareVariables(): void {
console.log('Analyzing Bootstrap variables...')
const bootstrapVars = extractVariables(bootstrapPath)
console.log(`Found ${bootstrapVars.size} variables in Bootstrap\n`)
console.log('Analyzing Bootstrap variables...')
const bootstrapVars = extractVariables(bootstrapPath)
console.log(`Found ${bootstrapVars.size} variables in Bootstrap\n`)
console.log('Analyzing Tabler variables...')
const tablerVars = extractVariables(tablerPath)
console.log(`Found ${tablerVars.size} variables in Tabler\n`)
console.log('Analyzing Tabler variables...')
const tablerVars = extractVariables(tablerPath)
console.log(`Found ${tablerVars.size} variables in Tabler\n`)
// Find variables that are in Bootstrap but not in Tabler
const missingInTabler: string[] = []
for (const varName of bootstrapVars) {
if (!tablerVars.has(varName)) {
missingInTabler.push(varName)
}
}
// Find variables that are in Bootstrap but not in Tabler
const missingInTabler: string[] = []
for (const varName of bootstrapVars) {
if (!tablerVars.has(varName)) {
missingInTabler.push(varName)
}
}
// Sort alphabetically
missingInTabler.sort()
// Sort alphabetically
missingInTabler.sort()
console.log('='.repeat(60))
console.log(`Variables in Bootstrap that are missing in Tabler: ${missingInTabler.length}`)
console.log('='.repeat(60))
console.log('='.repeat(60))
console.log(`Variables in Bootstrap that are missing in Tabler: ${missingInTabler.length}`)
console.log('='.repeat(60))
if (missingInTabler.length === 0) {
console.log('All Bootstrap variables are present in Tabler!')
} else {
console.log('\nList of missing variables:\n')
missingInTabler.forEach((varName: string, index: number) => {
console.log(`${(index + 1).toString().padStart(4)}. $${varName}`)
})
}
if (missingInTabler.length === 0) {
console.log('All Bootstrap variables are present in Tabler!')
} else {
console.log('\nList of missing variables:\n')
missingInTabler.forEach((varName: string, index: number) => {
console.log(`${(index + 1).toString().padStart(4)}. $${varName}`)
})
}
// Optionally: show statistics
console.log('\n' + '='.repeat(60))
console.log('Statistics:')
console.log(` Bootstrap: ${bootstrapVars.size} variables`)
console.log(` Tabler: ${tablerVars.size} variables`)
console.log(` Missing: ${missingInTabler.length} variables`)
console.log(` Coverage: ${((1 - missingInTabler.length / bootstrapVars.size) * 100).toFixed(1)}%`)
console.log('='.repeat(60))
// Optionally: show statistics
console.log('\n' + '='.repeat(60))
console.log('Statistics:')
console.log(` Bootstrap: ${bootstrapVars.size} variables`)
console.log(` Tabler: ${tablerVars.size} variables`)
console.log(` Missing: ${missingInTabler.length} variables`)
console.log(` Coverage: ${((1 - missingInTabler.length / bootstrapVars.size) * 100).toFixed(1)}%`)
console.log('='.repeat(60))
}
// Run analysis
try {
compareVariables()
compareVariables()
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
console.error('Error during analysis:', errorMessage)
process.exit(1)
const errorMessage = error instanceof Error ? error.message : String(error)
console.error('Error during analysis:', errorMessage)
process.exit(1)
}
+18 -19
View File
@@ -9,14 +9,14 @@ import { join, dirname } from 'node:path'
const __dirname = dirname(fileURLToPath(import.meta.url))
interface LibConfig {
npm?: string
js?: string[]
css?: string[]
head?: boolean
npm?: string
js?: string[]
css?: string[]
head?: boolean
}
interface Libs {
[key: string]: LibConfig
[key: string]: LibConfig
}
const libsData = libs as Libs
@@ -24,22 +24,21 @@ const libsData = libs as Libs
emptyDirSync(join(__dirname, '..', 'dist/libs'))
for (const name in libsData) {
const { npm } = libsData[name]
const { npm } = libsData[name]
if (npm) {
const from = join(__dirname, '..', `node_modules/${npm}`)
const to = join(__dirname, '..', `dist/libs/${npm}`)
if (npm) {
const from = join(__dirname, '..', `node_modules/${npm}`)
const to = join(__dirname, '..', `dist/libs/${npm}`)
// create dir in dist/libs
if (!existsSync(to)) {
mkdirSync(to, { recursive: true })
}
// create dir in dist/libs
if (!existsSync(to)) {
mkdirSync(to, { recursive: true })
}
copySync(from, to, {
dereference: true,
})
copySync(from, to, {
dereference: true,
})
console.log(`Successfully copied ${npm}`)
}
console.log(`Successfully copied ${npm}`)
}
}
+94 -95
View File
@@ -8,114 +8,113 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
const configFile = path.join(__dirname, '../../shared/data/sri.json')
interface FileConfig {
file: string
configPropertyName: string
file: string
configPropertyName: string
}
const files: FileConfig[] = [
{
file: 'dist/css/tabler.min.css',
configPropertyName: 'css'
},
{
file: 'dist/css/tabler.rtl.min.css',
configPropertyName: 'css-rtl'
},
{
file: 'dist/css/tabler-flags.min.css',
configPropertyName: 'css-flags'
},
{
file: 'dist/css/tabler-flags.rtl.min.css',
configPropertyName: 'css-flags-rtl'
},
{
file: 'dist/css/tabler-marketing.min.css',
configPropertyName: 'css-marketing'
},
{
file: 'dist/css/tabler-marketing.rtl.min.css',
configPropertyName: 'css-marketing-rtl'
},
{
file: 'dist/css/tabler-payments.min.css',
configPropertyName: 'css-payments'
},
{
file: 'dist/css/tabler-payments.rtl.min.css',
configPropertyName: 'css-payments-rtl'
},
{
file: 'dist/css/tabler-props.min.css',
configPropertyName: 'css-props'
},
{
file: 'dist/css/tabler-props.rtl.min.css',
configPropertyName: 'css-props-rtl'
},
{
file: 'dist/css/tabler-themes.min.css',
configPropertyName: 'css-themes'
},
{
file: 'dist/css/tabler-themes.rtl.min.css',
configPropertyName: 'css-themes-rtl'
},
{
file: 'dist/css/tabler-socials.min.css',
configPropertyName: 'css-socials'
},
{
file: 'dist/css/tabler-socials.rtl.min.css',
configPropertyName: 'css-socials-rtl'
},
{
file: 'dist/css/tabler-vendors.min.css',
configPropertyName: 'css-vendors'
},
{
file: 'dist/css/tabler-vendors.rtl.min.css',
configPropertyName: 'css-vendors-rtl'
},
{
file: 'dist/js/tabler.min.js',
configPropertyName: 'js'
},
{
file: 'dist/js/tabler-theme.min.js',
configPropertyName: 'js-theme'
},
{
file: 'dist/css/tabler.min.css',
configPropertyName: 'css',
},
{
file: 'dist/css/tabler.rtl.min.css',
configPropertyName: 'css-rtl',
},
{
file: 'dist/css/tabler-flags.min.css',
configPropertyName: 'css-flags',
},
{
file: 'dist/css/tabler-flags.rtl.min.css',
configPropertyName: 'css-flags-rtl',
},
{
file: 'dist/css/tabler-marketing.min.css',
configPropertyName: 'css-marketing',
},
{
file: 'dist/css/tabler-marketing.rtl.min.css',
configPropertyName: 'css-marketing-rtl',
},
{
file: 'dist/css/tabler-payments.min.css',
configPropertyName: 'css-payments',
},
{
file: 'dist/css/tabler-payments.rtl.min.css',
configPropertyName: 'css-payments-rtl',
},
{
file: 'dist/css/tabler-props.min.css',
configPropertyName: 'css-props',
},
{
file: 'dist/css/tabler-props.rtl.min.css',
configPropertyName: 'css-props-rtl',
},
{
file: 'dist/css/tabler-themes.min.css',
configPropertyName: 'css-themes',
},
{
file: 'dist/css/tabler-themes.rtl.min.css',
configPropertyName: 'css-themes-rtl',
},
{
file: 'dist/css/tabler-socials.min.css',
configPropertyName: 'css-socials',
},
{
file: 'dist/css/tabler-socials.rtl.min.css',
configPropertyName: 'css-socials-rtl',
},
{
file: 'dist/css/tabler-vendors.min.css',
configPropertyName: 'css-vendors',
},
{
file: 'dist/css/tabler-vendors.rtl.min.css',
configPropertyName: 'css-vendors-rtl',
},
{
file: 'dist/js/tabler.min.js',
configPropertyName: 'js',
},
{
file: 'dist/js/tabler-theme.min.js',
configPropertyName: 'js-theme',
},
]
function generateSRI(): void {
const sriData: Record<string, string> = {}
const sriData: Record<string, string> = {}
for (const { file, configPropertyName } of files) {
try {
const filePath = path.join(__dirname, '..', file)
const data = readFileSync(filePath, 'utf8')
for (const { file, configPropertyName } of files) {
try {
const filePath = path.join(__dirname, '..', file)
const data = readFileSync(filePath, 'utf8')
const algorithm = 'sha384'
const hash = crypto.createHash(algorithm).update(data, 'utf8').digest('base64')
const integrity = `${algorithm}-${hash}`
const algorithm = 'sha384'
const hash = crypto.createHash(algorithm).update(data, 'utf8').digest('base64')
const integrity = `${algorithm}-${hash}`
console.log(`${configPropertyName}: ${integrity}`)
console.log(`${configPropertyName}: ${integrity}`)
sriData[configPropertyName] = integrity
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
console.error(`Error processing ${file}:`, errorMessage)
throw error
}
}
sriData[configPropertyName] = integrity
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
console.error(`Error processing ${file}:`, errorMessage)
throw error
}
}
writeFileSync(configFile, JSON.stringify(sriData, null, 2) + '\n', 'utf8')
writeFileSync(configFile, JSON.stringify(sriData, null, 2) + '\n', 'utf8')
}
try {
generateSRI()
generateSRI()
} catch (error) {
console.error('Failed to generate SRI:', error)
process.exit(1)
console.error('Failed to generate SRI:', error)
process.exit(1)
}
+17 -18
View File
@@ -12,7 +12,7 @@ const toDir = join(__dirname, '..', 'fonts')
// Create fonts directory if it doesn't exist
if (!existsSync(toDir)) {
mkdirSync(toDir, { recursive: true })
mkdirSync(toDir, { recursive: true })
}
// Copy geist-mono fonts
@@ -20,17 +20,17 @@ const monoFrom = join(fromDir, 'geist-mono')
const monoTo = join(toDir, 'geist-mono')
if (existsSync(monoFrom)) {
if (!existsSync(monoTo)) {
mkdirSync(monoTo, { recursive: true })
}
if (!existsSync(monoTo)) {
mkdirSync(monoTo, { recursive: true })
}
copySync(monoFrom, monoTo, {
dereference: true,
})
copySync(monoFrom, monoTo, {
dereference: true,
})
console.log(`Successfully copied geist-mono fonts`)
console.log(`Successfully copied geist-mono fonts`)
} else {
console.warn(`Warning: geist-mono fonts not found at ${monoFrom}`)
console.warn(`Warning: geist-mono fonts not found at ${monoFrom}`)
}
// Copy geist-sans fonts
@@ -38,16 +38,15 @@ const sansFrom = join(fromDir, 'geist-sans')
const sansTo = join(toDir, 'geist-sans')
if (existsSync(sansFrom)) {
if (!existsSync(sansTo)) {
mkdirSync(sansTo, { recursive: true })
}
if (!existsSync(sansTo)) {
mkdirSync(sansTo, { recursive: true })
}
copySync(sansFrom, sansTo, {
dereference: true,
})
copySync(sansFrom, sansTo, {
dereference: true,
})
console.log(`Successfully copied geist-sans fonts`)
console.log(`Successfully copied geist-sans fonts`)
} else {
console.warn(`Warning: geist-sans fonts not found at ${sansFrom}`)
console.warn(`Warning: geist-sans fonts not found at ${sansFrom}`)
}
+15 -15
View File
@@ -1,15 +1,15 @@
export default context => {
return {
map: {
inline: false,
annotation: true,
sourcesContent: true
},
plugins: {
autoprefixer: {
cascade: false
},
rtlcss: context.env === 'RTL'
}
}
}
export default (context) => {
return {
map: {
inline: false,
annotation: true,
sourcesContent: true,
},
plugins: {
autoprefixer: {
cascade: false,
},
rtlcss: context.env === 'RTL',
},
}
}
+10 -11
View File
@@ -16,15 +16,14 @@ const entryPath = path.resolve(__dirname, `../js/${entryFile}`)
const entry = `${entryPath}.ts`
export default createViteConfig({
entry: entry,
name: libraryName,
fileName: (format) => {
const esmSuffix = format === 'es' ? '.esm' : ''
return `${baseName}${esmSuffix}.js`
},
formats: ['es', 'umd'],
outDir: path.resolve(__dirname, '../dist/js'),
banner: bannerText,
minify: false
entry: entry,
name: libraryName,
fileName: (format) => {
const esmSuffix = format === 'es' ? '.esm' : ''
return `${baseName}${esmSuffix}.js`
},
formats: ['es', 'umd'],
outDir: path.resolve(__dirname, '../dist/js'),
banner: bannerText,
minify: false,
})
-6
View File
@@ -88,12 +88,6 @@ $form-invalid-border-color-dark: tint-color($red, 40%) !default;
// Accordion dark
//
$accordion-icon-color-dark: $primary-text-emphasis-dark !default;
$accordion-icon-active-color-dark: $primary-text-emphasis-dark !default;
$accordion-button-icon-dark: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$accordion-icon-color-dark}'><path fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/></svg>") !default;
$accordion-button-active-icon-dark: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$accordion-icon-active-color-dark}'><path fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/></svg>") !default;
//
// Carousel dark
//
+5 -92
View File
@@ -7,13 +7,11 @@
// Prefixes
// $prefix moved to `_settings.scss`
$variable-prefix: bs- !default; // Deprecated in v5.2.0 for the shorter `$prefix`
// Assets Base
$assets-base: '..' !default;
// Base Config
$enable-social-colors: true !default;
$enable-extra-colors: true !default;
$enable-gradients: false !default;
$enable-shadows: true !default;
@@ -22,7 +20,6 @@ $enable-dark-mode: true !default;
$enable-negative-margins: true !default;
// $enable-rfs is declared in `_mixins.scss` (forwarded from rfs with `false !default`)
$enable-cssgrid: true !default;
$enable-caret: true !default;
$enable-rounded: true !default;
$enable-transitions: true !default;
$enable-reduced-motion: true !default;
@@ -104,6 +101,10 @@ $colors: (
// $min-contrast-ratio, $color-contrast-dark and $color-contrast-light moved to `_settings.scss`
// Color shade ladders and per-color maps (Bootstrap parity). Nothing in the
// build consumes them yet, but they are kept as configurable API — hence
// excluded from the unused-variable lint.
// fusv-disable
$blue-100: tint-color($blue, 80%) !default;
$blue-200: tint-color($blue, 60%) !default;
$blue-300: tint-color($blue, 40%) !default;
@@ -323,9 +324,9 @@ $cyans: (
'cyan-800': $cyan-800,
'cyan-900': $cyan-900,
) !default;
// fusv-enable
// Spacers
$spacer-0: 0 !default;
$spacer-1: 0.25rem !default;
$spacer-2: 0.5rem !default;
$spacer-3: 1rem !default;
@@ -380,7 +381,6 @@ $body-emphasis-color: $black !default;
// Typography
$font-google: null !default;
$font-google-monospaced: null !default;
$font-local: null !default;
$font-icons: () !default;
$font-family-sans-serif: ('Geist', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif) !default;
@@ -400,23 +400,6 @@ $icon-sm-size: 1rem !default;
$icon-md-size: 2.5rem !default;
$icon-lg-size: 3.5rem !default;
$font-size-75: 0.75rem !default;
$font-size-100: 0.875rem !default;
$font-size-200: 1rem !default;
$font-size-300: 1.25rem !default;
$font-size-400: 1.5rem !default;
$font-size-500: 1.75rem !default;
$font-size-600: 2rem !default;
$font-size-700: 2.5rem !default;
$line-height-100: 1rem !default;
$line-height-200: 1.25rem !default;
$line-height-300: 1.5rem !default;
$line-height-400: 1.75rem !default;
$line-height-500: 2rem !default;
$line-height-600: 2.5rem !default;
$line-height-700: 3rem !default;
$font-size-root: null !default;
$font-size-base: 0.875rem !default;
$font-size-sm: $font-size-base * 0.875 !default;
@@ -561,13 +544,6 @@ $zindex-levels: (
) !default;
$text-secondary-opacity: 0.7 !default;
$text-secondary-light-opacity: 0.4 !default;
$text-secondary-dark-opacity: 0.8 !default;
$bg-surface: var(--#{$prefix}white) !default;
$bg-surface-secondary: var(--#{$prefix}gray-100) !default;
$bg-surface-tertiary: var(--#{$prefix}gray-50) !default;
$bg-surface-dark: var(--#{$prefix}dark) !default;
$body-text-align: null !default;
$body-bg: $gray-50 !default;
@@ -578,7 +554,6 @@ $body-emphasis-color: $gray-700 !default;
// Note: they were already no-ops here — the `!default` definitions earlier
// in this file always won, so their effective values were $black / $white.
$text-muted: $gray-500 !default;
$text-secondary: $gray-500 !default;
$text-secondary-light: $gray-400 !default;
$text-secondary-dark: $gray-600 !default;
@@ -622,15 +597,8 @@ $link-color: $primary !default;
$link-shade-percentage: 20% !default;
$link-hover-color: shift-color($link-color, $link-shade-percentage) !default;
$link-hover-decoration: null !default;
$stretched-link-pseudo-element: after !default;
$stretched-link-z-index: 1 !default;
// Icon links
$icon-link-gap: 0.375rem !default;
$icon-link-underline-offset: 0.25em !default;
$icon-link-icon-size: 1em !default;
$icon-link-icon-transition: 0.2s ease-in-out transform !default;
$icon-link-icon-transform: translate3d(0.25em, 0, 0) !default;
// Paragraphs
$paragraph-margin-bottom: 1rem !default;
@@ -787,7 +755,6 @@ $border-radius: 6px !default;
$border-radius-lg: 8px !default;
$border-radius-xl: 1rem !default;
$border-radius-xxl: 2rem !default;
$border-radius-2xl: $border-radius-xxl !default; // Deprecated in v5.3.0
$border-radius-pill: 100rem !default;
$border-radiuses: (
@@ -812,7 +779,6 @@ $icon-color: var(--#{$prefix}gray-400) !default;
$code-color: light-dark(var(--#{$prefix}gray-600), var(--#{$prefix}gray-400)) !default;
$code-bg: light-dark(var(--#{$prefix}gray-100), var(--#{$prefix}gray-900)) !default;
$code-font-size: $font-size-reative-sm !default;
$code-line-height: 1.25rem !default;
$pre-padding: 1rem !default;
$pre-bg: var(--#{$prefix}bg-surface-dark) !default;
@@ -893,7 +859,6 @@ $avatar-sizes: (
) !default;
$avatar-border-radius: var(--#{$prefix}border-radius-pill) !default;
$avatar-font-size: $h4-font-size !default;
$avatar-box-shadow: var(--#{$prefix}shadow-border) !default;
$avatar-list-spacing: -0.5;
$link-decoration: none !default;
@@ -902,8 +867,6 @@ $link-hover-decoration: underline !default;
// Horizontal rules
$hr-margin-y: 2rem !default;
$hr-color: inherit !default;
$hr-bg-color: null !default;
$hr-height: null !default;
$hr-border-color: null !default;
$hr-border-width: var(--#{$prefix}border-width) !default;
$hr-opacity: $border-opacity !default;
@@ -1065,15 +1028,10 @@ $overlay-gradient: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.
$alert-padding-y: 0.75rem !default;
$alert-padding-x: 1rem !default;
$alert-margin-bottom: 1rem !default;
$alert-border-radius: var(--#{$prefix}border-radius) !default;
$alert-link-font-weight: var(--#{$prefix}font-weight-semibold) !default;
$alert-border-width: var(--#{$prefix}border-width) !default;
$alert-dismissible-padding-r: $alert-padding-x * 3 !default; // 3x covers width of x plus default padding on either side
$alert-border-color: var(--#{$prefix}border-color-translucent) !default;
$alert-shadow: rgba($dark, 0.04) 0 2px 4px 0 !default;
$alert-bg-scale: -80% !default; // Deprecated in v5.2.0, to be removed in v6
$alert-border-scale: -70% !default; // Deprecated in v5.2.0, to be removed in v6
$alert-color-scale: 40% !default; // Deprecated in v5.2.0, to be removed in v6
$alert-heading-margin-bottom: 0.25rem !default;
$alert-icon-size: 1.25rem !default;
$alert-padding-inline-end: 3rem !default;
@@ -1089,7 +1047,6 @@ $breadcrumb-bg: null !default;
$breadcrumb-divider-color: var(--#{$prefix}gray-500) !default;
$breadcrumb-active-color: inherit !default;
$breadcrumb-divider: string.quote('/') !default;
$breadcrumb-divider-flipped: $breadcrumb-divider !default;
$breadcrumb-border-radius: null !default;
$breadcrumb-link-color: var(--#{$prefix}link-color) !default;
$breadcrumb-active-font-weight: var(--#{$prefix}font-weight-semibold) !default;
@@ -1105,7 +1062,6 @@ $breadcrumb-variants: (
$badge-font-size: $font-size-reative-sm !default;
$badge-font-size-sm: $font-size-reative-xs !default;
$badge-font-size-lg: $font-size-reative-md !default;
$badge-line-height: $code-line-height !default;
$badge-font-weight: var(--#{$prefix}font-weight-medium) !default;
$badge-padding-y: 0.25em !default;
$badge-padding-x: 0.5em !default;
@@ -1156,7 +1112,6 @@ $btn-border-width: $input-btn-border-width !default;
$btn-font-weight: var(--#{$prefix}font-weight-medium) !default;
$btn-box-shadow: var(--#{$prefix}shadow-input) !default;
$btn-focus-width: $input-btn-focus-width !default;
$btn-focus-box-shadow: $input-btn-focus-box-shadow !default;
$btn-disabled-opacity: 0.4 !default;
$btn-active-box-shadow: inset 0 3px 5px rgba($black, 0.125) !default;
@@ -1267,7 +1222,6 @@ $card-border-color: var(--#{$prefix}border-color-translucent) !default;
$card-border-radius: var(--#{$prefix}border-radius-lg) !default;
$card-box-shadow: var(--#{$prefix}shadow-card) !default;
$card-inner-border-radius: subtract($card-border-radius, $card-border-width) !default;
$card-hover-box-shadow: var(--#{$prefix}shadow-card-hover) !default;
$card-cap-padding-y: $card-spacer-y !default;
$card-cap-padding-x: $card-spacer-x !default;
@@ -1276,7 +1230,6 @@ $card-cap-color: inherit !default;
$card-height: null !default;
$card-color: inherit !default;
$card-bg: var(--#{$prefix}bg-surface) !default;
$card-bg-hover: $white !default;
$card-img-overlay-padding: $spacer !default;
$card-group-margin: 1.5rem !default;
@@ -1324,10 +1277,7 @@ $ribbon-bookmark-notch-width: 0.5rem !default;
// Accordion
$accordion-padding-y: 1rem !default;
$accordion-padding-x: 1.25rem !default;
$accordion-color: var(--#{$prefix}body-color) !default;
$accordion-bg: transparent !default;
$accordion-border-width: var(--#{$prefix}border-width) !default;
$accordion-border-color: var(--#{$prefix}border-color-translucent) !default;
$accordion-border-radius: var(--#{$prefix}border-radius) !default;
$accordion-inner-border-radius: subtract($accordion-border-radius, $accordion-border-width) !default;
$accordion-btn-toggle-width: $icon-size !default;
@@ -1336,25 +1286,8 @@ $accordion-body-padding-y: $accordion-padding-y !default;
$accordion-body-padding-x: $accordion-padding-x !default;
$accordion-button-padding-y: $accordion-padding-y !default;
$accordion-button-padding-x: $accordion-padding-x !default;
$accordion-button-color: var(--#{$prefix}body-color) !default;
$accordion-button-bg: transparent !default;
$accordion-transition:
$btn-transition,
border-radius 0.15s ease !default;
$accordion-button-active-bg: transparent !default;
$accordion-button-active-color: inherit !default;
$accordion-button-focus-border-color: $accordion-border-color !default; // Deprecated in v5.3.3
$accordion-button-focus-box-shadow: $btn-focus-box-shadow !default;
$accordion-icon-width: 1rem !default;
$accordion-icon-color: $body-color !default;
$accordion-icon-active-color: $primary-text-emphasis !default;
$accordion-icon-transition: transform 0.2s ease-in-out !default;
$accordion-icon-transform: rotate(-180deg) !default;
$accordion-button-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='#{$accordion-icon-color}' stroke-linecap='round' stroke-linejoin='round'><path d='m2 5 6 6 6-6'/></svg>") !default;
$accordion-button-active-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='#{$accordion-icon-active-color}' stroke-linecap='round' stroke-linejoin='round'><path d='m2 5 6 6 6-6'/></svg>") !default;
$accordion-btn-gap: 0.75rem !default;
$accordion-header-gap: 1rem !default;
$accordion-tabs-gap: 0.75rem !default;
@@ -1398,7 +1331,6 @@ $carousel-indicators-vertical-margin-inline-end: 1rem !default;
// Close
$btn-close-width: 1em !default;
$btn-close-height: $btn-close-width !default;
$btn-close-padding-x: 0.25em !default;
$btn-close-padding-y: $btn-close-padding-x !default;
$btn-close-color: $body-color !default;
@@ -1456,7 +1388,6 @@ $dropdown-item-padding-x: 0.75rem !default;
$dropdown-header-color: $gray-600 !default;
$dropdown-header-padding-x: $dropdown-item-padding-x !default;
$dropdown-header-padding-y: $dropdown-padding-y !default;
$dropdown-header-padding: $dropdown-header-padding-y $dropdown-header-padding-x !default; // Deprecated in v5.2.0
$dropdown-max-width: 25rem !default;
$dropdown-scrollable-height: 13rem !default;
@@ -1495,7 +1426,6 @@ $tooltip-margin: null !default; // TODO: remove this in v6
$tooltip-arrow-width: 0.8rem !default;
$tooltip-arrow-height: 0.4rem !default;
$tooltip-arrow-color: null !default;
// Loader
$loader-size: 2.5rem !default;
@@ -1565,7 +1495,6 @@ $nav-link-transition:
border-color 0.15s ease-in-out !default;
$nav-link-disabled-color: var(--#{$prefix}disabled-color) !default;
$nav-link-focus-box-shadow: $focus-ring-box-shadow !default;
$nav-link-height: $font-size-base * $line-height-base + $nav-link-padding-y * 2 !default;
$nav-link-icon-size: $icon-size !default;
$nav-link-icon-color: color-transparent(var(--#{$prefix}nav-link-color), 0.5) !default;
$nav-link-hover-icon-color: color-transparent(var(--#{$prefix}nav-link-color), 0.8) !default;
@@ -1588,7 +1517,6 @@ $nav-tabs-link-hover-border-color: $nav-tabs-border-color $nav-tabs-border-color
$nav-tabs-link-active-color: var(--#{$prefix}body-color) !default;
$nav-tabs-link-active-bg: var(--#{$prefix}body-bg) !default;
$nav-tabs-link-active-border-color: $nav-tabs-link-hover-border-color !default;
$nav-tabs-bg: var(--#{$prefix}bg-surface-tertiary) !default;
$nav-underline-gap: 1rem !default;
$nav-underline-border-width: 0.125rem !default;
@@ -1626,17 +1554,14 @@ $navbar-dark-hover-color: rgba($white, 0.75) !default;
$navbar-dark-brand-color: $white !default;
$navbar-dark-active-color: $white !default;
$navbar-dark-disabled-color: var(--#{$prefix}disabled-color) !default;
$navbar-dark-active-bg: rgba(255, 255, 255, 0.06) !default;
$navbar-dark-icon-color: $navbar-dark-color !default;
$navbar-dark-toggler-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path stroke='#{$navbar-dark-icon-color}' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>") !default;
$navbar-dark-toggler-border-color: rgba($white, 0.1) !default;
$navbar-dark-brand-hover-color: $navbar-dark-active-color !default;
$navbar-brand-font-size: $h2-font-size !default;
$navbar-brand-height: $navbar-brand-font-size * $line-height-base !default;
$navbar-brand-padding-y: $nav-link-padding-y !default;
$navbar-brand-image-height: 2rem !default;
$navbar-brand-margin-right: 0 !default;
$navbar-brand-margin-end: 1rem !default;
$navbar-brand-font-weight: var(--#{$prefix}font-weight-semibold) !default;
@@ -1683,8 +1608,6 @@ $popover-body-padding-x: 0.5rem !default;
$popover-arrow-width: 1rem !default;
$popover-arrow-height: 0.5rem !default;
$popover-arrow-color: $popover-bg !default;
$popover-arrow-outer-color: var(--#{$prefix}border-color-translucent) !default;
// Footer
$footer-padding-y: 2rem !default;
@@ -1754,7 +1677,6 @@ $status-circle-size: 0.75rem !default;
$steps-border-width: 2px !default;
$steps-color: var(--#{$prefix}primary) !default;
$steps-inactive-color: var(--#{$prefix}border-color) !default;
$steps-margin: 2rem 0 !default;
$steps-dot-size: 0.5rem !default;
$step-item-min-height: 1rem !default;
$steps-counter-dot-size: 1.5rem !default;
@@ -1777,7 +1699,6 @@ $spinner-border-width-sm: 1px !default;
// Tables
$table-bg: transparent !default;
$table-bg-scale-dark: 40% !default;
$table-color: inherit !default;
$table-accent-bg: transparent !default;
@@ -1791,11 +1712,9 @@ $table-border-factor: 0.2 !default;
$table-border-width: var(--#{$prefix}border-width) !default;
$table-border-color: var(--#{$prefix}border-color-translucent) !default;
$table-th-border-color: var(--#{$prefix}border-color-translucent) !default;
$table-th-padding-x: $table-cell-padding-x !default;
$table-th-padding-y: 0.5rem !default;
$table-th-font-weight: null !default;
$table-th-color: var(--#{$prefix}gray-500) !default;
$table-th-bg: var(--#{$prefix}bg-surface-tertiary) !default;
$table-striped-color: $table-color !default;
@@ -1887,8 +1806,6 @@ $list-group-border-radius: var(--#{$prefix}border-radius) !default;
$list-group-item-padding-y: $card-cap-padding-y !default;
$list-group-item-padding-x: $card-cap-padding-x !default;
$list-group-item-bg-scale: -80% !default; // Deprecated in v5.3.0
$list-group-item-color-scale: 40% !default; // Deprecated in v5.3.0
$list-group-hover-bg: $hover-bg !default;
$list-group-active-color: inherit !default;
@@ -1941,7 +1858,6 @@ $form-check-input-box-shadow: $input-box-shadow !default;
$form-check-input-checked-bg-size: 1.25rem !default;
$form-check-input-checked-bg-color: var(--#{$prefix}primary) !default;
$form-check-input-checked-color: var(--#{$prefix}bg-forms) !default;
$form-check-input-checked-bg-repeat: repeat !default;
$form-check-input-checked-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='16' height='16'><path fill='none' stroke='#{$white}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8.5l2.5 2.5l5.5 -5.5'/></svg>") !default;
$form-check-input-checked-bg-image-dark: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='16' height='16'><path fill='none' stroke='#{$body-color}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8.5l2.5 2.5l5.5 -5.5'/></svg>") !default;
@@ -2006,7 +1922,6 @@ $form-switch-focus-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.o
$form-switch-checked-color: white !default;
$form-switch-checked-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-checked-color}'/></svg>") !default;
$form-switch-checked-bg-position: right center !default;
$form-switch-bg-size: auto !default;
// Form range
$form-range-track-width: 100% !default;
@@ -2070,8 +1985,6 @@ $form-feedback-font-style: $form-text-font-style !default;
$form-feedback-valid-color: $success !default;
$form-feedback-invalid-color: $danger !default;
$form-feedback-icon-valid-color: $form-feedback-valid-color !default;
$form-feedback-icon-invalid-color: $form-feedback-invalid-color !default;
$form-feedback-icon-valid: str-replace(
url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='" + $green + "' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='20 6 9 17 4 12'></polyline></svg>"),
'#',
-2
View File
@@ -1,7 +1,5 @@
@use '../config' as *;
$select-color-dropdown: #fff;
$select-color-dropdown-border-top: red;
$input-border-width: 1px;
:root {
+70 -47
View File
@@ -1,55 +1,78 @@
// Rebuild Astro's public directory from source and generated workspace assets.
import { cpSync, existsSync, mkdirSync, rmSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { cpSync, existsSync, mkdirSync, rmSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { setTimeout as sleep } from 'node:timers/promises'
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
const repo = join(root, '..');
const publicDir = join(root, 'public');
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
const repo = join(root, '..')
const publicDir = join(root, 'public')
const copies = [
{
from: join(root, 'assets'),
to: publicDir,
packageName: '@tabler/docs',
requiredFile: join(root, 'assets', 'css', 'docs.css'),
},
{
from: join(repo, 'core', 'dist'),
to: join(publicDir, 'dist'),
packageName: '@tabler/core',
requiredFile: join(repo, 'core', 'dist', 'css', 'tabler.css'),
},
{
// Sourced from preview's isolated tmp-assets/ (not dist/) — dist/ is Astro's own
// build output there, and reading demo assets from it caused unbounded growth
// across repeated builds. See preview/.build/copy-assets.mjs.
from: join(repo, 'preview', 'tmp-assets'),
to: join(publicDir, 'preview'),
packageName: '@tabler/preview',
requiredFile: join(repo, 'preview', 'tmp-assets', 'css', 'demo.css'),
},
{
from: join(repo, 'shared', 'static'),
to: join(publicDir, 'static'),
packageName: 'shared assets',
requiredFile: join(repo, 'shared', 'static', 'logo.svg'),
},
];
{
from: join(root, 'assets'),
to: publicDir,
packageName: '@tabler/docs',
requiredFile: join(root, 'assets', 'css', 'docs.css'),
},
{
// core's own `dev` builds this asynchronously (nodemon-driven watch), so in
// `pnpm run dev` this script can start before it exists — wait for it instead
// of failing outright. See preview/.build/copy-assets.mjs for the same race.
from: join(repo, 'core', 'dist'),
to: join(publicDir, 'dist'),
packageName: '@tabler/core',
requiredFile: join(repo, 'core', 'dist', 'css', 'tabler.css'),
waitForBuild: true,
},
{
// Sourced from preview's isolated tmp-assets/ (not dist/) — dist/ is Astro's own
// build output there, and reading demo assets from it caused unbounded growth
// across repeated builds. See preview/.build/copy-assets.mjs.
from: join(repo, 'preview', 'tmp-assets'),
to: join(publicDir, 'preview'),
packageName: '@tabler/preview',
requiredFile: join(repo, 'preview', 'tmp-assets', 'css', 'demo.css'),
waitForBuild: true,
},
{
from: join(repo, 'shared', 'static'),
to: join(publicDir, 'static'),
packageName: 'shared assets',
requiredFile: join(repo, 'shared', 'static', 'logo.svg'),
},
]
rmSync(publicDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
mkdirSync(publicDir, { recursive: true });
const BUILD_WAIT_TIMEOUT_MS = 120_000
const BUILD_WAIT_POLL_MS = 200
for (const { from, to, packageName, requiredFile } of copies) {
if (!existsSync(from) || !existsSync(requiredFile)) {
throw new Error(`copy-assets: missing ${requiredFile} — build ${packageName} first`);
}
cpSync(from, to, {
recursive: true,
dereference: true,
filter: src => !src.includes('/.vscode') && !src.includes('\\.vscode'),
});
async function waitForBuildOutput(requiredFile, packageName) {
if (existsSync(requiredFile)) return
console.log(`copy-assets: waiting for ${packageName} to finish its first build (${requiredFile})...`)
const deadline = Date.now() + BUILD_WAIT_TIMEOUT_MS
while (!existsSync(requiredFile)) {
if (Date.now() > deadline) {
throw new Error(`copy-assets: timed out waiting for ${requiredFile} — is ${packageName}'s dev/build running?`)
}
await sleep(BUILD_WAIT_POLL_MS)
}
}
console.log('copy-assets: done');
rmSync(publicDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 })
mkdirSync(publicDir, { recursive: true })
for (const { from, to, packageName, requiredFile, waitForBuild } of copies) {
if (waitForBuild) {
await waitForBuildOutput(requiredFile, packageName)
} else if (!existsSync(from) || !existsSync(requiredFile)) {
throw new Error(`copy-assets: missing ${requiredFile} — build ${packageName} first`)
}
cpSync(from, to, {
recursive: true,
dereference: true,
filter: (src) => !src.includes('/.vscode') && !src.includes('\\.vscode'),
})
}
console.log('copy-assets: done')
+2
View File
@@ -25,6 +25,8 @@ export default defineConfig({
'@ui': fileURLToPath(new URL('../shared/ui', import.meta.url)),
// docs-only components (Example, DocsMenu, …)
'@components': fileURLToPath(new URL('./components', import.meta.url)),
// docs-only layouts (DocsLayout + the MDX adapter, referenced by `layout:` front matter)
'@layouts': fileURLToPath(new URL('./layouts', import.meta.url)),
// this package's pages dir — used by @shared/lib/docs-children's glob
'@pages': fileURLToPath(new URL('./pages', import.meta.url)),
},
+3 -3
View File
@@ -4,7 +4,7 @@ import { site } from '@shared/lib/site.ts'
---
<Code
lang="html"
code={`<link rel="stylesheet" href="${site.cdnUrl}/dist/css/tabler.min.css" />
lang="html"
code={`<link rel="stylesheet" href="${site.cdnUrl}/dist/css/tabler.min.css" />
<script src="${site.cdnUrl}/dist/js/tabler.min.js"></script>`}
/>
/>
+8 -6
View File
@@ -3,15 +3,17 @@ import { Code } from 'astro:components'
import { site } from '@shared/lib/site.ts'
interface Props {
plugins: string[],
plugins: string[]
}
const { plugins }: Props = Astro.props
---
<Code
lang="html"
code={plugins.map(plugin => {
return `<link rel="stylesheet" href="${site.cdnUrl}/dist/css/tabler-${plugin}.min.css" />`
}).join('\n')}
/>
lang="html"
code={plugins
.map((plugin) => {
return `<link rel="stylesheet" href="${site.cdnUrl}/dist/css/tabler-${plugin}.min.css" />`
})
.join('\n')}
/>
+13 -11
View File
@@ -1,18 +1,20 @@
---
interface Props {
colors: Record<string, { prop: string; title: string }>;
colors: Record<string, { prop: string; title: string }>
}
const { colors }: Props = Astro.props;
const { colors }: Props = Astro.props
---
<div class="row row-cols-4 row-cols-md-6 g-3 g-md-4">
{Object.values(colors).map((color) => {
return (
<div class="col-2 text-center">
<div class="p-6 rounded border" style={{ backgroundColor: `var(${color.prop})` }}></div>
<div class="small">{color.title}</div>
</div>
)
})}
</div>
{
Object.values(colors).map((color) => {
return (
<div class="col-2 text-center">
<div class="p-6 rounded border" style={{ backgroundColor: `var(${color.prop})` }} />
<div class="small">{color.title}</div>
</div>
)
})
}
</div>
+28 -26
View File
@@ -1,36 +1,38 @@
---
import Icon from "@ui/Icon.astro";
import Icon from '@ui/Icon.astro'
interface Props {
/** When omitted, the card renders as a non-interactive "Coming soon" tile. */
href?: string;
icon?: string;
title: string;
description: string;
/** When omitted, the card renders as a non-interactive "Coming soon" tile. */
href?: string
icon?: string
title: string
description: string
}
const { href, icon, title, description }: Props = Astro.props;
const { href, icon, title, description }: Props = Astro.props
const Tag = href ? 'a' : 'div';
const Tag = href ? 'a' : 'div'
---
<div class="col-md-6">
<Tag href={href} class:list={['card', !href && 'bg-surface-tertiary']}>
<div class="card-body">
<div class="position-relative">
{ !href && <span class="badge position-absolute top-0 end-0">Coming soon</span> }
<div class="row">
{ icon && (
<div class="col-auto">
<Icon name={icon} class="text-secondary" />
</div>
)}
<div class="col">
<div class="h3">{ title }</div>
<p class="text-secondary m-0 fs-4 lh-base">{ description }</p>
</div>
</div>
</div>
</div>
</Tag>
<Tag href={href} class:list={['card', !href && 'bg-surface-tertiary']}>
<div class="card-body">
<div class="position-relative">
{!href && <span class="badge position-absolute top-0 end-0">Coming soon</span>}
<div class="row">
{
icon && (
<div class="col-auto">
<Icon name={icon} class="text-secondary" />
</div>
)
}
<div class="col">
<div class="h3">{title}</div>
<p class="text-secondary m-0 fs-4 lh-base">{description}</p>
</div>
</div>
</div>
</div>
</Tag>
</div>
+9 -12
View File
@@ -3,16 +3,13 @@
---
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="32" fill="none">
<path
fill="currentColor"
d="M78.263 10.957a5.96 5.96 0 0 1 3.158.895c.947.632 1.737 1.474 2.21 2.474.58 1.105.843 2.368.843 3.631 0 1.369-.264 2.527-.79 3.632-.474 1-1.263 1.895-2.21 2.526-.948.58-2 .895-3.106.895a5.45 5.45 0 0 1-2.263-.474c-.684-.316-1.263-.684-1.79-1.21v.158c0 .42-.157.79-.42 1.105-.263.316-.632.421-1.105.421-.79 0-1.474-.632-1.527-1.421V7.536c0-.42.158-.79.421-1.105a1.24 1.24 0 0 1 1.106-.421c.42 0 .789.158 1.105.421.263.316.42.684.42 1.105V12.8c.475-.579 1.053-1 1.685-1.315.684-.369 1.474-.527 2.263-.527ZM51.737 21.852a.82.82 0 0 1 .631.316c.21.21.316.526.316.895 0 .473-.263.842-.737 1.158-.473.315-1.052.473-1.684.473-.947.053-1.842-.21-2.631-.684-.737-.421-1.053-1.421-1.053-2.842V14.22H45.42c-.737.052-1.368-.58-1.421-1.316v-.106c0-.368.158-.736.421-1 .263-.263.632-.368 1-.368h1.158V9.799c0-.42.158-.789.421-1.105a1.24 1.24 0 0 1 1.105-.42c.421 0 .79.157 1.053.42.263.316.42.684.42 1.105v1.632h1.79c.737-.053 1.369.579 1.421 1.316v.105c0 .737-.631 1.368-1.368 1.368h-1.84v6.843c0 .368.105.631.263.79.21.157.421.262.737.262.108 0 .217-.024.359-.057.065-.015.137-.032.22-.048.21-.105.368-.158.579-.158Zm14.737-10.895c.79 0 1.473.632 1.526 1.421v10.79c0 .42-.158.79-.421 1.105-.263.316-.632.421-1.105.421a1.47 1.47 0 0 1-1.053-.42c-.263-.317-.421-.685-.421-1.106a5.664 5.664 0 0 1-1.684 1.263c-.684.316-1.474.526-2.263.526a5.959 5.959 0 0 1-3.158-.894c-.948-.632-1.737-1.474-2.21-2.474a7.842 7.842 0 0 1-.843-3.632c0-1.368.263-2.526.79-3.631.473-1 1.263-1.895 2.21-2.527.947-.579 2-.894 3.105-.894 1.527 0 2.948.526 4.053 1.579 0-.421.158-.79.421-1.106a1.144 1.144 0 0 1 1.053-.42Zm-2.421 10a4.737 4.737 0 0 0 1.052-3c0-1.21-.368-2.21-1.052-3.052a3.316 3.316 0 0 0-2.685-1.21c-1.105 0-1.947.42-2.631 1.21-.684.842-1.053 1.947-1.053 3.052 0 1.21.369 2.21 1 3 .684.79 1.684 1.264 2.684 1.21 1.106 0 2-.42 2.685-1.21Zm16.473 0c.684-.842 1.053-1.947 1.053-3.052 0-1.21-.316-2.21-1-3a3.405 3.405 0 0 0-2.684-1.21c-1.105 0-2 .42-2.684 1.21a4.842 4.842 0 0 0-1.053 3c0 1.21.316 2.21 1.052 3.052.685.79 1.685 1.264 2.685 1.21 1.105 0 1.947-.42 2.631-1.21Zm9.79 2.21c0 .422-.158.79-.421 1.106-.263.316-.684.421-1.105.421a1.47 1.47 0 0 1-1.053-.42c-.263-.317-.421-.685-.421-1.106V7.536c0-.42.158-.79.42-1.105.317-.316.685-.474 1.106-.421.421 0 .79.158 1.053.421.316.316.42.684.42 1.105v15.632Zm14.947-4.525c.316-.264.474-.58.474-1 0-2.369-1.263-4.58-3.105-5.843-1-.579-2.053-.894-3.158-.894-1.158 0-2.21.316-3.21.894a6.563 6.563 0 0 0-2.422 2.527c-.579 1.052-.895 2.316-.895 3.79a6.729 6.729 0 0 0 .843 3.526c.578 1 1.368 1.842 2.42 2.42 1.053.58 2.264.895 3.685.895.842 0 1.631-.158 2.421-.42a6.38 6.38 0 0 0 1.895-1c.473-.316.684-.738.684-1.211 0-.369-.106-.632-.369-.895a1.153 1.153 0 0 0-.894-.368 1.67 1.67 0 0 0-.843.21l-.473.263c-.316.21-.632.316-.948.421-.421.158-.947.21-1.631.21a3.805 3.805 0 0 1-3.842-3.157h8.316c.421 0 .736-.105 1.052-.368Zm-5.947-4.948c.789 0 1.526.263 2.105.737s1 1.105 1.053 1.842v.158h-6.632c.368-1.842 1.526-2.737 3.474-2.737Zm17.21-2.316c-.315-.263-.737-.42-1.263-.42-.789 0-1.579.157-2.263.526-.737.368-1.316.894-1.684 1.579v-.316c0-.421-.105-.79-.421-1.105a1.695 1.695 0 0 0-1.106-.421 1.239 1.239 0 0 0-1.105.42c-.263.316-.421.685-.421 1.106v10.368c0 .421.158.79.421 1.106.316.263.632.42 1.105.42.474 0 .843-.105 1.106-.42.263-.316.421-.685.421-1.106v-6c-.053-.684.105-1.316.368-1.947.211-.474.579-.895 1-1.21.527-.37 1.211-.474 1.842-.316l.316.105c.316.105.579.158.842.158.369.052.684-.106.948-.369.315-.263.421-.631.421-1.158 0-.368-.211-.736-.527-1Z"
/>
<path
fill="#066FD1"
d="M31.288 7.107A8.833 8.833 0 0 0 24.893.712a55.899 55.899 0 0 0-17.786 0A8.833 8.833 0 0 0 .712 7.107a55.899 55.899 0 0 0 0 17.786 8.833 8.833 0 0 0 6.395 6.395c5.895.95 11.89.95 17.786 0a8.833 8.833 0 0 0 6.395-6.395c.95-5.895.95-11.89 0-17.786Z"
/>
<path
fill="#fff"
d="M10.019 22.452a17.5 17.5 0 0 0 5.498-5.449 2.193 2.193 0 0 0 0-2.328c-1.436-2.18-3.27-4.013-5.498-5.4-1.09-.693-2.527-.297-3.22.793-.05 0-.05.05-.05.05a2.278 2.278 0 0 0 .842 3.12c1.14.743 2.13 1.585 3.022 2.626a11.782 11.782 0 0 1-3.022 2.625c-.743.396-1.189 1.189-1.189 2.031a2.397 2.397 0 0 0 3.617 1.932Zm7.727-.644h5.647c1.04 0 1.883-.495 1.883-1.486 0-.99-.843-1.486-1.883-1.486h-5.647c-1.04 0-1.882.495-1.882 1.486 0 .99.842 1.486 1.882 1.486Z"
/>
<path
fill="currentColor"
d="M78.263 10.957a5.96 5.96 0 0 1 3.158.895c.947.632 1.737 1.474 2.21 2.474.58 1.105.843 2.368.843 3.631 0 1.369-.264 2.527-.79 3.632-.474 1-1.263 1.895-2.21 2.526-.948.58-2 .895-3.106.895a5.45 5.45 0 0 1-2.263-.474c-.684-.316-1.263-.684-1.79-1.21v.158c0 .42-.157.79-.42 1.105-.263.316-.632.421-1.105.421-.79 0-1.474-.632-1.527-1.421V7.536c0-.42.158-.79.421-1.105a1.24 1.24 0 0 1 1.106-.421c.42 0 .789.158 1.105.421.263.316.42.684.42 1.105V12.8c.475-.579 1.053-1 1.685-1.315.684-.369 1.474-.527 2.263-.527ZM51.737 21.852a.82.82 0 0 1 .631.316c.21.21.316.526.316.895 0 .473-.263.842-.737 1.158-.473.315-1.052.473-1.684.473-.947.053-1.842-.21-2.631-.684-.737-.421-1.053-1.421-1.053-2.842V14.22H45.42c-.737.052-1.368-.58-1.421-1.316v-.106c0-.368.158-.736.421-1 .263-.263.632-.368 1-.368h1.158V9.799c0-.42.158-.789.421-1.105a1.24 1.24 0 0 1 1.105-.42c.421 0 .79.157 1.053.42.263.316.42.684.42 1.105v1.632h1.79c.737-.053 1.369.579 1.421 1.316v.105c0 .737-.631 1.368-1.368 1.368h-1.84v6.843c0 .368.105.631.263.79.21.157.421.262.737.262.108 0 .217-.024.359-.057.065-.015.137-.032.22-.048.21-.105.368-.158.579-.158Zm14.737-10.895c.79 0 1.473.632 1.526 1.421v10.79c0 .42-.158.79-.421 1.105-.263.316-.632.421-1.105.421a1.47 1.47 0 0 1-1.053-.42c-.263-.317-.421-.685-.421-1.106a5.664 5.664 0 0 1-1.684 1.263c-.684.316-1.474.526-2.263.526a5.959 5.959 0 0 1-3.158-.894c-.948-.632-1.737-1.474-2.21-2.474a7.842 7.842 0 0 1-.843-3.632c0-1.368.263-2.526.79-3.631.473-1 1.263-1.895 2.21-2.527.947-.579 2-.894 3.105-.894 1.527 0 2.948.526 4.053 1.579 0-.421.158-.79.421-1.106a1.144 1.144 0 0 1 1.053-.42Zm-2.421 10a4.737 4.737 0 0 0 1.052-3c0-1.21-.368-2.21-1.052-3.052a3.316 3.316 0 0 0-2.685-1.21c-1.105 0-1.947.42-2.631 1.21-.684.842-1.053 1.947-1.053 3.052 0 1.21.369 2.21 1 3 .684.79 1.684 1.264 2.684 1.21 1.106 0 2-.42 2.685-1.21Zm16.473 0c.684-.842 1.053-1.947 1.053-3.052 0-1.21-.316-2.21-1-3a3.405 3.405 0 0 0-2.684-1.21c-1.105 0-2 .42-2.684 1.21a4.842 4.842 0 0 0-1.053 3c0 1.21.316 2.21 1.052 3.052.685.79 1.685 1.264 2.685 1.21 1.105 0 1.947-.42 2.631-1.21Zm9.79 2.21c0 .422-.158.79-.421 1.106-.263.316-.684.421-1.105.421a1.47 1.47 0 0 1-1.053-.42c-.263-.317-.421-.685-.421-1.106V7.536c0-.42.158-.79.42-1.105.317-.316.685-.474 1.106-.421.421 0 .79.158 1.053.421.316.316.42.684.42 1.105v15.632Zm14.947-4.525c.316-.264.474-.58.474-1 0-2.369-1.263-4.58-3.105-5.843-1-.579-2.053-.894-3.158-.894-1.158 0-2.21.316-3.21.894a6.563 6.563 0 0 0-2.422 2.527c-.579 1.052-.895 2.316-.895 3.79a6.729 6.729 0 0 0 .843 3.526c.578 1 1.368 1.842 2.42 2.42 1.053.58 2.264.895 3.685.895.842 0 1.631-.158 2.421-.42a6.38 6.38 0 0 0 1.895-1c.473-.316.684-.738.684-1.211 0-.369-.106-.632-.369-.895a1.153 1.153 0 0 0-.894-.368 1.67 1.67 0 0 0-.843.21l-.473.263c-.316.21-.632.316-.948.421-.421.158-.947.21-1.631.21a3.805 3.805 0 0 1-3.842-3.157h8.316c.421 0 .736-.105 1.052-.368Zm-5.947-4.948c.789 0 1.526.263 2.105.737s1 1.105 1.053 1.842v.158h-6.632c.368-1.842 1.526-2.737 3.474-2.737Zm17.21-2.316c-.315-.263-.737-.42-1.263-.42-.789 0-1.579.157-2.263.526-.737.368-1.316.894-1.684 1.579v-.316c0-.421-.105-.79-.421-1.105a1.695 1.695 0 0 0-1.106-.421 1.239 1.239 0 0 0-1.105.42c-.263.316-.421.685-.421 1.106v10.368c0 .421.158.79.421 1.106.316.263.632.42 1.105.42.474 0 .843-.105 1.106-.42.263-.316.421-.685.421-1.106v-6c-.053-.684.105-1.316.368-1.947.211-.474.579-.895 1-1.21.527-.37 1.211-.474 1.842-.316l.316.105c.316.105.579.158.842.158.369.052.684-.106.948-.369.315-.263.421-.631.421-1.158 0-.368-.211-.736-.527-1Z"
></path>
<path fill="#066FD1" d="M31.288 7.107A8.833 8.833 0 0 0 24.893.712a55.899 55.899 0 0 0-17.786 0A8.833 8.833 0 0 0 .712 7.107a55.899 55.899 0 0 0 0 17.786 8.833 8.833 0 0 0 6.395 6.395c5.895.95 11.89.95 17.786 0a8.833 8.833 0 0 0 6.395-6.395c.95-5.895.95-11.89 0-17.786Z"></path>
<path
fill="#fff"
d="M10.019 22.452a17.5 17.5 0 0 0 5.498-5.449 2.193 2.193 0 0 0 0-2.328c-1.436-2.18-3.27-4.013-5.498-5.4-1.09-.693-2.527-.297-3.22.793-.05 0-.05.05-.05.05a2.278 2.278 0 0 0 .842 3.12c1.14.743 2.13 1.585 3.022 2.626a11.782 11.782 0 0 1-3.022 2.625c-.743.396-1.189 1.189-1.189 2.031a2.397 2.397 0 0 0 3.617 1.932Zm7.727-.644h5.647c1.04 0 1.883-.495 1.883-1.486 0-.99-.843-1.486-1.883-1.486h-5.647c-1.04 0-1.882.495-1.882 1.486 0 .99.842 1.486 1.882 1.486Z"
></path>
</svg>
+52 -64
View File
@@ -4,87 +4,75 @@
// the tree is frozen in shared/data/docs.json (extracted from the reference
// development docs build).
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>
{/* as in Liquid: 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>
{/* as in Liquid: 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,63 +1,63 @@
---
// Equivalent of docs/navbar.html.
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=".": Liquid `page.url | relative` receives a string, so the filter always returns "." */}
<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=".": Liquid `page.url | relative` receives a string, so the filter always returns "." */}
<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,120 +1,114 @@
---
// Equivalent of docs/pagination.html: child-page cards or next/prev siblings.
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
@@ -4,44 +4,43 @@
// here it comes in as a prop from the consuming page.
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
// Equivalent of `illustrations | size` (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 -->
+5 -5
View File
@@ -1,12 +1,12 @@
---
interface Props {
href: string
title: string
href: string
title: string
}
const { href, title }: Props = Astro.props;
const { href, title }: Props = Astro.props
---
<div class="my-6 text-center">
<a href={href} class="btn btn btn-primary" target="_blank">{title}</a>
</div>
<a href={href} class="btn btn btn-primary" target="_blank">{title}</a>
</div>
+45 -76
View File
@@ -1,99 +1,68 @@
---
// copy button with data-clipboard-text (equivalent of escape_attribute).
// The example code comes from the `html` prop or from the rendered slot.
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 (the code param in Liquid) */
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 (the code param in Liquid) */
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
// equivalent of {% removeemptylines %} around include output (e.g. ui/alert.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">
{/* Astro escapes the attribute value itself (equivalent of escape_attribute) */}
<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">
{/* Astro escapes the attribute value itself (equivalent of escape_attribute) */}
<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-->
+24 -18
View File
@@ -3,24 +3,30 @@ import flags from '@data/flags.json'
---
<table class="table table-vcenter">
<thead>
<tr>
<th class="w-1">Flag</th>
<th>Name</th>
<th class="w-1">Country code</th>
<th>CSS class</th>
</tr>
<thead>
<tr>
<th class="w-1">Flag</th>
<th>Name</th>
<th class="w-1">Country code</th>
<th>CSS class</th>
</tr>
</thead>
<tbody>
{flags.map((flag) => (
<tr>
<td>
<div class={`flag flag-sm flag-country-${ flag.flag }`}></div>
</td>
<td>{ flag.name }</td>
<td><code>{ flag.flag }</code></td>
<td><code>.flag-country-{ flag.flag }</code></td>
</tr>
))}
{
flags.map((flag) => (
<tr>
<td>
<div class={`flag flag-sm flag-country-${flag.flag}`} />
</td>
<td>{flag.name}</td>
<td>
<code>{flag.flag}</code>
</td>
<td>
<code>.flag-country-{flag.flag}</code>
</td>
</tr>
))
}
</tbody>
</table>
</table>
+22 -26
View File
@@ -1,30 +1,26 @@
---
import openSources from '@data/open-source.json';
import openSources from '@data/open-source.json'
---
<table class="table">
<tr>
<th>
Name
</th>
<th>
Description
</th>
<th>
License
</th>
</tr>
{ openSources.map((el) => {
return (
<tr>
<td>
<a href={el.url} target="_blank">
{el.name}
</a>
</td>
<td>{el.description}</td>
<td>{el.license}</td>
</tr>
)
}) }
</table>
<tr>
<th> Name </th>
<th> Description </th>
<th> License </th>
</tr>
{
openSources.map((el) => {
return (
<tr>
<td>
<a href={el.url} target="_blank">
{el.name}
</a>
</td>
<td>{el.description}</td>
<td>{el.license}</td>
</tr>
)
})
}
</table>
+31 -26
View File
@@ -1,33 +1,38 @@
---
import payments from '@data/payments.json';
import payments from '@data/payments.json'
---
<table class="table table-vcenter">
<thead>
<tr>
<th class="w-1">Icon light</th>
<th class="w-1">Icon dark</th>
<th>Provider name</th>
<th>CSS class</th>
</tr>
<thead>
<tr>
<th class="w-1">Icon light</th>
<th class="w-1">Icon dark</th>
<th>Provider name</th>
<th>CSS class</th>
</tr>
</thead>
<tbody>
{payments.map((payment) => {
return (
<tr>
<td>
<div class={`payment payment-sm payment-provider-${ payment.logo }`}></div>
</td>
<td>
<div class={`payment payment-sm payment-provider-${ payment.logo }-dark`}></div>
</td>
<td>{ payment.name }</td>
<td>
<code>.payment-provider-{ payment.logo }</code><br>
<code>.payment-provider-{ payment.logo }-dark</code>
</td>
</tr>
);
})}
{
payments.map((payment) => {
return (
<tr>
<td>
<div class={`payment payment-sm payment-provider-${payment.logo}`} />
</td>
<td>
<div class={`payment payment-sm payment-provider-${payment.logo}-dark`} />
</td>
<td>{payment.name}</td>
<td>
<>
<code>.payment-provider-{payment.logo}</code>
<br />
</>
<code>.payment-provider-{payment.logo}-dark</code>
</td>
</tr>
)
})
}
</tbody>
</table>
</table>
+24 -19
View File
@@ -1,26 +1,31 @@
---
import socials from '@data/socials.json';
import socials from '@data/socials.json'
---
<table class="table table-vcenter">
<thead>
<tr>
<th class="w-1">App</th>
<th>Name</th>
<th>CSS class</th>
</tr>
<thead>
<tr>
<th class="w-1">App</th>
<th>Name</th>
<th>CSS class</th>
</tr>
</thead>
<tbody>
{socials.map((social) => (
<tr>
<td>
<div class={`social social-sm social-app-${ social.file }`}></div>
</td>
<td>{ social.name }</td>
<td>
<code>.social-app-{ social.file }</code><br>
</td>
</tr>
))}
{
socials.map((social) => (
<tr>
<td>
<div class={`social social-sm social-app-${social.file}`} />
</td>
<td>{social.name}</td>
<td>
<>
<code>.social-app-{social.file}</code>
<br />
</>
</td>
</tr>
))
}
</tbody>
</table>
</table>
+1 -1
View File
@@ -4,5 +4,5 @@
---
<div class="steps steps-vertical">
<slot />
<slot />
</div>
+25 -34
View File
@@ -1,45 +1,36 @@
---
import { Code } from 'astro:components';
import packageManagers from '@data/package-managers.json';
import { Code } from 'astro:components'
import packageManagers from '@data/package-managers.json'
interface Props {
name: string;
name: string
}
const { name }: Props = Astro.props;
const { name }: Props = Astro.props
---
<ul class="nav nav-bordered mb-2" role="tablist">
{packageManagers.map((manager, i) => {
return (
<li class="nav-item" role="presentation">
<button
class={`nav-link${i === 0 ? ' active' : ''}`}
id={`${manager.name}-tab`}
data-bs-toggle="tab"
data-bs-target={`#${manager.name}`}
type="button"
role="tab"
aria-controls="home"
aria-selected={i === 0 ? 'true' : 'false'}>
<span set:html={manager.icon.replace('<svg ', '<svg class="me-2" ')} />
{manager.name}
</button>
</li>
);
})}
{
packageManagers.map((manager, i) => {
return (
<li class="nav-item" role="presentation">
<button class={`nav-link${i === 0 ? ' active' : ''}`} id={`${manager.name}-tab`} data-bs-toggle="tab" data-bs-target={`#${manager.name}`} type="button" role="tab" aria-controls="home" aria-selected={i === 0 ? 'true' : 'false'}>
<span set:html={manager.icon.replace('<svg ', '<svg class="me-2" ')} />
{manager.name}
</button>
</li>
)
})
}
</ul>
<div class="tab-content">
{packageManagers.map((manager, i) => {
return (
<div
class={`tab-pane${i === 0 ? ' show active' : ''}`}
id={`${manager.name}`}
role="tabpanel"
aria-labelledby={`${manager.name}-tab`}
>
<Code code={`${manager.command} ${manager.install} ${name}`} lang="bash" />
</div>
);
})}
{
packageManagers.map((manager, i) => {
return (
<div class={`tab-pane${i === 0 ? ' show active' : ''}`} id={`${manager.name}`} role="tabpanel" aria-labelledby={`${manager.name}-tab`}>
<Code code={`${manager.command} ${manager.install} ${name}`} lang="bash" />
</div>
)
})
}
</div>
File renamed without changes.
+65
View File
@@ -0,0 +1,65 @@
---
// Adapter: MDX (layout in frontmatter) → DocsLayout.
// Astro passes { frontmatter, headings, url, file } to the MDX layout — we map it
// onto DocsLayout props, computing the TOC from the headings (equivalent of the `toc` filter).
import DocsLayout from './DocsLayout.astro'
interface MdxHeading {
depth: number
slug: string
text: string
}
interface MdxFrontmatter {
'title'?: string
'seoTitle'?: string
'summary'?: string
'description'?: string
'seoDescription'?: string
'added-in'?: string;
'docs-libs'?: string[]
'hide-pagination'?: boolean
}
const {
frontmatter,
headings = [],
url,
file,
} = Astro.props as {
frontmatter: MdxFrontmatter
headings: MdxHeading[]
url?: string
file?: string
}
// Repo-relative source path for the "Edit this page" link, e.g.
// "docs/pages/ui/components/alert.mdx" (`file` is the absolute path of the MDX page).
const editPath = file?.includes('/docs/pages/') ? file.replace(/^.*\/docs\/pages\//, 'docs/pages/') : undefined
const docsLibs = frontmatter['docs-libs'] ?? []
const toc = headings
.filter((heading) => heading.depth === 2 || heading.depth === 3)
.map((heading) => ({
level: heading.depth as 2 | 3,
text: heading.text,
id: heading.slug,
}))
---
<DocsLayout
title={frontmatter.title ?? ''}
seoTitle={frontmatter.seoTitle}
summary={frontmatter.summary}
description={frontmatter.description}
seoDescription={frontmatter.seoDescription}
addedIn={frontmatter['added-in']}
docsLibs={docsLibs}
hidePagination={frontmatter['hide-pagination']}
editPath={editPath}
toc={toc}
url={url ? `${url.replace(/\.html$/, '').replace(/\/$/, '')}/` : undefined}
>
<slot />
</DocsLayout>
+1 -1
View File
@@ -5,5 +5,5 @@ order: 4
description: Customizable email templates for over 90 clients and devices.
summary: Tabler Emails is a set of 80 eye-catching, customizable HTML templates. They are compatible with over 90 email clients and devices.
seoDescription: Tabler Emails is a collection of 80 premium, customizable HTML templates. They are compatible with over 90 email clients and devices.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
@@ -5,7 +5,7 @@ seoTitle: Tabler Emails - How to use the compiled HTML email templates
description: Learn how to use the compiled HTML email templates from the Tabler Emails package.
summary: The compiled HTML files from the Tabler Emails package are ready to use in your email marketing campaigns. This guide explains how to use them effectively.
seoDescription: The compiled HTML files from the Tabler Emails package are ready to use in your email marketing campaigns. This guide explains how to use them effectively.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
+1 -1
View File
@@ -5,7 +5,7 @@ seoTitle: Tabler Emails - Content of the package
description: Content of the Tabler Emails package.
summary: The Tabler Emails package contains files which can be used by everyone, even without great knowledge of HTML.
seoDescription: todo
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
+1 -1
View File
@@ -4,5 +4,5 @@ seoTitle: Introduction to Tabler Emails
description: Base information about Tabler Emails package.
summary: Tabler Emails is a set of 80 eye-catching, customizable HTML templates. They are compatible with over 90 email clients and devices.
seoDescription: Tabler Emails is a collection of 80 premium, customizable HTML templates. They are compatible with over 90 email clients and devices.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
@@ -5,7 +5,7 @@ seoTitle: Tabler Emails - How to use the source HTML email templates
description: Learn how to use the source HTML email templates from the Tabler Emails package.
summary: The source HTML files from the Tabler Emails package need a bit more work than the compiled ones. Learn how to use them.
seoDescription: The source HTML files from the Tabler Emails package need a bit more work than the compiled ones. Learn how to use them.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
+1 -1
View File
@@ -5,7 +5,7 @@ summary:
developers looking to enhance their projects with high-quality icons.
order: 2
description: Over 5000 pixel-perfect icons for web design and development
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import { site } from '@shared/lib/site'
+1 -1
View File
@@ -2,7 +2,7 @@
title: Astro
description: Tabler Icons library for Astro framework.
summary: Tabler Icons for Astro provides an optimized collection of icons specifically designed for use with Astro. These lightweight and scalable icons are easy to integrate into Astro-based projects.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import TabsPackage from '@components/TabsPackage.astro'
+1 -1
View File
@@ -2,5 +2,5 @@
title: Libraries
description: Explore Tabler Icons libraries.
summary: The libraries section offers various integrations of Tabler Icons for popular frameworks and technologies, making it easy to incorporate icons into any project.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
+1 -1
View File
@@ -2,7 +2,7 @@
title: Preact
description: Tabler Icons library for Preact framework.
summary: Tabler Icons for Preact provides an optimized collection of icons specifically designed for use with Preact. These lightweight and scalable icons are easy to integrate into Preact-based projects.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import TabsPackage from '@components/TabsPackage.astro'
+1 -1
View File
@@ -2,7 +2,7 @@
title: React
description: Tabler Icons library for React framework.
summary: Tabler Icons for React offers a robust set of icons tailored for React applications, providing developers with a seamless way to enhance their user interfaces with high-quality, scalable graphics.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import TabsPackage from '@components/TabsPackage.astro'
+1 -1
View File
@@ -2,7 +2,7 @@
title: SolidJS
description: Tabler Icons library for SolidJS framework.
summary: Tabler Icons for SolidJS is a lightweight library offering a vast selection of high-quality icons. It is designed for seamless integration with SolidJS, enabling developers to build visually appealing interfaces.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import TabsPackage from '@components/TabsPackage.astro'
+1 -1
View File
@@ -2,7 +2,7 @@
title: Svelte
description: Tabler Icons library for Svelte framework.
summary: Tabler Icons for Svelte provides a clean and efficient way to use Tabler's comprehensive icon set in Svelte applications, helping developers deliver polished, user-friendly designs.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import TabsPackage from '@components/TabsPackage.astro'
+1 -1
View File
@@ -2,7 +2,7 @@
title: Vue
description: Tabler Icons library for Vue framework.
summary: Tabler Icons for Vue offers a collection of customizable and scalable icons designed for use in Vue applications, providing a powerful tool for creating modern and engaging interfaces.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import TabsPackage from '@components/TabsPackage.astro'
+1 -1
View File
@@ -2,7 +2,7 @@
title: Webfont
description: Tabler Icons as a webfont.
summary: Tabler Icons as a webfont allows you to easily include icons in your projects using simple CSS classes, offering a lightweight and scalable solution for web development.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import TabsPackage from '@components/TabsPackage.astro'
+1 -1
View File
@@ -2,7 +2,7 @@
title: Figma plugin
description: Use Tabler Icons directly in Figma.
summary: The Tabler Figma plugin allows designers to seamlessly integrate Tabler Icons into their Figma projects, providing quick access to a vast library of customizable icons that enhance the design workflow.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import DownloadButton from '@components/DownloadButton.astro'
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: Plugins
description: Icon plugins for seamless integration.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
+1 -1
View File
@@ -1,7 +1,7 @@
---
title: EPS version
description: Download Tabler Icons in EPS format.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import TabsPackage from '@components/TabsPackage.astro'
+1 -1
View File
@@ -2,5 +2,5 @@
title: Static files
description: Download static file formats of icons.
summary: Static files provide multiple formats of Tabler Icons, including EPS, PDF, PNG, and SVG, offering flexibility for different design and development workflows.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
+1 -1
View File
@@ -1,7 +1,7 @@
---
title: PDF version
description: Download Tabler Icons in PDF format.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import TabsPackage from '@components/TabsPackage.astro'
+1 -1
View File
@@ -1,7 +1,7 @@
---
title: PNG version
description: Download Tabler Icons in PNG format.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import TabsPackage from '@components/TabsPackage.astro'
+1 -1
View File
@@ -1,7 +1,7 @@
---
title: SVG version
description: Download Tabler Icons in SVG format.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import TabsPackage from '@components/TabsPackage.astro'
+1 -1
View File
@@ -3,7 +3,7 @@ title: Tabler Illustrations
order: 3
description: Customizable illustrations for modern web and mobile designs.
summary: Tabler Illustrations is a collection of customizable SVG illustrations for your web project. Explore our library of illustrations to enhance your web development experience.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
# Tabler Illustrations
@@ -2,7 +2,7 @@
title: Contents
description: Explore Tabler Illustrations folder structure
summary: The Tabler Illustrations package is thoughtfully structured to provide designers and developers with an array of high-quality assets. This guide explores the various folders and their contents, helping users make the most of these resources.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
@@ -2,7 +2,7 @@
title: Customization
description: Customize the illustrations to match your brand.
summary: Learn how to tailor Tabler Illustrations by adjusting colors, sizes, and formats. This section provides insights into seamlessly integrating illustrations to align with your design and branding.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
@@ -2,5 +2,5 @@
title: Introduction
description: Introduction to Tabler Illustrations and their key features.
summary: Tabler Illustrations is a collection of high-quality, customizable illustrations designed to enhance the visual appeal of your projects. These illustrations align seamlessly with the Tabler design system, making it easy to create engaging and cohesive designs for websites, apps, and presentations
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
@@ -1,6 +1,6 @@
---
title: Tabler Illustrations License
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
@@ -1,7 +1,7 @@
---
title: Preview
summary: 'Tabler Illustrations offers 80 illustrations in two themes: light and dark. You can use them in your projects to enhance the visual appeal and convey messages effectively.'
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import illustrations from '@data/illustrations.json'
+1 -1
View File
@@ -2,7 +2,7 @@
title: Welcome to Tabler Documentation
summary: Tabler Docs provides a comprehensive guide to help you get started with the Tabler ecosystem, including its UI components, plugins, and icons. Explore detailed documentation to understand and leverage the full potential of Tabler in your projects.
description: Find comprehensive guides, examples, and resources to help you use Tabler UI components, plugins, icons, and tools effectively in your projects.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Card from '@components/DocsCard.astro'
+1 -1
View File
@@ -4,7 +4,7 @@ summary:
The choice of colors for a website or app interface has a big influence on how users interact with the product and what decisions they make. Harmonious colors can contribute to a nice first impression and encourage users to engage with your product, so it's a very important aspect of a successful design, which needs
to be well thought out.
description: Impact of colors on user interface design.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Colors from '@components/Colors.astro'
+1 -1
View File
@@ -3,5 +3,5 @@ title: Base
order: 2
description: Foundational elements for UI design.
summary: The base section includes foundational elements such as colors, typography, and spacing that form the building blocks of a cohesive and consistent user interface.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
+1 -1
View File
@@ -4,7 +4,7 @@
// so the include gets an empty url and the target collapses to `{{ page | relative }}`.
// For this 3-deep page that is "../../..", which is exactly what the reference
// build (ui/base/markdown/index.html) redirects to.
import RedirectLayout from '@shared/layouts/RedirectLayout.astro';
import RedirectLayout from '@shared/layouts/RedirectLayout.astro'
---
<RedirectLayout base="../../.." />
+1 -1
View File
@@ -2,7 +2,7 @@
title: Prose
summary: Use the `.prose` wrapper to style long-form content without adding classes to every element.
description: Long-form content styles for markdown-like HTML.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
import Prose from '@ui/Prose.astro';
+1 -1
View File
@@ -2,7 +2,7 @@
title: Typography
summary: Typography plays an important role in creating an attractive and clear interface design. Good typography will make the content easy to follow and improve the usability of your website.
description: Role of typography in interface design.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
import Prose from '@ui/Prose.astro';
+1 -1
View File
@@ -2,7 +2,7 @@
title: Accordion
summary: Use accordion panels to group related content and show one section at a time.
description: Build collapsible content sections with accordion classes and modifiers.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
import Icon from '@ui/Icon.astro';
+1 -1
View File
@@ -2,7 +2,7 @@
title: Alert
summary: An alert message is used to inform users about the status of their action and help them solve problems that may occur. Good alert design is important for the overall user experience of a website or app.
description: An alert message for user notification.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
import Alert from '@ui/Alert.astro';
+1 -1
View File
@@ -3,7 +3,7 @@ title: Autosize
summary: The autosize element will automatically adjust the textarea height and make it easier for users to follow as they type.
docs-libs: autosize
description: Auto-adjusting textarea for better usability.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
+1 -1
View File
@@ -2,7 +2,7 @@
title: Avatar
summary: An avatar helps customize interface elements and make the product experience more personalized. It is often used in communication apps, collaboration tools, and social media.
description: Personalize UI with a user avatar.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
import Icon from '@ui/Icon.astro';
+1 -1
View File
@@ -2,7 +2,7 @@
title: Badge
summary: A badge is a small count and labeling component used to add extra information to an interface element. You can use it to draw user attention to a new element, notify about unread messages, or provide additional context.
description: Add extra information with a badge.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import BadgesList from '@ui/BadgesList.astro';
import Example from '@components/Example.astro';
+1 -1
View File
@@ -2,7 +2,7 @@
title: Breadcrumb
summary: A breadcrumb is used to show the current website or app location and reduce the number of actions users need to take. It helps users navigate the website hierarchy and better understand its structure.
description: A navigation aid for better structure.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
import Breadcrumb from '@ui/Breadcrumb.astro';
+1 -1
View File
@@ -2,7 +2,7 @@
title: Button
summary: Use a button style that best suits your design and encourages users to take the desired action. You can customize button properties to improve user experience by changing size, shape, color, and more.
description: A customizable button for user actions.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import ButtonGroup from '@ui/ButtonGroup.astro';
import Example from '@components/Example.astro';
+1 -1
View File
@@ -2,7 +2,7 @@
title: Card gradient
summary: Card gradients add rich color backgrounds to cards and help emphasize key information in dashboards and marketing sections.
description: Build eye-catching cards with gradient variants, directions, and animated backgrounds.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro'
+1 -1
View File
@@ -2,7 +2,7 @@
title: Card
summary: A card is a flexible user interface element that helps organize content into meaningful sections and display it across different screen sizes. It can contain smaller elements such as images, text, links, and buttons, and can act as an entry point to more detailed information.
description: Organize content with a flexible card.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
import Icon from '@ui/Icon.astro';
+1 -1
View File
@@ -2,7 +2,7 @@
title: Carousel
summary: A carousel is used to display multiple pieces of visual content without taking up too much space. It eliminates the need to scroll down the page to see all content and is a popular method of presenting marketing information.
description: Display visual content with a carousel.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
+1 -1
View File
@@ -3,7 +3,7 @@ title: Chart
docs-libs: [apexcharts]
summary: Tabler uses ApexCharts - a free and open-source modern charting library that helps developers to create beautiful and interactive visualizations for web pages.
description: Interactive data visualizations with ApexCharts.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro'
+1 -1
View File
@@ -3,7 +3,7 @@ title: Countup
summary: A countup element is used to display numerical data in an interesting way and make the interface more interactive.
docs-libs: countup
description: Display numbers dynamically with a countup.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
+1 -1
View File
@@ -2,7 +2,7 @@
title: Data grid
summary: Use the data grid component to display detailed information about your product. The data is displayed as a column of items consisting of a title and content.
description: Detailed product information in grids.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
+1 -1
View File
@@ -2,7 +2,7 @@
title: Divider
summary: Dividers help organize content and make the interface layout clear and uncluttered. Greater clarity adds up to better user experience and enhanced interaction with a website or app.
description: Separate content with clear dividers.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
+1 -1
View File
@@ -2,7 +2,7 @@
title: Dropdown
summary: A dropdown is used to display a list of options or include more items in a menu without overwhelming users with too many buttons and long lists. It improves interaction with your website or software and keeps the interface clear.
description: Organize options with a dropdown menu.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
+1 -1
View File
@@ -3,7 +3,7 @@ title: Dropzone
summary: Dropzone is a simple JavaScript library that helps you add file drag and drop functionality to your web forms. It is one of the most popular drag and drop libraries on the web and is used by millions of people.
description: Drag-and-drop file upload tool.
docs-libs: dropzone
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
+1 -1
View File
@@ -2,7 +2,7 @@
title: Empty State
summary: An empty state or blank page is commonly used as a placeholder for first-use, empty data, or an error screen. Its goal is to engage users when there is no content to display, which makes its design important for the overall user experience of your website or app.
description: Engage users in an empty or error screen.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
import Illustration from '@ui/Illustration.astro';
+1 -1
View File
@@ -3,7 +3,7 @@ title: Icon
summary: Use an icon from over 5000 options created specifically for Tabler to make your dashboard more attractive. Each icon is available under the MIT license, so it can be used in both private and commercial projects.
banner: icons
description: Enhance a dashboard with a custom icon.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
import Icon from '@ui/Icon.astro';
+1 -1
View File
@@ -3,5 +3,5 @@ title: Components
order: 4
description: Various components to enhance UI.
summary: Tabler UI includes a variety of components to help you build web applications that are both functional and visually appealing. From buttons and cards to modals and navigation, these components provide a wide range of features to enhance your site.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
+1 -1
View File
@@ -3,7 +3,7 @@ title: Inline player
docs-libs: plyr
summary: A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo media player that supports modern browsers.
description: Lightweight media player for websites.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
import { Code } from 'astro:components';
+1 -1
View File
@@ -2,7 +2,7 @@
title: Modal
summary: Use Bootstrap’s JavaScript modal plugin to add dialogs to your site for lightboxes, user notifications, or completely custom content.
description: Dialogs for notifications and content.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
import Icon from '@ui/Icon.astro';
+1 -1
View File
@@ -1,6 +1,6 @@
---
title: Offcanvas
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
+1 -1
View File
@@ -1,7 +1,7 @@
---
title: Pagination
summary: Pagination is a user interface element that allows users to navigate through a set of data or content that is divided into multiple pages. It is commonly used in web applications, blogs, and e-commerce sites to display large amounts of information in a manageable way.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
import Pagination from '@ui/Pagination.astro';
+1 -1
View File
@@ -2,7 +2,7 @@
title: Placeholder
summary: Placeholder is used to reserve space for content that will soon appear in a layout.
description: Reserve space for upcoming content.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
+1 -1
View File
@@ -2,7 +2,7 @@
title: Popover
summary: A popover is used to provide additional information for elements where a simple tooltip is not sufficient.
description: Provide extra information with a popover.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
+1 -1
View File
@@ -3,7 +3,7 @@ title: Progress Steps
summary: A progress step helps users track their place in a short process by breaking it into clear, simple steps. This makes flows like setup or onboarding easier to follow and finish.
new: true
description: Use progress steps to display compact onboarding, checkout, and setup progress.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
import ProgressSteps from '@ui/ProgressSteps.astro';
+1 -1
View File
@@ -2,7 +2,7 @@
title: Progress Bar
summary: A progress bar is used to provide feedback on an action status and inform users about current progress. Although it is a small interface element, it is extremely helpful in managing user expectations and preventing abandonment of an initiated process.
description: Track and display progress with a progress bar.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
import ProgressBg from '@ui/ProgressBg.astro';
+1 -1
View File
@@ -3,7 +3,7 @@ title: Range slider
docs-libs: nouislider
description: Adjust values with a range slider.
summary: A range slider allows users to select a range of values by adjusting two handles along a track, providing an intuitive and space-efficient input method.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
+1 -1
View File
@@ -2,7 +2,7 @@
title: Ribbon
summary: A ribbon is a graphical element that attracts user attention to a given interface element and makes it stand out.
description: Highlight an element with a graphical ribbon.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
import Icon from '@ui/Icon.astro';
@@ -1,7 +1,7 @@
---
title: Segmented Control
summary: A segmented control is a set of two or more segments, each of which functions as a mutually exclusive button. A segmented control is used to display a set of mutually exclusive options.
layout: '@shared/layouts/DocsMdxLayout.astro'
layout: '@layouts/DocsMdxLayout.astro'
---
import Example from '@components/Example.astro';
import Icon from '@ui/Icon.astro';
Loaded 100 of 552 files, more files were not shown because too many files have changed in this diff. Show more