Merge branch 'dev' of https://github.com/tabler/tabler into dev-docs-improvements-2

This commit is contained in:
codecalm
2026-01-09 22:15:39 +01:00
201 changed files with 4476 additions and 3427 deletions

View File

@@ -1,6 +1,10 @@
>= 1%
last 2 versions
Firefox ESR
>= 0.5%
last 2 major versions
not dead
safari >= 15.4
iOS >= 15.4
Chrome >= 120
Firefox >= 121
iOS >= 15.6
Safari >= 15.6
not Explorer <= 11
Samsung >= 23
not kaios <= 2.5

View File

@@ -1,63 +0,0 @@
#!/usr/bin/env node
'use strict'
import { readFileSync, writeFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url'
import { sync } from 'glob';
import * as prettier from "prettier";
const __dirname = dirname(fileURLToPath(import.meta.url))
const docs = sync(join(__dirname, '..', 'docs', '**', '*.md'))
async function formatHTML(htmlString) {
try {
const formattedHtml = await prettier.format(htmlString, {
parser: "html",
printWidth: 100,
});
return formattedHtml;
} catch (error) {
console.error("Error formatting HTML:", error);
return htmlString; // Return original in case of an error
}
}
async function replaceAsync(str, regex, asyncFn) {
const matches = [...str.matchAll(regex)];
const replacements = await Promise.all(
matches.map(async (match) => asyncFn(...match))
);
let result = str;
matches.forEach((match, i) => {
result = result.replace(match[0], replacements[i]);
});
return result;
}
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, m1, m2, m3, m4) => {
if (m2 === 'html') {
m3 = await formatHTML(m3);
// remove empty lines
m3 = m3.replace(/^\s*[\r\n]/gm, '');
return m1 + m3.trim() + "\n" + m4;
}
return m.trim();
})
if (content !== oldContent) {
writeFileSync(file, content, 'utf8')
console.log(`Reformatted ${file}`)
}
}

79
.build/reformat-mdx.ts Normal file
View File

@@ -0,0 +1,79 @@
#!/usr/bin/env node
import { readFileSync, writeFileSync } from 'node:fs'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { sync } from 'glob'
import * as prettier from 'prettier'
const __dirname = dirname(fileURLToPath(import.meta.url))
const docs: string[] = sync(join(__dirname, '..', 'docs', '**', '*.md'))
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
}
}
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))
)
let result = str
matches.forEach((match: RegExpMatchArray, i: number) => {
result = result.replace(match[0], replacements[i])
})
return result
}
async function processFiles(): Promise<void> {
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)
// remove empty lines
formattedHtml = formattedHtml.replace(/^\s*[\r\n]/gm, '')
return m1 + formattedHtml.trim() + '\n' + m4
}
return m.trim()
}
)
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)
})

View File

@@ -0,0 +1,76 @@
import path from 'node:path'
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'
}
/**
* Creates a Vite configuration for building libraries
*/
export function createViteConfig({
entry,
name,
fileName,
formats,
outDir,
banner,
minify = false
}: CreateViteConfigOptions): UserConfig {
const rollupOutput: {
generatedCode: {
constBindings: boolean
}
banner?: string
} = {
generatedCode: {
constBindings: true
}
}
// Add banner if provided
if (banner) {
rollupOutput.banner = banner
}
const config: UserConfig = {
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)
}

View File

@@ -1,30 +0,0 @@
#!/usr/bin/env node
import AdmZip from 'adm-zip';
import path from 'path';
import { fileURLToPath } from 'url';
import { readFileSync } from 'fs';
// Get __dirname in ESM
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const pkg = 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, '../preview/static', 'og.png'), '.', 'preview.png');
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`);
// Write the zip file
zip.writeZip(outputZipPath);
console.log(`Zipped folder to ${outputZipPath}`);

46
.build/zip-package.ts Normal file
View File

@@ -0,0 +1,46 @@
#!/usr/bin/env node
import AdmZip from 'adm-zip'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { readFileSync } from 'node:fs'
// Get __dirname in ESM
const __dirname = path.dirname(fileURLToPath(import.meta.url))
interface PackageJson {
version: string
[key: string]: unknown
}
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, '../preview/static', 'og.png'),
'.',
'preview.png'
)
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`
)
// Write the zip file
zip.writeZip(outputZipPath)
console.log(`Zipped folder to ${outputZipPath}`)

View File

@@ -0,0 +1,5 @@
---
"@tabler/core": patch
---
Refactored Bootstrap exports to use single source of truth in `bootstrap.js` and removed duplicate exports from `tabler.js` for better maintainability.

View File

@@ -0,0 +1,6 @@
---
"@tabler/core": minor
---
Added `.card-gradient` component with gradient variants, direction modifiers, and animated backgrounds.

View File

@@ -0,0 +1,6 @@
---
"@tabler/preview": minor
---
Added new `card-gradients.html` page showcasing various gradient card styles and components.

View File

@@ -0,0 +1,5 @@
---
"@tabler/core": patch
---
Updated `$border-color-translucent-dark` from `rgba(72, 110, 149, 0.14)` to `rgba(128, 150, 172, 0.2)` to improve visibility of form checkboxes and other form elements in dark mode.

View File

@@ -0,0 +1,5 @@
---
"@tabler/core": patch
---
Fixed status color classes to use CSS variables instead of hardcoded values and include social colors (bitbucket, facebook, etc.) in status class generation.

View File

@@ -0,0 +1,5 @@
---
"@tabler/core": patch
---
Fixed white space on left side when scrollbar is present by replacing `margin-inline-start: calc(100vw - 100%)` with `scrollbar-gutter: stable` on `html` element, with `overflow-y: scroll` fallback for unsupported browsers.

View File

@@ -0,0 +1,7 @@
---
"@tabler/core": minor
"@tabler/docs": patch
---
Added Geist font family integration.

View File

@@ -0,0 +1,6 @@
---
"@tabler/core": patch
---
Added `border-top-left-radius` and `border-top-right-radius` to first and last child elements in `.card-table` for proper corner rounding.

View File

@@ -0,0 +1,6 @@
---
"@tabler/core": patch
---
Migrated `rgba()` functions to modern CSS color functions (`color-mix()` and `color-transparent()`) for better browser support and cleaner code. Replaced `rgba(var(--#{$prefix}*-rgb), ...)` with `color-mix(in srgb, var(--#{$prefix}*) ..., transparent)`, static percentage `color-mix()` with `color-transparent()`, and `rgba($variable, ...)` with `color-transparent($variable, ...)`.

View File

@@ -0,0 +1,8 @@
---
"@tabler/core": minor
"@tabler/preview": minor
"@tabler/docs": minor
---
Migrated build system from Rollup to Vite across all packages. Replaced `rollup.config.mjs` with `vite.config.mjs` and updated build scripts to use `vite build` instead of `rollup`. Build outputs remain identical (UMD and ESM formats) with no breaking changes for end users.

View File

@@ -0,0 +1,6 @@
---
"@tabler/core": patch
---
Removed redundant nullish coalescing operator from `html` option in popover and tooltip initialization.

View File

@@ -0,0 +1,7 @@
---
"@tabler/core": patch
"@tabler/preview": patch
---
Added Driver.js library integration and Tour demo page for interactive product tours and onboarding guides.

View File

@@ -0,0 +1,6 @@
---
"@tabler/preview": patch
---
Updated `@tabler/icons` to v3.36.1.

View File

@@ -0,0 +1,7 @@
---
"@tabler/core": minor
"@tabler/preview": minor
---
Upgraded `apexcharts` from `3.54.1` to `5.3.6` and added CSS variables (`--chart-{id}-color-{index}`) for dynamic chart colors to fix compatibility with the new version.

View File

@@ -1,68 +0,0 @@
name: Argos Tests
on:
push:
branches:
- dev
pull_request:
paths:
- 'preview/**/*.js'
- 'preview/**/*.html'
- 'preview/**/*.scss'
- 'core/**/*.js'
- 'core/**/*.scss'
env:
NODE: 20
permissions:
contents: read
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
# if: github.event.pull_request.draft == false
if: false
steps:
- name: Clone repository
uses: actions/checkout@v5
- name: Cache turbo build setup
uses: actions/cache@v4
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
- name: Install PNPM
uses: pnpm/action-setup@v4
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: "${{ env.NODE }}"
cache: 'pnpm'
- name: Get installed Playwright version
id: playwright-version
run: echo "PLAYWRIGHT_VERSION=$(node -e "console.log(require('./package.json').devDependencies['@playwright/test'])")" >> $GITHUB_ENV
- name: Cache playwright binaries
uses: actions/cache@v4
id: playwright-cache
with:
path: |
~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }}
- name: Install pnpm dependencies
run: pnpm install
- name: Install Playwright Browsers
run: pnpm exec playwright install --with-deps
if: steps.playwright-cache.outputs.cache-hit != 'true'
- name: Run Playwright tests
run: pnpm run playwright

View File

@@ -9,7 +9,7 @@ on:
env:
FORCE_COLOR: 2
NODE: 20
NODE: 22
jobs:
bundlewatch:
@@ -17,10 +17,10 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Cache turbo build setup
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
persist-credentials: false

32
.github/workflows/lint.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Lint
on:
pull_request: null
env:
NODE: 22
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v6
- name: Install PNPM
uses: pnpm/action-setup@v4
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: "${{ env.NODE }}"
cache: 'pnpm'
- name: Install pnpm dependencies
run: pnpm install
- name: Lint Markdown
run: pnpm run lint

View File

@@ -12,7 +12,7 @@ jobs:
name: Verify lock file integrity
steps:
- name: Clone Tabler
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Prevent lock file change
uses: xalvarez/prevent-file-change-action@v3
with:

View File

@@ -21,7 +21,7 @@ jobs:
pull-requests: write # to create pull request
steps:
- name: Checkout Repo
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Install PNPM
uses: pnpm/action-setup@v4

View File

@@ -4,7 +4,7 @@ on:
pull_request: null
env:
NODE: 20
NODE: 22
permissions:
contents: read
@@ -14,10 +14,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Cache turbo build setup
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}

45
.github/workflows/type-check.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: Type Check
on:
pull_request: null
push:
branches:
- main
- dev
env:
NODE: 20
permissions:
contents: read
jobs:
type-check:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v6
- name: Cache turbo build setup
uses: actions/cache@v4
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
- name: Install PNPM
uses: pnpm/action-setup@v4
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: "${{ env.NODE }}"
cache: 'pnpm'
- name: Install pnpm dependencies
run: pnpm install
- name: Run type-check
run: pnpm run type-check

7
.gitignore vendored
View File

@@ -36,4 +36,9 @@ package-lock.json
demo/
dist/
packages-zip/
.env
.env
sri.json
# TypeScript
*.tsbuildinfo
.tsbuildinfo

61
.markdownlint.json Normal file
View File

@@ -0,0 +1,61 @@
{
"default": true,
"MD001": {
"level": 2
},
"MD003": {
"style": "atx"
},
"MD004": {
"style": "dash"
},
"MD007": {
"indent": 2
},
"MD009": {
"br_spaces": 2
},
"MD010": false,
"MD012": {
"maximum": 2
},
"MD013": {
"line_length": 120,
"code_blocks": false,
"tables": false,
"headings": false,
"headings_line_length": 120
},
"MD022": true,
"MD025": {
"front_matter_title": ""
},
"MD026": {
"punctuation": ".,;:!"
},
"MD030": {
"ul_single": 1,
"ul_multi": 1,
"ol_single": 1,
"ol_multi": 1
},
"MD031": true,
"MD032": true,
"MD033": false,
"MD034": false,
"MD035": {
"style": "---"
},
"MD036": false,
"MD037": true,
"MD038": true,
"MD039": true,
"MD040": true,
"MD041": {
"front_matter_title": ""
},
"MD046": {
"style": "fenced"
},
"MD047": true
}

2
.nvmrc
View File

@@ -1 +1 @@
20
22

View File

@@ -0,0 +1 @@
../../..

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2018-2025 The Tabler Authors
Copyright (c) 2018-2026 The Tabler Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,18 +1,20 @@
#!/usr/bin/env node
'use strict'
import { readFileSync, writeFileSync } from 'node:fs';
import { join, dirname, basename } from 'node:path';
import { readFileSync, writeFileSync } from 'node:fs'
import { join, dirname, basename } from 'node:path'
import { fileURLToPath } from 'node:url'
import { sync } from 'glob';
import banner from '../../shared/banner/index.mjs';
import { sync } from 'glob'
import banner from '../../shared/banner/index.mjs'
const __dirname = dirname(fileURLToPath(import.meta.url))
const styles = sync(join(__dirname, '..', 'dist', 'css', '*.css'))
const styles: string[] = sync(join(__dirname, '..', 'dist', 'css', '*.css'))
const plugins = {
interface Plugins {
[key: string]: string
}
const plugins: Plugins = {
'tabler-flags': 'Flags',
'tabler-flags.rtl': 'Flags RTL',
'tabler-marketing': 'Marketing',
@@ -25,22 +27,24 @@ const plugins = {
'tabler-vendors.rtl': 'Vendors RTL',
}
styles.forEach((file, i) => {
styles.forEach((file: string) => {
const content = readFileSync(file, 'utf8')
const filename = basename(file)
const pluginKey = Object.keys(plugins).find(plugin => filename.includes(plugin))
const plugin = plugins[pluginKey]
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)
if (content.match(regex)) {
newContent = content.replace(regex, (m, m1) => {
return `${m1}\n${banner(plugin)}\n`
newContent = content.replace(regex, (m: string, m1: string) => {
return `${m1}\n${bannerText}\n`
})
} else {
newContent = `${banner(plugin)}\n${content}`
newContent = `${bannerText}\n${content}`
}
writeFileSync(file, newContent, 'utf8')
})
})

View File

@@ -1,82 +0,0 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
// Get __dirname in ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// File paths (relative to core/.build directory)
const bootstrapPath = path.join(__dirname, '../node_modules/bootstrap/scss/_variables.scss');
const tablerPath = path.join(__dirname, '../scss/_variables.scss');
// Function to extract variable names from SCSS file
function extractVariables(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const variables = new Set();
// 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;
while ((match = variableRegex.exec(content)) !== null) {
const varName = match[1];
variables.add(varName);
}
return variables;
}
// Main function
function compareVariables() {
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`);
// Find variables that are in Bootstrap but not in Tabler
const missingInTabler = [];
for (const varName of bootstrapVars) {
if (!tablerVars.has(varName)) {
missingInTabler.push(varName);
}
}
// Sort alphabetically
missingInTabler.sort();
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, index) => {
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));
}
// Run analysis
try {
compareVariables();
} catch (error) {
console.error('Error during analysis:', error.message);
process.exit(1);
}

View File

@@ -0,0 +1,84 @@
import { readFileSync } from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
// Get __dirname in ES modules
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
// File paths (relative to core/.build directory)
const bootstrapPath = path.join(__dirname, '../node_modules/bootstrap/scss/_variables.scss')
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>()
// 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)
}
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 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)
}
}
// Sort alphabetically
missingInTabler.sort()
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}`)
})
}
// 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()
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
console.error('Error during analysis:', errorMessage)
process.exit(1)
}

View File

@@ -1,19 +1,30 @@
#!/usr/bin/env node
'use strict'
import { existsSync, mkdirSync, lstatSync } from 'fs'
import { existsSync, mkdirSync } from 'node:fs'
import { emptyDirSync, copySync } from 'fs-extra/esm'
import libs from '../libs.json' with { type: 'json' }
import { fileURLToPath } from 'url'
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url'
import { join, dirname } from 'node:path'
const __dirname = dirname(fileURLToPath(import.meta.url))
interface LibConfig {
npm?: string
js?: string[]
css?: string[]
head?: boolean
}
interface Libs {
[key: string]: LibConfig
}
const libsData = libs as Libs
emptyDirSync(join(__dirname, '..', 'dist/libs'))
for(const name in libs) {
const { npm } = libs[name]
for (const name in libsData) {
const { npm } = libsData[name]
if (npm) {
const from = join(__dirname, '..', `node_modules/${npm}`)
@@ -23,11 +34,12 @@ for(const name in libs) {
if (!existsSync(to)) {
mkdirSync(to, { recursive: true })
}
copySync(from, to, {
dereference: true,
})
console.log(`Successfully copied ${npm}`)
}
}
}

View File

@@ -1,13 +1,18 @@
const crypto = require('node:crypto');
const fs = require('node:fs');
const path = require('node:path');
const sh = require('shelljs');
import * as crypto from 'node:crypto'
import { readFileSync, writeFileSync } from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
sh.config.fatal = true
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const configFile = path.join(__dirname, '../../shared/data/sri.json')
const files = [
interface FileConfig {
file: string
configPropertyName: string
}
const files: FileConfig[] = [
{
file: 'dist/css/tabler.min.css',
configPropertyName: 'css'
@@ -80,28 +85,37 @@ const files = [
file: 'dist/js/tabler-theme.min.js',
configPropertyName: 'js-theme'
},
// {
// file: 'dist/preview/css/demo.min.css',
// configPropertyName: 'demo-css'
// },
// {
// file: 'dist/preview/js/demo.min.js',
// configPropertyName: 'demo-js'
// },
]
for (const { file, configPropertyName } of files) {
fs.readFile(path.join(__dirname, '..', file), 'utf8', (error, data) => {
if (error) {
function generateSRI(): void {
const sriData: Record<string, string> = {}
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}`
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
}
}
const algorithm = 'sha384'
const hash = crypto.createHash(algorithm).update(data, 'utf8').digest('base64')
const integrity = `${algorithm}-${hash}`
writeFileSync(configFile, JSON.stringify(sriData, null, 2) + '\n', 'utf8')
}
console.log(`${configPropertyName}: ${integrity}`)
try {
generateSRI()
} catch (error) {
console.error('Failed to generate SRI:', error)
process.exit(1)
}
sh.sed('-i', new RegExp(`^(\\s+"${configPropertyName}":\\s+["'])\\S*(["'])`), `$1${integrity}$2`, configFile)
})
}

View File

@@ -0,0 +1,53 @@
#!/usr/bin/env node
import { existsSync, mkdirSync } from 'node:fs'
import { copySync } from 'fs-extra/esm'
import { fileURLToPath } from 'node:url'
import { join, dirname } from 'node:path'
const __dirname = dirname(fileURLToPath(import.meta.url))
const fromDir = join(__dirname, '..', 'node_modules/geist/dist/fonts')
const toDir = join(__dirname, '..', 'fonts')
// Create fonts directory if it doesn't exist
if (!existsSync(toDir)) {
mkdirSync(toDir, { recursive: true })
}
// Copy geist-mono fonts
const monoFrom = join(fromDir, 'geist-mono')
const monoTo = join(toDir, 'geist-mono')
if (existsSync(monoFrom)) {
if (!existsSync(monoTo)) {
mkdirSync(monoTo, { recursive: true })
}
copySync(monoFrom, monoTo, {
dereference: true,
})
console.log(`Successfully copied geist-mono fonts`)
} else {
console.warn(`Warning: geist-mono fonts not found at ${monoFrom}`)
}
// Copy geist-sans fonts
const sansFrom = join(fromDir, 'geist-sans')
const sansTo = join(toDir, 'geist-sans')
if (existsSync(sansFrom)) {
if (!existsSync(sansTo)) {
mkdirSync(sansTo, { recursive: true })
}
copySync(sansFrom, sansTo, {
dereference: true,
})
console.log(`Successfully copied geist-sans fonts`)
} else {
console.warn(`Warning: geist-sans fonts not found at ${sansFrom}`)
}

View File

@@ -1,47 +0,0 @@
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { babel } from '@rollup/plugin-babel'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import replace from '@rollup/plugin-replace'
import banner from '../../shared/banner/index.mjs'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const ESM = process.env.ESM === 'true'
const THEME = process.env.THEME === 'true'
const external = []
const plugins = [
babel({
exclude: 'node_modules/**',
babelHelpers: 'bundled'
})
]
plugins.push(
replace({
'process.env.NODE_ENV': '"production"',
preventAssignment: true
}),
nodeResolve()
)
const destinationFile = `tabler${THEME ? '-theme' : ''}${ESM ? '.esm' : ''}`
const rollupConfig = {
input: path.resolve(__dirname, `../js/tabler${THEME ? '-theme' : ''}.js`),
output: {
banner: banner(),
file: path.resolve(__dirname, `../dist/js/${destinationFile}.js`),
format: ESM ? 'esm' : 'umd',
generatedCode: 'es2015'
},
external,
plugins
}
if (!ESM) {
rollupConfig.output.name = `tabler${THEME ? '-theme' : ''}`
}
export default rollupConfig

View File

@@ -0,0 +1,33 @@
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { existsSync } from 'node:fs'
import { createViteConfig } from '../../.build/vite.config.helper'
import getBanner from '../../shared/banner/index.mjs'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const ESM = process.env.ESM === 'true'
const THEME = process.env.THEME === 'true'
const MINIFY = process.env.MINIFY === 'true'
const destinationFile = `tabler${THEME ? '-theme' : ''}${ESM ? '.esm' : ''}`
const entryFile = `tabler${THEME ? '-theme' : ''}`
const libraryName = `tabler${THEME ? '-theme' : ''}`
const bannerText = getBanner()
// Try .ts first, fallback to .js for gradual migration
const entryPath = path.resolve(__dirname, `../js/${entryFile}`)
const entry = existsSync(`${entryPath}.ts`) ? `${entryPath}.ts` : `${entryPath}.js`
export default createViteConfig({
entry: entry,
name: ESM ? undefined : libraryName,
fileName: () => MINIFY ? `${destinationFile}.min.js` : `${destinationFile}.js`,
formats: [ESM ? 'es' : 'umd'],
outDir: path.resolve(__dirname, '../dist/js'),
banner: bannerText,
minify: MINIFY ? true : false
})

View File

@@ -106,7 +106,6 @@
### Minor Changes
- a2640e2: Add Playwright configuration and visual regression tests
- d3ae77c: Enable `scrollSpy` in `countup` module
- bd3d959: Refactor SCSS files to replace divide function with calc
- cb278c7: Add Segmented Control component

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="16" fill="none"><mask id="a" width="21" height="16" x="0" y="0" mask-type="alpha" maskUnits="userSpaceOnUse"><path fill="#fff" d="M.001.927h20v15h-20z"/></mask><g mask="url(#a)"><path fill="#F7FCFF" fill-rule="evenodd" d="M.001.927v15h20v-15h-20Z" clip-rule="evenodd"/><mask id="b" width="21" height="16" x="0" y="0" mask-type="alpha" maskUnits="userSpaceOnUse"><path fill="#fff" fill-rule="evenodd" d="M.001.927v15h20v-15h-20Z" clip-rule="evenodd"/></mask><g fill-rule="evenodd" clip-rule="evenodd" mask="url(#b)"><path fill="#3D58DB" d="M.001.927v15h20v-15h-20Z"/><path fill="#FFD018" d="m9.407 3.137-.14.818L10 3.57l.735.386-.14-.818.594-.64h-.821L10 1.695l-.367.804h-.822l.595.639Zm0 10.855-.14.819.734-.387.735.387-.14-.819.594-.639h-.821L10 12.55l-.367.804h-.822l.595.64ZM3.484 9.438l.14-.818-.594-.64h.822l.367-.803.367.804h.822l-.595.639.14.818-.734-.386-.735.386Zm1.352 1.77-.14.818.734-.386.735.386-.14-.818.594-.64h-.821l-.368-.803-.367.804H4.24l.595.639Zm9.009.818.14-.818-.595-.64h.822l.367-.803.368.804h.821l-.594.639.14.818-.735-.386-.734.386Zm-9.01-6.062-.14.818.735-.386.735.386-.14-.818.594-.639h-.821l-.368-.804-.367.804H4.24l.595.64Zm9.01.818.14-.818-.595-.639h.822l.367-.804.368.804h.821l-.594.64.14.817-.735-.386-.734.386ZM6.66 13.29l-.14.819.735-.387.734.386-.14-.818.595-.639h-.822l-.367-.804-.368.804h-.821l.594.64Zm5.418.819.14-.819-.594-.639h.821l.367-.804.368.804h.821l-.594.64.14.817-.735-.386-.734.386ZM6.52 4.666l.735-.387.734.387-.14-.818.595-.64h-.822l-.367-.804-.368.804h-.821l.594.64-.14.818Zm5.558 0 .14-.818-.594-.64h.821l.367-.804.368.804h.821l-.594.64.14.818-.735-.387-.734.387Zm3.062 3.879-.14.818.735-.386.735.386-.14-.818.593-.64h-.82l-.368-.803-.368.804h-.821l.594.639Z"/></g></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="15" fill="none" viewBox="0 0 20 15"><path fill="#3d58db" fill-rule="evenodd" d="M0 0v15h20V0z" clip-rule="evenodd"/><path fill="#ffd018" fill-rule="evenodd" d="m9.407 2.442-.14.818.733-.385.735.386-.14-.818.594-.64h-.821L10 1l-.367.804h-.822zm0 10.855-.14.819.734-.387.735.387-.14-.819.594-.639h-.821L10 11.855l-.367.804h-.822l.595.64zM3.484 8.743l.14-.818-.594-.64h.822l.367-.803.367.804h.822l-.595.639.14.818-.734-.386zm1.352 1.77-.14.818.734-.386.735.386-.14-.818.594-.64h-.821L5.43 9.07l-.367.804H4.24zm9.009.818.14-.818-.595-.64h.822l.367-.803.368.804h.821l-.594.639.14.818-.735-.386zm-9.01-6.062-.14.818.735-.386.735.386-.14-.818.594-.639h-.821l-.368-.804-.367.804H4.24zm9.01.818.14-.818-.595-.639h.822l.367-.804.368.804h.821l-.594.64.14.817-.735-.386zM6.66 12.595l-.14.819.735-.387.734.386-.14-.818.595-.639h-.822l-.367-.804-.368.804h-.821zm5.418.819.14-.819-.594-.639h.821l.367-.804.368.804h.821l-.594.64.14.817-.735-.386zM6.52 3.971l.735-.387.734.387-.14-.818.595-.64h-.822l-.367-.804-.368.804h-.821l.594.64zm5.558 0 .14-.818-.594-.64h.821l.367-.804.368.804h.821l-.594.64.14.818-.735-.387zM15.14 7.85l-.14.818.735-.386.735.386-.14-.818.593-.64h-.82l-.368-.803-.368.804h-.821z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,8 +0,0 @@
// Autosize plugin
const elements = document.querySelectorAll('[data-bs-toggle="autosize"]')
if (elements.length) {
elements.forEach(function (element) {
window.autosize && window.autosize(element)
})
}

10
core/js/src/autosize.ts Normal file
View File

@@ -0,0 +1,10 @@
// Autosize plugin
const autosizeElements: NodeListOf<HTMLElement> = document.querySelectorAll<HTMLElement>('[data-bs-toggle="autosize"]')
if (autosizeElements.length) {
autosizeElements.forEach(function (element: HTMLElement) {
if (window.autosize) {
window.autosize(element)
}
})
}

View File

@@ -1,3 +0,0 @@
export * as Popper from '@popperjs/core'
export { Dropdown, Tooltip, Popover, Tab, Toast } from 'bootstrap'

7
core/js/src/bootstrap.ts Normal file
View File

@@ -0,0 +1,7 @@
export * as Popper from '@popperjs/core'
// Export all Bootstrap components directly for consistent usage
export { Alert, Button, Carousel, Collapse, Dropdown, Modal, Offcanvas, Popover, ScrollSpy, Tab, Toast, Tooltip } from 'bootstrap'
// Re-export everything as namespace for backward compatibility
export * as bootstrap from 'bootstrap'

View File

@@ -1,17 +1,19 @@
const elements = document.querySelectorAll('[data-countup]')
const countupElements: NodeListOf<HTMLElement> = document.querySelectorAll<HTMLElement>('[data-countup]')
if (elements.length) {
elements.forEach(function (element) {
let options = {}
if (countupElements.length) {
countupElements.forEach(function (element: HTMLElement) {
let options: Record<string, any> = {}
try {
const dataOptions = element.getAttribute('data-countup') ? JSON.parse(element.getAttribute('data-countup')) : {}
const dataOptions = element.getAttribute('data-countup') ? JSON.parse(element.getAttribute('data-countup')!) : {}
options = Object.assign(
{
enableScrollSpy: true,
},
dataOptions,
)
} catch (error) {}
} catch (error) {
// ignore invalid JSON
}
const value = parseInt(element.innerHTML, 10)

View File

@@ -3,9 +3,9 @@ import { Dropdown } from './bootstrap'
/*
Core dropdowns
*/
let dropdownTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="dropdown"]'))
dropdownTriggerList.map(function (dropdownTriggerEl) {
let options = {
const dropdownTriggerList: HTMLElement[] = [].slice.call(document.querySelectorAll<HTMLElement>('[data-bs-toggle="dropdown"]'))
dropdownTriggerList.map(function (dropdownTriggerEl: HTMLElement) {
const options = {
boundary: dropdownTriggerEl.getAttribute('data-bs-boundary') === 'viewport' ? document.querySelector('.btn') : 'clippingParents',
}
return new Dropdown(dropdownTriggerEl, options)

14
core/js/src/global.d.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
// Global type declarations for window properties
interface Window {
autosize?: (element: HTMLElement | HTMLTextAreaElement) => void
countUp?: {
CountUp: new (target: HTMLElement, endVal: number, options?: any) => {
error: boolean
start: () => void
}
}
IMask?: new (element: HTMLElement, options: { mask: string; lazy?: boolean }) => any
Sortable?: new (element: HTMLElement, options?: any) => any
}

View File

@@ -1,7 +1,7 @@
// Input mask plugin
var maskElementList = [].slice.call(document.querySelectorAll('[data-mask]'))
maskElementList.map(function (maskEl) {
const maskElementList: HTMLElement[] = [].slice.call(document.querySelectorAll<HTMLElement>('[data-mask]'))
maskElementList.map(function (maskEl: HTMLElement) {
window.IMask &&
new window.IMask(maskEl, {
mask: maskEl.dataset.mask,

View File

@@ -3,11 +3,11 @@ import { Popover } from './bootstrap'
/*
Core popovers
*/
let popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'))
popoverTriggerList.map(function (popoverTriggerEl) {
let options = {
const popoverTriggerList: HTMLElement[] = [].slice.call(document.querySelectorAll<HTMLElement>('[data-bs-toggle="popover"]'))
popoverTriggerList.map(function (popoverTriggerEl: HTMLElement) {
const options = {
delay: { show: 50, hide: 50 },
html: popoverTriggerEl.getAttribute('data-bs-html') === 'true' ?? false,
html: popoverTriggerEl.getAttribute('data-bs-html') === 'true',
placement: popoverTriggerEl.getAttribute('data-bs-placement') ?? 'auto',
}
return new Popover(popoverTriggerEl, options)

View File

@@ -2,11 +2,11 @@
// Initializes Sortable on elements marked with [data-sortable]
// Allows options via JSON in data attribute: data-sortable='{"animation":150}'
const sortableElements = document.querySelectorAll('[data-sortable]')
const sortableElements: NodeListOf<HTMLElement> = document.querySelectorAll<HTMLElement>('[data-sortable]')
if (sortableElements.length) {
sortableElements.forEach(function (element) {
let options = {}
sortableElements.forEach(function (element: HTMLElement) {
let options: Record<string, any> = {}
try {
const rawOptions = element.getAttribute('data-sortable')

View File

@@ -1,11 +0,0 @@
/*
Switch icons
*/
let switchesTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="switch-icon"]'))
switchesTriggerList.map(function (switchTriggerEl) {
switchTriggerEl.addEventListener('click', (e) => {
e.stopPropagation()
switchTriggerEl.classList.toggle('active')
})
})

View File

@@ -0,0 +1,11 @@
/*
Switch icons
*/
const switchesTriggerList: HTMLElement[] = [].slice.call(document.querySelectorAll<HTMLElement>('[data-bs-toggle="switch-icon"]'))
switchesTriggerList.map(function (switchTriggerEl: HTMLElement) {
switchTriggerEl.addEventListener('click', (e: MouseEvent) => {
e.stopPropagation()
switchTriggerEl.classList.toggle('active')
})
})

View File

@@ -1,16 +0,0 @@
import { Tab } from './bootstrap'
export const EnableActivationTabsFromLocationHash = () => {
const locationHash = window.location.hash
if (locationHash) {
const tabsList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tab"]'))
const matchedTabs = tabsList.filter((tab) => tab.hash === locationHash)
matchedTabs.map((tab) => {
new Tab(tab).show()
})
}
}
EnableActivationTabsFromLocationHash()

16
core/js/src/tab.ts Normal file
View File

@@ -0,0 +1,16 @@
import { Tab } from './bootstrap'
export const EnableActivationTabsFromLocationHash = (): void => {
const locationHash: string = window.location.hash
if (locationHash) {
const tabsList: HTMLAnchorElement[] = [].slice.call(document.querySelectorAll<HTMLAnchorElement>('[data-bs-toggle="tab"]'))
const matchedTabs = tabsList.filter((tab: HTMLAnchorElement) => tab.hash === locationHash)
matchedTabs.map((tab: HTMLAnchorElement) => {
new Tab(tab).show()
})
}
}
EnableActivationTabsFromLocationHash()

View File

@@ -1,12 +1,12 @@
export const prefix = 'tblr-'
export const prefix: string = 'tblr-'
export const hexToRgba = (hex, opacity) => {
export const hexToRgba = (hex: string, opacity: number): string | null => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result ? `rgba(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}, ${opacity})` : null
}
export const getColor = (color, opacity = 1) => {
export const getColor = (color: string, opacity: number = 1): string | null => {
const c = getComputedStyle(document.body).getPropertyValue(`--${prefix}${color}`).trim()
if (opacity !== 1) {

View File

@@ -1,17 +0,0 @@
import { Toast } from './bootstrap'
/*
Toasts
*/
let toastsTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="toast"]'))
toastsTriggerList.map(function (toastTriggerEl) {
if (!toastTriggerEl.hasAttribute('data-bs-target')) {
return
}
const toastEl = new Toast(toastTriggerEl.getAttribute('data-bs-target'))
toastTriggerEl.addEventListener('click', () => {
toastEl.show()
})
})

18
core/js/src/toast.ts Normal file
View File

@@ -0,0 +1,18 @@
import { Toast } from './bootstrap'
/*
Toasts
*/
const toastsTriggerList: HTMLElement[] = [].slice.call(document.querySelectorAll<HTMLElement>('[data-bs-toggle="toast"]'))
toastsTriggerList.map(function (toastTriggerEl: HTMLElement) {
const target = toastTriggerEl.getAttribute('data-bs-target')
if (target === null) {
return
}
const toastEl = new Toast(target)
toastTriggerEl.addEventListener('click', () => {
toastEl.show()
})
})

View File

@@ -1,10 +1,10 @@
import { Tooltip } from './bootstrap'
let tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
tooltipTriggerList.map(function (tooltipTriggerEl) {
let options = {
const tooltipTriggerList: HTMLElement[] = [].slice.call(document.querySelectorAll<HTMLElement>('[data-bs-toggle="tooltip"]'))
tooltipTriggerList.map(function (tooltipTriggerEl: HTMLElement) {
const options = {
delay: { show: 50, hide: 50 },
html: tooltipTriggerEl.getAttribute('data-bs-html') === 'true' ?? false,
html: tooltipTriggerEl.getAttribute('data-bs-html') === 'true',
placement: tooltipTriggerEl.getAttribute('data-bs-placement') ?? 'auto',
}
return new Tooltip(tooltipTriggerEl, options)

View File

@@ -3,7 +3,15 @@
* to ensure we switch to the chosen dark/light theme as fast as possible.
* This will prevent any flashes of the light theme (default) before switching.
*/
const themeConfig = {
interface ThemeConfig {
'theme': string
'theme-base': string
'theme-font': string
'theme-primary': string
'theme-radius': string
}
const themeConfig: ThemeConfig = {
'theme': 'light',
'theme-base': 'gray',
'theme-font': 'sans-serif',
@@ -12,22 +20,22 @@ const themeConfig = {
}
const params = new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, prop) => searchParams.get(prop),
get: (searchParams: URLSearchParams, prop: string): string | null => searchParams.get(prop),
})
for (const key in themeConfig) {
const param = params[key]
let selectedValue
let selectedValue: string
if (!!param) {
localStorage.setItem('tabler-' + key, param)
selectedValue = param
} else {
const storedTheme = localStorage.getItem('tabler-' + key)
selectedValue = storedTheme ? storedTheme : themeConfig[key]
selectedValue = storedTheme ? storedTheme : themeConfig[key as keyof ThemeConfig]
}
if (selectedValue !== themeConfig[key]) {
if (selectedValue !== themeConfig[key as keyof ThemeConfig]) {
document.documentElement.setAttribute('data-bs-' + key, selectedValue)
} else {
document.documentElement.removeAttribute('data-bs-' + key)

View File

@@ -9,7 +9,8 @@ import './src/tab'
import './src/toast'
import './src/sortable'
export * as bootstrap from 'bootstrap'
export * as tabler from './src/tabler'
// Re-export everything from bootstrap.ts (single source of truth)
export * from './src/bootstrap'
export { Alert, Modal, Toast, Tooltip, Tab, Button, Carousel, Collapse, Dropdown, Popover, ScrollSpy, Offcanvas } from 'bootstrap'
// Re-export tabler namespace
export * as tabler from './src/tabler'

View File

@@ -166,5 +166,14 @@
"dist/turbo.es2017-umd.js"
],
"head": true
},
"driver.js": {
"npm": "driver.js",
"js": [
"dist/driver.js.iife.js"
],
"css": [
"dist/driver.css"
]
}
}

View File

@@ -5,41 +5,43 @@
"homepage": "https://tabler.io",
"scripts": {
"dev": "pnpm run clean && pnpm run copy && pnpm run watch",
"build": "pnpm run clean && pnpm run css && pnpm run js && pnpm run copy && pnpm run generate-sri",
"build": "pnpm run clean && pnpm run build-assets && pnpm run copy && pnpm run generate-sri",
"build-assets": "concurrently \"pnpm run css\" \"pnpm run js\"",
"clean": "shx rm -rf dist demo",
"css": "pnpm run css-compile && pnpm run css-prefix && pnpm run css-rtl && pnpm run css-minify && pnpm run css-banner",
"css-compile": "sass --no-source-map --load-path=node_modules --style expanded scss/:dist/css/",
"css-banner": "node .build/add-banner.mjs",
"css": "pnpm run css-build && pnpm run css-prefix && pnpm run css-rtl && pnpm run css-minify && pnpm run css-banner",
"css-build": "sass --no-source-map --load-path=node_modules --style expanded scss/:dist/css/",
"css-banner": "tsx .build/add-banner.ts",
"css-prefix": "postcss --config .build/postcss.config.mjs --replace \"dist/css/*.css\" \"!dist/css/*.rtl*.css\" \"!dist/css/*.min.css\"",
"css-rtl": "cross-env NODE_ENV=RTL postcss --config .build/postcss.config.mjs --dir \"dist/css\" --ext \".rtl.css\" \"dist/css/*.css\" \"!dist/css/*.min.css\" \"!dist/css/*.rtl.css\"",
"css-minify": "pnpm run css-minify-main && pnpm run css-minify-rtl",
"css-minify": "concurrently \"pnpm run css-minify-main\" \"pnpm run css-minify-rtl\"",
"css-minify-main": "cleancss -O1 --format breakWith=lf --with-rebase --source-map --source-map-inline-sources --output dist/css/ --batch --batch-suffix \".min\" \"dist/css/*.css\" \"!dist/css/*.min.css\" \"!dist/css/*rtl*.css\"",
"css-minify-rtl": "cleancss -O1 --format breakWith=lf --with-rebase --source-map --source-map-inline-sources --output dist/css/ --batch --batch-suffix \".min\" \"dist/css/*rtl.css\" \"!dist/css/*.min.css\"",
"css-lint": "pnpm run css-lint-variables",
"css-lint-variables": "find-unused-sass-variables scss/ node_modules/bootstrap/scss/",
"js": "pnpm run js-compile && pnpm run js-minify",
"js-compile": "pnpm run js-compile-standalone && pnpm run js-compile-standalone-esm && pnpm run js-compile-theme && pnpm run js-compile-theme-esm",
"js-compile-theme-esm": "rollup --environment THEME:true --environment ESM:true --config .build/rollup.config.mjs --sourcemap",
"js-compile-theme": "rollup --environment THEME:true --config .build/rollup.config.mjs --sourcemap",
"js-compile-standalone": "rollup --config .build/rollup.config.mjs --sourcemap",
"js-compile-standalone-esm": "rollup --environment ESM:true --config .build/rollup.config.mjs --sourcemap",
"js-minify": "pnpm run js-minify-standalone && pnpm run js-minify-standalone-esm && pnpm run js-minify-theme && pnpm run js-minify-theme-esm",
"js-minify-standalone": "terser --compress passes=2 --mangle --comments \"/^!/\" --source-map \"content=dist/js/tabler.js.map,includeSources,url=tabler.min.js.map\" --output dist/js/tabler.min.js dist/js/tabler.js",
"js-minify-standalone-esm": "terser --compress passes=2 --mangle --comments \"/^!/\" --source-map \"content=dist/js/tabler.esm.js.map,includeSources,url=tabler.esm.min.js.map\" --output dist/js/tabler.esm.min.js dist/js/tabler.esm.js",
"js-minify-theme": "terser --compress passes=2 --mangle --comments \"/^!/\" --source-map \"content=dist/js/tabler-theme.js.map,includeSources,url=tabler-theme.min.js.map\" --output dist/js/tabler-theme.min.js dist/js/tabler-theme.js",
"js-minify-theme-esm": "terser --compress passes=2 --mangle --comments \"/^!/\" --source-map \"content=dist/js/tabler-theme.esm.js.map,includeSources,url=tabler-theme.esm.min.js.map\" --output dist/js/tabler-theme.esm.min.js dist/js/tabler-theme.esm.js",
"copy": "pnpm run copy-img && pnpm run copy-libs && pnpm run copy-fonts",
"js": "pnpm run js-build && pnpm run js-build-min",
"js-build": "concurrently \"pnpm run js-build-standalone\" \"pnpm run js-build-standalone-esm\" \"pnpm run js-build-theme\" \"pnpm run js-build-theme-esm\"",
"js-build-theme-esm": "cross-env THEME=true ESM=true vite build --config .build/vite.config.ts",
"js-build-theme": "cross-env THEME=true vite build --config .build/vite.config.ts",
"js-build-standalone": "vite build --config .build/vite.config.ts",
"js-build-standalone-esm": "cross-env ESM=true vite build --config .build/vite.config.ts",
"js-build-min": "concurrently \"pnpm run js-build-min-standalone\" \"pnpm run js-build-min-standalone-esm\" \"pnpm run js-build-min-theme\" \"pnpm run js-build-min-theme-esm\"",
"js-build-min-standalone": "cross-env MINIFY=true vite build --config .build/vite.config.ts",
"js-build-min-standalone-esm": "cross-env MINIFY=true ESM=true vite build --config .build/vite.config.ts",
"js-build-min-theme": "cross-env MINIFY=true THEME=true vite build --config .build/vite.config.ts",
"js-build-min-theme-esm": "cross-env MINIFY=true THEME=true ESM=true vite build --config .build/vite.config.ts",
"copy": "concurrently \"pnpm run copy-img\" \"pnpm run copy-libs\" \"pnpm run copy-fonts\"",
"copy-img": "shx mkdir -p dist/img && shx cp -rf img/* dist/img",
"copy-libs": "node .build/copy-libs.mjs",
"copy-libs": "tsx .build/copy-libs.ts",
"copy-fonts": "shx mkdir -p dist/fonts && shx cp -rf fonts/* dist/fonts",
"import-fonts": "node .build/import-fonts.mjs",
"import-fonts": "tsx .build/import-fonts.ts",
"watch": "concurrently \"pnpm run watch-css\" \"pnpm run watch-js\"",
"watch-css": "nodemon --watch scss/ --ext scss --exec \"pnpm run css-compile && pnpm run css-prefix\"",
"watch-js": "nodemon --watch js/ --ext js --exec \"pnpm run js-compile\"",
"watch-css": "nodemon --watch scss/ --ext scss --exec \"pnpm run css-build && pnpm run css-prefix\"",
"watch-js": "nodemon --watch js/ --ext ts,js --exec \"pnpm run js-build\"",
"bundlewatch": "bundlewatch",
"generate-sri": "node .build/generate-sri.js",
"format:check": "prettier --check \"scss/**/*.scss\" \"js/**/*.js\" --cache",
"format:write": "prettier --write \"scss/**/*.scss\" \"js/**/*.js\" --cache"
"generate-sri": "tsx .build/generate-sri.ts",
"format:check": "prettier --check \"scss/**/*.scss\" \"js/**/*.{js,ts}\" --cache",
"format:write": "prettier --write \"scss/**/*.scss\" \"js/**/*.{js,ts}\" --cache",
"type-check": "tsc --noEmit"
},
"repository": {
"type": "git",
@@ -69,7 +71,7 @@
"files": [
"docs/**/*",
"dist/**/*",
"js/**/*.{js,map}",
"js/**/*.{ts,js,map}",
"img/**/*.{svg}",
"scss/**/*.scss",
"libs.json"
@@ -150,13 +152,13 @@
},
"dependencies": {
"@popperjs/core": "^2.11.8",
"bootstrap": "5.3.8",
"geist": "^1.5.1"
"bootstrap": "5.3.8"
},
"devDependencies": {
"@hotwired/turbo": "^8.0.18",
"@melloware/coloris": "^0.25.0",
"apexcharts": "3.54.1",
"@types/node": "^22.0.0",
"apexcharts": "^5.3.6",
"autosize": "^6.0.1",
"choices.js": "^11.1.0",
"clipboard": "^2.0.11",
@@ -166,6 +168,7 @@
"flatpickr": "^4.6.13",
"fslightbox": "^3.7.4",
"fullcalendar": "^6.1.19",
"geist": "^1.5.1",
"hugerte": "^1.0.9",
"imask": "^7.6.1",
"jsvectormap": "^1.7.0",
@@ -177,7 +180,9 @@
"sortablejs": "^1.15.6",
"star-rating.js": "^4.3.1",
"tom-select": "^2.4.3",
"typed.js": "^2.1.0"
"typed.js": "^2.1.0",
"typescript": "^5.9.3",
"driver.js": "^1.0.0"
},
"directories": {
"doc": "docs"

View File

@@ -32,6 +32,7 @@
/** Theme colors */
@each $name, $color in map.merge($theme-colors, $social-colors) {
@debug contrast-ratio($color, white), $name, $min-contrast-ratio;
--#{$prefix}#{$name}: #{$color};
--#{$prefix}#{$name}-rgb: #{to-rgb($color)};
--#{$prefix}#{$name}-fg: #{if(contrast-ratio($color) > $min-contrast-ratio, var(--#{$prefix}light), var(--#{$prefix}dark))};

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@
$darken-dark: color.adjust($dark, $lightness: -2%) !default;
$lighten-dark: color.adjust($dark, $lightness: 2%) !default;
$border-color-dark: color.adjust($dark, $lightness: 8%) !default;
$border-color-translucent-dark: rgba(72, 110, 149, 0.14) !default;
$border-color-translucent-dark: rgba(128, 150, 172, 0.2) !default;
$border-dark-color-dark: color.adjust($dark, $lightness: 4%) !default;
$border-active-color-dark: color.adjust($dark, $lightness: 12%) !default;

View File

@@ -33,12 +33,7 @@ $enable-deprecation-messages: true !default;
$enable-important-utilities: true !default;
// Escaped Characters
$escaped-characters: (
('<', '%3c'),
('>', '%3e'),
('#', '%23'),
('(', '%28'),
(')', '%29')) !default;
$escaped-characters: (('<', '%3c'), ('>', '%3e'), ('#', '%23'), ('(', '%28'), (')', '%29')) !default;
// Dark Mode
$color-mode-type: data !default;
@@ -106,7 +101,7 @@ $colors: (
'gray-dark': $gray-800,
) !default;
$min-contrast-ratio: 4.5 !default;
$min-contrast-ratio: 2 !default;
$color-contrast-dark: $black !default;
$color-contrast-light: $white !default;
@@ -444,7 +439,7 @@ $headings-font-style: null !default;
$headings-line-height: 1.2 !default;
$headings-font-weight: var(--#{$prefix}font-weight-semibold) !default;
$headings-margin-bottom: var(--#{$prefix}spacer) !default;
$headings-color: inherit !default;
$headings-color: light-dark(var(--#{$prefix}gray-900), var(--#{$prefix}white)) !default;
$font-weights: (
'light': $font-weight-light,
@@ -528,7 +523,7 @@ $initialism-font-size: $small-font-size !default;
$sub-sup-font-size: 0.75em !default;
$dt-font-weight: $font-weight-bold !default;
$dt-font-weight: $font-weight-semibold !default;
$list-inline-padding: 0.5rem !default;
@@ -556,16 +551,10 @@ $zindex-levels: (
3: 3,
) !default;
$min-contrast-ratio: 1.5 !default;
$text-secondary-opacity: 0.7 !default;
$text-secondary-light-opacity: 0.4 !default;
$text-secondary-dark-opacity: 0.8 !default;
$border-opacity: 0.16 !default;
$border-light-opacity: 0.08 !default;
$border-dark-opacity: 0.24 !default;
$border-active-opacity: 0.58 !default;
$bg-surface: var(--#{$prefix}white) !default;
$bg-surface-secondary: var(--#{$prefix}gray-100) !default;
$bg-surface-tertiary: var(--#{$prefix}gray-50) !default;
@@ -573,12 +562,8 @@ $bg-surface-dark: var(--#{$prefix}dark) !default;
$body-text-align: null !default;
$body-bg: $gray-50 !default;
$body-color: $dark !default;
$body-color: $gray-800 !default;
$body-emphasis-color: $gray-700 !default;
$body-secondary-color: rgba($body-color, 0.75) !default;
$body-secondary-bg: $gray-200 !default;
$body-tertiary-color: rgba($body-color, 0.5) !default;
$body-tertiary-bg: $gray-100 !default;
$color-contrast-dark: $body-color !default;
$color-contrast-light: $light !default;
@@ -588,20 +573,27 @@ $text-secondary: $gray-500 !default;
$text-secondary-light: $gray-400 !default;
$text-secondary-dark: $gray-600 !default;
$border-color: $gray-200 !default;
$border-color-translucent: rgba(4, 32, 69, 0.1);
$border-light-color: var(--#{$prefix}gray-200) !default;
$border-light-opacity: 4.7% !default;
$border-light-color-translucent: color-mix(in srgb, var(--#{$prefix}gray-800) #{$border-light-opacity}, transparent) !default;
$border-dark-color: $gray-400 !default;
$border-dark-color-translucent: rgba(4, 32, 69, 0.27);
$border-color: var(--#{$prefix}gray-200) !default;
$border-opacity: 11.9% !default;
$border-color-translucent: color-mix(in srgb, var(--#{$prefix}gray-800) #{$border-opacity}, transparent) !default;
$border-active-color: color.mix($text-secondary, #ffffff, math.percentage($border-active-opacity)) !default;
$border-active-color-translucent: rgba($text-secondary, $border-active-opacity) !default;
$border-dark-color: var(--#{$prefix}gray-300) !default;
$border-dark-opacity: 20.7% !default;
$border-dark-color-translucent: color-mix(in srgb, var(--#{$prefix}gray-800) #{$border-dark-opacity}, transparent) !default;
$active-bg: rgba(var(--#{$prefix}primary-rgb), 0.04) !default;
$border-active-color: var(--#{$prefix}gray-400) !default;
$border-active-opacity: 44.8% !default;
$border-active-color-translucent: color-mix(in srgb, var(--#{$prefix}gray-800) #{$border-active-opacity}, transparent) !default;
$active-bg: color-transparent(var(--#{$prefix}primary), 0.04) !default;
$active-color: var(--#{$prefix}primary) !default;
$active-border-color: var(--#{$prefix}primary) !default;
$hover-bg: rgba(var(--#{$prefix}secondary-rgb), 0.08) !default;
$hover-bg: color-transparent(var(--#{$prefix}secondary), 0.08) !default;
$disabled-bg: var(--#{$prefix}bg-surface-secondary) !default;
$disabled-color: color-transparent(var(--#{$prefix}body-color), 0.4) !default;
@@ -881,7 +873,7 @@ $avatar-sizes: (
brand-size: 2rem,
),
) !default;
$avatar-border-radius: var(--#{$prefix}border-radius) !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;
@@ -989,14 +981,14 @@ $aspect-ratios: (
) !default;
// Shadows
$box-shadow: rgba(var(--#{$prefix}body-color-rgb), 0.04) 0 2px 4px 0 !default;
$box-shadow: color-transparent(var(--#{$prefix}body-color), 0.04) 0 2px 4px 0 !default;
$box-shadow-sm: 0 0.125rem 0.25rem rgba($black, 0.075) !default;
$box-shadow-lg: 0 1rem 3rem rgba($black, 0.175) !default;
$box-shadow-transparent: 0 0 0 0 transparent !default;
$box-shadow-border: inset 0 0 0 1px var(--#{$prefix}border-color-translucent) !default;
$box-shadow-input: 0 1px 1px rgba(var(--#{$prefix}body-color-rgb), 0.06) !default;
$box-shadow-card: 0 0 4px rgba(var(--#{$prefix}body-color-rgb), 0.04) !default;
$box-shadow-card-hover: rgba(var(--#{$prefix}body-color-rgb), 0.16) 0 2px 16px 0 !default;
$box-shadow-input: 0 1px 1px color-transparent(var(--#{$prefix}body-color), 0.06) !default;
$box-shadow-card: 0px 1px 3px rgba(0, 0, 0, 0.08) !default;
$box-shadow-card-hover: color-transparent(var(--#{$prefix}body-color), 0.16) 0 2px 16px 0 !default;
$box-shadow-dropdown:
0 16px 24px 2px rgba(0, 0, 0, 0.07),
0 6px 30px 5px rgba(0, 0, 0, 0.06),
@@ -1021,7 +1013,7 @@ $component-active-bg: $primary !default;
// Focus
$focus-ring-width: 0.25rem !default;
$focus-ring-opacity: 0.25 !default;
$focus-ring-color: rgba(var(--#{$prefix}primary-rgb), $focus-ring-opacity) !default;
$focus-ring-color: color-mix(in srgb, var(--#{$prefix}primary) #{percentage($focus-ring-opacity)}, transparent) !default;
$focus-ring-blur: 0 !default;
$focus-ring-box-shadow: 0 0 $focus-ring-blur $focus-ring-width $focus-ring-color !default;
@@ -1040,7 +1032,7 @@ $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-bold) !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;
@@ -1062,7 +1054,7 @@ $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-bold) !default;
$breadcrumb-active-font-weight: var(--#{$prefix}font-weight-semibold) !default;
$breadcrumb-disabled-color: var(--#{$prefix}disabled-color) !default;
$breadcrumb-variants: (
@@ -1167,9 +1159,9 @@ $btn-padding-y-lg: $input-btn-padding-y-lg !default;
$btn-padding-x-lg: $input-btn-padding-x-lg !default;
// Inputs
$input-bg: var(--#{$prefix}body-bg) !default;
$input-bg: var(--#{$prefix}bg-forms) !default;
$input-disabled-color: null !default;
$input-disabled-bg: var(--#{$prefix}secondary-bg) !default;
$input-disabled-bg: var(--#{$prefix}bg-surface-secondary) !default;
$input-disabled-border-color: null !default;
$input-height: null !default;
@@ -1518,9 +1510,9 @@ $navbar-light-active-color: var(--#{$prefix}body-color) !default;
$navbar-light-hover-color: var(--#{$prefix}body-color) !default;
$navbar-light-disabled-color: var(--#{$prefix}disabled-color) !default;
$navbar-light-active-bg: rgba(0, 0, 0, 0.2) !default;
$navbar-light-icon-color: rgba($body-color, 0.75) !default;
$navbar-light-icon-color: color-transparent(var(--#{$prefix}body-color), 0.75) !default;
$navbar-light-toggler-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path stroke='#{$navbar-light-icon-color}' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>") !default;
$navbar-light-toggler-border-color: rgba(var(--#{$prefix}emphasis-color-rgb), 0.15) !default;
$navbar-light-toggler-border-color: color-transparent(var(--#{$prefix}emphasis-color), 0.15) !default;
$navbar-light-brand-hover-color: $navbar-light-active-color !default;
$navbar-dark-color: rgba($white, $text-secondary-opacity) !default;
@@ -1540,7 +1532,7 @@ $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-bold) !default;
$navbar-brand-font-weight: var(--#{$prefix}font-weight-semibold) !default;
$navbar-toggler-padding-y: 0.25rem !default;
$navbar-toggler-padding-x: 0.75rem !default;
@@ -1695,7 +1687,7 @@ $table-active-bg: var(--#{$prefix}active-bg) !default;
$table-hover-color: $table-color !default;
$table-hover-bg-factor: 0.075 !default;
$table-hover-bg: rgba(var(--#{$prefix}emphasis-color-rgb), $table-hover-bg-factor) !default;
$table-hover-bg: color-mix(in srgb, var(--#{$prefix}emphasis-color) #{percentage($table-hover-bg-factor)}, transparent) !default;
$table-caption-color: var(--#{$prefix}secondary-color) !default;
@@ -1730,7 +1722,7 @@ $toast-box-shadow: var(--#{$prefix}box-shadow) !default;
$toast-spacing: $container-padding-x !default;
$toast-header-color: var(--#{$prefix}gray-500) !default;
$toast-header-background-color: rgba(var(--#{$prefix}body-bg-rgb), 0.85) !default;
$toast-header-background-color: color-transparent(var(--#{$prefix}body-bg), 0.85) !default;
$toast-header-border-color: $toast-border-color !default;
// Tracking
@@ -1775,8 +1767,6 @@ $list-group-action-active-color: var(--#{$prefix}body-color) !default;
$list-group-action-active-bg: var(--#{$prefix}secondary-bg) !default;
// Forms
$input-bg: var(--#{$prefix}bg-forms) !default;
$input-disabled-bg: $disabled-bg !default;
$input-border-color: var(--#{$prefix}border-color) !default;
$input-border-color-translucent: var(--#{$prefix}border-color-translucent) !default;
@@ -1866,16 +1856,16 @@ $form-select-font-size-lg: $input-font-size-lg !default;
$form-select-border-radius-lg: $input-border-radius-lg !default;
$form-select-transition: $input-transition !default;
$form-switch-color: rgba($black, 0.25) !default;
$form-switch-color: white !default;
$form-switch-width: 2rem !default;
$form-switch-height: 1.25rem !default;
$form-switch-padding-start: $form-switch-width + 0.5rem !default;
$form-switch-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$border-color}'/></svg>") !default;
$form-switch-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-color}'/></svg>") !default;
$form-switch-border-radius: $form-switch-width !default;
$form-switch-transition: background-position 0.15s ease-in-out !default;
$form-switch-focus-color: $input-focus-border-color !default;
$form-switch-focus-color: white !default;
$form-switch-focus-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-focus-color}'/></svg>") !default;
$form-switch-checked-color: $component-active-color !default;
$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;
@@ -1961,7 +1951,7 @@ $form-validation-states: (
'icon': $form-feedback-icon-valid,
'tooltip-color': #fff,
'tooltip-bg-color': var(--#{$prefix}success),
'focus-box-shadow': 0 0 $input-btn-focus-blur $input-focus-width rgba(var(--#{$prefix}success-rgb), $input-btn-focus-color-opacity),
'focus-box-shadow': 0 0 $input-btn-focus-blur $input-focus-width color-mix(in srgb, var(--#{$prefix}success) #{percentage($input-btn-focus-color-opacity)}, transparent),
'border-color': var(--#{$prefix}form-valid-border-color),
),
'invalid': (
@@ -1969,7 +1959,7 @@ $form-validation-states: (
'icon': $form-feedback-icon-invalid,
'tooltip-color': #fff,
'tooltip-bg-color': var(--#{$prefix}danger),
'focus-box-shadow': 0 0 $input-btn-focus-blur $input-focus-width rgba(var(--#{$prefix}danger-rgb), $input-btn-focus-color-opacity),
'focus-box-shadow': 0 0 $input-btn-focus-blur $input-focus-width color-mix(in srgb, var(--#{$prefix}danger) #{percentage($input-btn-focus-color-opacity)}, transparent),
'border-color': var(--#{$prefix}form-invalid-border-color),
),
) !default;

View File

@@ -1,184 +1,203 @@
// Geist Sans Font Family
@font-face {
font-family: 'Geist';
src: url('#{$assets-base}/fonts/geist-sans/Geist-Thin.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-Thin.ttf') format('truetype');
font-weight: 100;
font-style: normal;
font-display: swap;
font-family: 'Geist';
src:
url('#{$assets-base}/fonts/geist-sans/Geist-Thin.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-Thin.ttf') format('truetype');
font-weight: 100;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Geist';
src: url('#{$assets-base}/fonts/geist-sans/Geist-UltraLight.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-UltraLight.ttf') format('truetype');
font-weight: 200;
font-style: normal;
font-display: swap;
font-family: 'Geist';
src:
url('#{$assets-base}/fonts/geist-sans/Geist-UltraLight.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-UltraLight.ttf') format('truetype');
font-weight: 200;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Geist';
src: url('#{$assets-base}/fonts/geist-sans/Geist-Light.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-Light.ttf') format('truetype');
font-weight: 300;
font-style: normal;
font-display: swap;
font-family: 'Geist';
src:
url('#{$assets-base}/fonts/geist-sans/Geist-Light.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-Light.ttf') format('truetype');
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Geist';
src: url('#{$assets-base}/fonts/geist-sans/Geist-Regular.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-Regular.ttf') format('truetype');
font-weight: 400;
font-style: normal;
font-display: swap;
font-family: 'Geist';
src:
url('#{$assets-base}/fonts/geist-sans/Geist-Regular.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-Regular.ttf') format('truetype');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Geist';
src: url('#{$assets-base}/fonts/geist-sans/Geist-Medium.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-Medium.ttf') format('truetype');
font-weight: 500;
font-style: normal;
font-display: swap;
font-family: 'Geist';
src:
url('#{$assets-base}/fonts/geist-sans/Geist-Medium.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-Medium.ttf') format('truetype');
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Geist';
src: url('#{$assets-base}/fonts/geist-sans/Geist-SemiBold.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-SemiBold.ttf') format('truetype');
font-weight: 600;
font-style: normal;
font-display: swap;
font-family: 'Geist';
src:
url('#{$assets-base}/fonts/geist-sans/Geist-SemiBold.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-SemiBold.ttf') format('truetype');
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Geist';
src: url('#{$assets-base}/fonts/geist-sans/Geist-Bold.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-Bold.ttf') format('truetype');
font-weight: 700;
font-style: normal;
font-display: swap;
font-family: 'Geist';
src:
url('#{$assets-base}/fonts/geist-sans/Geist-Bold.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-Bold.ttf') format('truetype');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Geist';
src: url('#{$assets-base}/fonts/geist-sans/Geist-Black.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-Black.ttf') format('truetype');
font-weight: 800;
font-style: normal;
font-display: swap;
font-family: 'Geist';
src:
url('#{$assets-base}/fonts/geist-sans/Geist-Black.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-Black.ttf') format('truetype');
font-weight: 800;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Geist';
src: url('#{$assets-base}/fonts/geist-sans/Geist-UltraBlack.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-UltraBlack.ttf') format('truetype');
font-weight: 900;
font-style: normal;
font-display: swap;
font-family: 'Geist';
src:
url('#{$assets-base}/fonts/geist-sans/Geist-UltraBlack.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-UltraBlack.ttf') format('truetype');
font-weight: 900;
font-style: normal;
font-display: swap;
}
// Geist Sans Variable Font
@font-face {
font-family: 'Geist';
src: url('#{$assets-base}/fonts/geist-sans/Geist-Variable.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-Variable.ttf') format('truetype');
font-weight: 100 900;
font-style: normal;
font-display: swap;
font-family: 'Geist';
src:
url('#{$assets-base}/fonts/geist-sans/Geist-Variable.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-sans/Geist-Variable.ttf') format('truetype');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
// Geist Mono Font Family
@font-face {
font-family: 'Geist Mono';
src: url('#{$assets-base}/fonts/geist-mono/GeistMono-Thin.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-Thin.ttf') format('truetype');
font-weight: 100;
font-style: normal;
font-display: swap;
font-family: 'Geist Mono';
src:
url('#{$assets-base}/fonts/geist-mono/GeistMono-Thin.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-Thin.ttf') format('truetype');
font-weight: 100;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Geist Mono';
src: url('#{$assets-base}/fonts/geist-mono/GeistMono-UltraLight.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-UltraLight.ttf') format('truetype');
font-weight: 200;
font-style: normal;
font-display: swap;
font-family: 'Geist Mono';
src:
url('#{$assets-base}/fonts/geist-mono/GeistMono-UltraLight.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-UltraLight.ttf') format('truetype');
font-weight: 200;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Geist Mono';
src: url('#{$assets-base}/fonts/geist-mono/GeistMono-Light.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-Light.ttf') format('truetype');
font-weight: 300;
font-style: normal;
font-display: swap;
font-family: 'Geist Mono';
src:
url('#{$assets-base}/fonts/geist-mono/GeistMono-Light.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-Light.ttf') format('truetype');
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Geist Mono';
src: url('#{$assets-base}/fonts/geist-mono/GeistMono-Regular.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-Regular.ttf') format('truetype');
font-weight: 400;
font-style: normal;
font-display: swap;
font-family: 'Geist Mono';
src:
url('#{$assets-base}/fonts/geist-mono/GeistMono-Regular.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-Regular.ttf') format('truetype');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Geist Mono';
src: url('#{$assets-base}/fonts/geist-mono/GeistMono-Medium.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-Medium.ttf') format('truetype');
font-weight: 500;
font-style: normal;
font-display: swap;
font-family: 'Geist Mono';
src:
url('#{$assets-base}/fonts/geist-mono/GeistMono-Medium.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-Medium.ttf') format('truetype');
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Geist Mono';
src: url('#{$assets-base}/fonts/geist-mono/GeistMono-SemiBold.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-SemiBold.ttf') format('truetype');
font-weight: 600;
font-style: normal;
font-display: swap;
font-family: 'Geist Mono';
src:
url('#{$assets-base}/fonts/geist-mono/GeistMono-SemiBold.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-SemiBold.ttf') format('truetype');
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Geist Mono';
src: url('#{$assets-base}/fonts/geist-mono/GeistMono-Bold.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-Bold.ttf') format('truetype');
font-weight: 700;
font-style: normal;
font-display: swap;
font-family: 'Geist Mono';
src:
url('#{$assets-base}/fonts/geist-mono/GeistMono-Bold.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-Bold.ttf') format('truetype');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Geist Mono';
src: url('#{$assets-base}/fonts/geist-mono/GeistMono-Black.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-Black.ttf') format('truetype');
font-weight: 800;
font-style: normal;
font-display: swap;
font-family: 'Geist Mono';
src:
url('#{$assets-base}/fonts/geist-mono/GeistMono-Black.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-Black.ttf') format('truetype');
font-weight: 800;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Geist Mono';
src: url('#{$assets-base}/fonts/geist-mono/GeistMono-UltraBlack.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-UltraBlack.ttf') format('truetype');
font-weight: 900;
font-style: normal;
font-display: swap;
font-family: 'Geist Mono';
src:
url('#{$assets-base}/fonts/geist-mono/GeistMono-UltraBlack.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-UltraBlack.ttf') format('truetype');
font-weight: 900;
font-style: normal;
font-display: swap;
}
// Geist Mono Variable Font
@font-face {
font-family: 'Geist Mono';
src: url('#{$assets-base}/fonts/geist-mono/GeistMono-Variable.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-Variable.ttf') format('truetype');
font-weight: 100 900;
font-style: normal;
font-display: swap;
font-family: 'Geist Mono';
src:
url('#{$assets-base}/fonts/geist-mono/GeistMono-Variable.woff2') format('woff2'),
url('#{$assets-base}/fonts/geist-mono/GeistMono-Variable.ttf') format('truetype');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}

View File

@@ -119,7 +119,7 @@
content: '';
}
>* {
> * {
position: absolute;
top: 0;
inset-inline-start: 0;
@@ -141,4 +141,4 @@
outline: 0;
// By default, there is no `--bs-focus-ring-x`, `--bs-focus-ring-y`, or `--bs-focus-ring-blur`, but we provide CSS variables with fallbacks to initial `0` values
box-shadow: var(--#{$prefix}focus-ring-x, 0) var(--#{$prefix}focus-ring-y, 0) var(--#{$prefix}focus-ring-blur, 0) var(--#{$prefix}focus-ring-width) var(--#{$prefix}focus-ring-color);
}
}

View File

@@ -1,5 +1,13 @@
@use 'sass:map';
html {
scrollbar-gutter: stable;
@supports not (scrollbar-gutter: stable) {
overflow-y: scroll;
}
}
// stylelint-disable property-no-vendor-prefix
body {
letter-spacing: $body-letter-spacing;

View File

@@ -69,7 +69,7 @@
.nav-item.active:after {
border-bottom-width: 0;
border-inline-start-width: 3px;
inset-inline-end: auto;
inset-inline-end: auto;
top: 0;
bottom: 0;
}
@@ -118,7 +118,7 @@ Navbar
.badge {
position: absolute;
top: 0.5rem;
inset-inline-end: 0.5rem;
inset-inline-end: 0.5rem;
transform: translate(50%, -50%);
}
}
@@ -151,8 +151,8 @@ Navbar
&:after {
content: '';
position: absolute;
inset-inline-start: 0;
inset-inline-end: 0;
inset-inline-start: 0;
inset-inline-end: 0;
bottom: -0.25rem;
border: 0 var(--#{$prefix}border-style) var(--#{$prefix}navbar-active-border-color);
border-bottom-width: 2px;
@@ -235,7 +235,7 @@ Navbar toggler
border-radius: inherit;
background: inherit;
position: absolute;
inset-inline-start: 0;
inset-inline-start: 0;
@include transition(inherit);
}
@@ -313,7 +313,7 @@ Navbar vertical
width: $sidebar-width;
position: fixed;
top: 0;
inset-inline-start: 0;
inset-inline-start: 0;
bottom: 0;
z-index: $zindex-fixed;
align-items: start;
@@ -323,8 +323,8 @@ Navbar vertical
&.navbar-right,
&.navbar-end {
inset-inline-start: auto;
inset-inline-end: 0;
inset-inline-start: auto;
inset-inline-end: 0;
}
.navbar-brand {
@@ -384,8 +384,8 @@ Navbar vertical
height: $navbar-overlap-height;
position: absolute;
top: 100%;
inset-inline-start: 0;
inset-inline-end: 0;
inset-inline-start: 0;
inset-inline-end: 0;
background: inherit;
z-index: -1;
box-shadow: inherit;

View File

@@ -67,8 +67,8 @@
content: '';
position: absolute;
top: 0;
inset-inline-start: 0;
inset-inline-end: 0;
inset-inline-start: 0;
inset-inline-end: 0;
bottom: 0;
background-image: $overlay-gradient;
}

View File

@@ -2,11 +2,6 @@
:host {
font-size: 16px;
height: 100%;
@include media-breakpoint-up(lg) {
margin-inline-start: calc(100vw - 100%);
margin-inline-end: 0;
}
}
:root,
@@ -38,7 +33,10 @@
--#{$prefix}border-color-translucent: #{$border-color-translucent};
--#{$prefix}border-dark-color: #{$border-dark-color};
--#{$prefix}border-dark-color-translucent: #{$border-dark-color-translucent};
--#{$prefix}border-light-color: #{$border-light-color};
--#{$prefix}border-light-color-translucent: #{$border-light-color-translucent};
--#{$prefix}border-active-color: #{$border-active-color};
--#{$prefix}border-active-color-translucent: #{$border-active-color-translucent};
--#{$prefix}icon-color: #{$icon-color};

View File

@@ -79,7 +79,7 @@ $pricing-card-width: 22rem;
justify-content: center;
font-size: 2.5rem;
line-height: 1;
font-weight: $font-weight-bold;
font-weight: $font-weight-semibold;
margin: 0.75rem 0;
}
@@ -87,7 +87,7 @@ $pricing-card-width: 22rem;
font-size: $h2-font-size;
line-height: 1.5;
margin-inline-end: 0.25rem;
font-weight: $font-weight-bold;
font-weight: $font-weight-semibold;
}
.pricing-price-description {

View File

@@ -72,7 +72,7 @@
.section-title {
font-size: var(--#{$prefix}font-size-h1);
font-weight: var(--#{$prefix}font-weight-bold);
font-weight: var(--#{$prefix}font-weight-semibold);
line-height: 1.2;
}

View File

@@ -138,18 +138,20 @@
// Colors
@function to-rgb($value) {
@debug $value;
@return color.channel($value, 'red', $space: rgb), color.channel($value, 'green', $space: rgb), color.channel($value, 'blue', $space: rgb);
}
// stylelint-disable scss/dollar-variable-pattern
@function rgba-css-var($identifier, $target) {
@if $identifier == 'body' and $target == 'bg' {
@return rgba(var(--#{$prefix}#{$identifier}-bg-rgb), var(--#{$prefix}#{$target}-opacity));
@return color-mix(in srgb, var(--#{$prefix}#{$identifier}-bg) calc(var(--#{$prefix}#{$target}-opacity) * 100%), transparent);
}
@if $identifier == 'body' and $target == 'text' {
@return rgba(var(--#{$prefix}#{$identifier}-color-rgb), var(--#{$prefix}#{$target}-opacity));
@return color-mix(in srgb, var(--#{$prefix}#{$identifier}-color) calc(var(--#{$prefix}#{$target}-opacity) * 100%), transparent);
} @else {
@return rgba(var(--#{$prefix}#{$identifier}-rgb), var(--#{$prefix}#{$target}-opacity));
@return color-mix(in srgb, var(--#{$prefix}#{$identifier}) calc(var(--#{$prefix}#{$target}-opacity) * 100%), transparent);
}
}

View File

@@ -59,10 +59,10 @@
@mixin focus-ring($show-border: false) {
outline: 0;
box-shadow: 0 0 $focus-ring-blur $focus-ring-width rgba(var(--#{$prefix}primary-rgb), 0.25);
box-shadow: 0 0 $focus-ring-blur $focus-ring-width color-transparent(var(--#{$prefix}primary), 0.25);
@if ($show-border) {
border-color: rgba(var(--#{$prefix}primary-rgb), 0.25);
border-color: color-transparent(var(--#{$prefix}primary), 0.25);
}
}

View File

@@ -15,17 +15,17 @@
}
[data-bs-theme-base='gray'] {
--#{$prefix}gray-50: #f9fafb;
--#{$prefix}gray-100: #f3f4f6;
--#{$prefix}gray-200: #e5e7eb;
--#{$prefix}gray-300: #d1d5db;
--#{$prefix}gray-400: #9ca3af;
--#{$prefix}gray-500: #6b7280;
--#{$prefix}gray-600: #4b5563;
--#{$prefix}gray-700: #374151;
--#{$prefix}gray-800: #1f2937;
--#{$prefix}gray-900: #111827;
--#{$prefix}gray-950: #030712;
--#{$prefix}gray-50: $gray-50;
--#{$prefix}gray-100: $gray-100;
--#{$prefix}gray-200: $gray-200;
--#{$prefix}gray-300: $gray-300;
--#{$prefix}gray-400: $gray-400;
--#{$prefix}gray-500: $gray-500;
--#{$prefix}gray-600: $gray-600;
--#{$prefix}gray-700: $gray-700;
--#{$prefix}gray-800: $gray-800;
--#{$prefix}gray-900: $gray-900;
--#{$prefix}gray-950: $gray-950;
}
[data-bs-theme-base='zinc'] {

View File

@@ -1,10 +1,12 @@
.alert {
--#{$prefix}alert-color: var(--#{$prefix}body-color);
--#{$prefix}alert-bg: #{color-transparent(var(--#{$prefix}alert-color), 0.1)};
--#{$prefix}alert-variant-color: var(--#{$prefix}body-color);
--#{$prefix}alert-color: var(--#{$prefix}alert-variant-color);
--#{$prefix}alert-bg: #{color-transparent(var(--#{$prefix}alert-variant-color), 0.16, var(--#{$prefix}bg-surface))};
--#{$prefix}alert-padding-x: #{$alert-padding-x};
--#{$prefix}alert-padding-y: #{$alert-padding-y};
--#{$prefix}alert-margin-bottom: #{$alert-margin-bottom};
--#{$prefix}alert-border-color: #{color-transparent(var(--#{$prefix}alert-color), 0.2)};
--#{$prefix}alert-border-color: #{color-transparent(var(--#{$prefix}alert-variant-color), 0.2, var(--#{$prefix}bg-surface))};
--#{$prefix}alert-border-color: var(--#{$prefix}border-color);
--#{$prefix}alert-border: var(--#{$prefix}border-width) solid var(--#{$prefix}alert-border-color);
--#{$prefix}alert-border-radius: var(--#{$prefix}border-radius);
--#{$prefix}alert-link-color: inherit;
@@ -16,6 +18,8 @@
background-color: color-mix(in srgb, var(--#{$prefix}alert-bg), var(--#{$prefix}bg-surface));
border-radius: var(--#{$prefix}alert-border-radius);
border: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}alert-border-color);
box-shadow: var(--#{$prefix}box-shadow);
color: var(--#{$prefix}alert-color);
display: flex;
flex-direction: row;
gap: 1rem;
@@ -66,15 +70,14 @@
.btn-close {
position: absolute;
top: calc(var(--#{$prefix}alert-padding-x) / 2 - 1px);
inset-inline-end: calc(var(--#{$prefix}alert-padding-y) / 2 - 1px);
inset-inline-end: calc(var(--#{$prefix}alert-padding-y) / 2 - 1px);
z-index: 1;
padding: calc(var(--#{$prefix}alert-padding-y) * 1.25) var(--#{$prefix}alert-padding-x);
}
}
.alert-important {
border-color: var(--#{$prefix}alert-color);
background-color: var(--#{$prefix}alert-color);
background-color: var(--#{$prefix}alert-variant-color);
color: var(--#{$prefix}white);
.alert-description {
@@ -93,6 +96,6 @@
@each $name, $color in $theme-colors {
.alert-#{$name} {
--#{$prefix}alert-color: var(--#{$prefix}#{$name});
--#{$prefix}alert-variant-color: var(--#{$prefix}#{$name});
}
}

View File

@@ -9,6 +9,7 @@
--#{$prefix}avatar-font-size: #{$avatar-font-size};
--#{$prefix}avatar-icon-size: #{$avatar-icon-size};
--#{$prefix}avatar-brand-size: #{$avatar-brand-size};
--#{$prefix}avatar-border-radius: #{$avatar-border-radius};
position: relative;
width: var(--#{$prefix}avatar-size);
height: var(--#{$prefix}avatar-size);
@@ -24,7 +25,7 @@
vertical-align: bottom;
user-select: none;
background: var(--#{$prefix}avatar-bg) no-repeat center/cover;
border-radius: $avatar-border-radius;
border-radius: var(--#{$prefix}avatar-border-radius);
box-shadow: var(--#{$prefix}avatar-box-shadow);
transition:
color $transition-time,
@@ -38,7 +39,7 @@
.badge {
position: absolute;
inset-inline-end: 0;
inset-inline-end: 0;
bottom: 0;
border-radius: $border-radius-pill;
box-shadow: 0 0 0 calc(var(--#{$prefix}avatar-status-size) / 4) $card-bg;
@@ -58,6 +59,10 @@
border-radius: $border-radius-pill;
}
.avatar-square {
border-radius: var(--#{$prefix}border-radius);
}
@each $avatar-size, $size in $avatar-sizes {
.avatar-#{$avatar-size} {
--#{$prefix}avatar-size: #{map.get($size, size)};
@@ -66,14 +71,14 @@
--#{$prefix}avatar-icon-size: #{map.get($size, icon-size)};
--#{$prefix}avatar-brand-size: #{map.get($size, brand-size)};
@if map.has-key($size, border-radius) {
border-radius: map.get($size, border-radius);
}
.badge:empty {
width: map.get($size, status-size);
height: map.get($size, status-size);
}
&.avatar-square {
--#{$prefix}avatar-border-radius: #{map.get($size, border-radius)};
}
}
}
@@ -96,10 +101,13 @@
--#{$prefix}list-gap: 0;
.avatar {
margin-inline-end: calc(#{$avatar-list-spacing} * var(--#{$prefix}avatar-size)) !important;
box-shadow:
var(--#{$prefix}avatar-box-shadow),
0 0 0 2px var(--#{$prefix}card-bg, var(--#{$prefix}bg-surface));
&:not(:first-child) {
margin-inline-start: calc(#{$avatar-list-spacing} * var(--#{$prefix}avatar-size)) !important;
}
}
}

View File

@@ -76,6 +76,15 @@
//
// Button color variations
//
.btn-ghost {
--#{$prefix}btn-bg: transparent;
--#{$prefix}btn-border-color: transparent;
--#{$prefix}btn-box-shadow: none;
--#{$prefix}btn-hover-bg: var(--#{$prefix}bg-surface-secondary);
--#{$prefix}btn-hover-border-color: transparent;
--#{$prefix}btn-hover-color: var(--#{$prefix}body-color);
}
@each $color, $value in map.merge($theme-colors, $social-colors) {
.btn-#{$color} {
@if $color == 'dark' {
@@ -114,15 +123,6 @@
--#{$prefix}btn-disabled-border-color: var(--#{$prefix}#{$color});
}
.btn-ghost {
--#{$prefix}btn-bg: transparent;
--#{$prefix}btn-border-color: transparent;
--#{$prefix}btn-box-shadow: none;
--#{$prefix}btn-hover-bg: var(--#{$prefix}bg-surface-secondary);
--#{$prefix}btn-hover-border-color: transparent;
--#{$prefix}btn-hover-color: var(--#{$prefix}body-color);
}
.btn-ghost-#{$color},
.btn-ghost.btn-#{$color} {
--#{$prefix}btn-color: var(--#{$prefix}#{$color});
@@ -207,6 +207,10 @@
@include elements-list;
}
.btn-list-center {
justify-content: center;
}
//
// Button floating
//
@@ -246,7 +250,7 @@
position: absolute;
width: var(--#{$prefix}btn-icon-size);
height: var(--#{$prefix}btn-icon-size);
inset-inline-start: calc(50% - var(--#{$prefix}btn-icon-size) / 2);
inset-inline-start: calc(50% - var(--#{$prefix}btn-icon-size) / 2);
top: calc(50% - var(--#{$prefix}btn-icon-size) / 2);
animation: spinner-border 0.75s linear infinite;
}

View File

@@ -77,11 +77,11 @@
&:before {
position: absolute;
top: 50%;
inset-inline-end: 0;
inset-inline-start: 0;
inset-inline-end: 0;
inset-inline-start: 0;
height: 1.4rem;
content: '';
background: rgba(var(--#{$prefix}primary-rgb), 0.1);
background: color-transparent(var(--#{$prefix}primary), 0.1);
transform: translateY(-50%);
}
@@ -95,10 +95,10 @@
}
&.range-start:before {
inset-inline-start: 50%;
inset-inline-start: 50%;
}
&.range-end:before {
inset-inline-end: 50%;
inset-inline-end: 50%;
}
}

View File

@@ -1,3 +1,21 @@
@use 'sass:map';
@property --tblr-card-gradient-direction {
syntax: '<angle>';
inherits: true;
initial-value: 180deg;
}
@keyframes gradient-animation {
0% {
--#{$prefix}card-gradient-direction: 180deg;
}
100% {
--#{$prefix}card-gradient-direction: 540deg;
}
}
.card {
@include transition(transform $transition-time ease-out, opacity $transition-time ease-out, box-shadow $transition-time ease-out);
@@ -29,6 +47,18 @@
}
}
// Card dashed
.card-dashed {
border: var(--#{$prefix}border-width) dashed var(--#{$prefix}border-color);
}
// Card transparent
.card-transparent {
background: transparent;
border: var(--#{$prefix}border-width) dashed var(--#{$prefix}border-color);
box-shadow: none;
}
// Card stamp
.card-stamp {
--#{$prefix}stamp-size: 7rem;
@@ -137,8 +167,8 @@ Stacked card
&:after {
position: absolute;
top: calc(-1 * var(--#{$prefix}card-stacked-offset));
inset-inline-end: var(--#{$prefix}card-stacked-offset);
inset-inline-start: var(--#{$prefix}card-stacked-offset);
inset-inline-end: var(--#{$prefix}card-stacked-offset);
inset-inline-start: var(--#{$prefix}card-stacked-offset);
height: var(--#{$prefix}card-stacked-offset);
content: '';
background: var(--#{$prefix}card-bg, var(--#{$prefix}bg-surface));
@@ -155,9 +185,9 @@ Stacked card
&:before {
position: absolute;
top: 0;
inset-inline-end: 0;
inset-inline-end: 0;
bottom: 0;
inset-inline-start: 0;
inset-inline-start: 0;
content: '';
background: rgba($dark, 0.48);
}
@@ -287,7 +317,7 @@ Stacked card
margin: 0 0 1rem;
font-size: $h3-font-size;
font-weight: var(--#{$prefix}font-weight-medium);
color: $headings-color;
color: var(--#{$prefix}heading-color);
line-height: 1.5rem;
@at-root a#{&}:hover {
@@ -411,6 +441,15 @@ Card table
td,
th {
&:first-child {
padding-left: $card-spacer-x;
border-left: 0;
border-top-left-radius: var(--#{$prefix}card-border-radius);
}
&:last-child {
padding-right: $card-spacer-x;
border-right: 0;
border-top-right-radius: var(--#{$prefix}card-border-radius);
padding-inline-start: $card-spacer-x;
border-inline-start: 0;
}
@@ -624,3 +663,75 @@ Card note
--#{$prefix}card-bg: #fff7dd;
--#{$prefix}card-border-color: #fff1c9;
}
/**
Card gradient
*/
.card-gradient {
--#{$prefix}card-gradient-direction: 180deg;
--#{$prefix}card-gradient-opacity: 86%;
--#{$prefix}card-gradient: var(--tblr-primary), var(--tblr-primary);
background:
radial-gradient(ellipse at center, var(--#{$prefix}card-bg) 0%, color-mix(in srgb, var(--#{$prefix}card-bg) 0%, transparent) 80%) border-box,
linear-gradient(var(--#{$prefix}card-gradient-direction), color-mix(in srgb, var(--#{$prefix}card-bg) var(--#{$prefix}card-gradient-opacity), transparent) 0%, var(--#{$prefix}card-bg) 40%) border-box,
linear-gradient(calc(270deg + var(--#{$prefix}card-gradient-direction)), var(--#{$prefix}card-gradient)) border-box;
}
@each $name, $color in map.merge($colors, $theme-colors) {
.card-gradient-#{$name} {
--#{$prefix}card-gradient: var(--tblr-#{$name}), var(--tblr-#{$name});
}
}
.card-gradient-rainbow {
--#{$prefix}card-gradient: #78c5d6, #459ba8, #79c267, #c5d647, #f5d63d, #f08b33, #e868a2, #be61a5;
}
.card-gradient-sun {
--#{$prefix}card-gradient: #fd1d1d, #fcb045;
}
.card-gradient-snow {
--#{$prefix}card-gradient: #333, #e9ecef;
}
.card-gradient-ocean {
--#{$prefix}card-gradient: #1cb5e0, #000851;
}
.card-gradient-mellow {
--#{$prefix}card-gradient: #f8ff00, #3ad59f;
}
.card-gradient-disco {
--#{$prefix}card-gradient: #fc466b, #3f5efb;
}
.card-gradient-psychedelic {
--#{$prefix}card-gradient: #fcc5e4, #fda34b, #ff7882, #c8699e, #7046aa, #0c1db8, #020f75;
}
.card-gradient-love {
--#{$prefix}card-gradient: #f235e6, #bc0707;
}
.card-gradient-gold {
--#{$prefix}card-gradient: #9d4100, #bf7122, #f59f00, #ffd700;
}
.card-gradient-animated {
animation: gradient-animation 15s linear infinite;
}
.card-gradient-bottom {
--#{$prefix}card-gradient-direction: 0deg;
}
.card-gradient-end {
--#{$prefix}card-gradient-direction: 270deg;
}
.card-gradient-start {
--#{$prefix}card-gradient-direction: 90deg;
}

View File

@@ -74,7 +74,7 @@
content: '';
position: absolute;
top: -0.25rem;
inset-inline-start: 0.75rem;
inset-inline-start: 0.75rem;
display: block;
background: inherit;
width: 14px;
@@ -90,8 +90,8 @@
&.dropdown-menu-end {
&:before {
inset-inline-end: 0.75rem;
inset-inline-start: auto;
inset-inline-end: 0.75rem;
inset-inline-start: auto;
}
}
}

View File

@@ -139,7 +139,7 @@ Form help
*/
.form-help {
display: inline-flex;
font-weight: var(--#{$prefix}font-weight-bold);
font-weight: var(--#{$prefix}font-weight-semibold);
align-items: center;
justify-content: center;
width: 1.125rem;

View File

@@ -82,7 +82,7 @@
}
&.active {
font-weight: var(--#{$prefix}font-weight-bold);
font-weight: var(--#{$prefix}font-weight-semibold);
color: inherit;
background: $list-group-active-bg;

View File

@@ -9,7 +9,7 @@
&:after {
position: absolute;
top: 0;
inset-inline-start: 0;
inset-inline-start: 0;
width: 100%;
height: 100%;
content: '';
@@ -33,8 +33,8 @@ Dimmer
.loader {
position: absolute;
top: 50%;
inset-inline-end: 0;
inset-inline-start: 0;
inset-inline-end: 0;
inset-inline-start: 0;
display: none;
margin: 0 auto;
transform: translateY(-50%);

View File

@@ -3,7 +3,7 @@
> .btn-close {
position: absolute;
top: 0;
inset-inline-end: 0;
inset-inline-end: 0;
width: $modal-header-height;
height: $modal-header-height;
margin: 0;

View File

@@ -17,7 +17,7 @@
.nav-link.active,
.nav-item.show .nav-link {
font-weight: var(--#{$prefix}nav-link-font-weight);
font-weight: var(--#{$prefix}font-weight-semibold);
color: var(--#{$prefix}nav-link-active-color);
}

View File

@@ -9,7 +9,7 @@
vertical-align: bottom;
font-style: normal;
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.1);
border-radius: 2px;
border-radius: var(--#{config.$prefix}border-radius);
}
@each $payment in config.$payment-providers {

View File

@@ -1,13 +1,13 @@
@keyframes progress-indeterminate {
0% {
inset-inline-end: 100%;
inset-inline-start: -35%;
inset-inline-end: 100%;
inset-inline-start: -35%;
}
100%,
60% {
inset-inline-end: -90%;
inset-inline-start: 100%;
inset-inline-end: -90%;
inset-inline-start: 100%;
}
}
@@ -64,7 +64,7 @@ Progress bar
position: absolute;
top: 0;
bottom: 0;
inset-inline-start: 0;
inset-inline-start: 0;
content: '';
background-color: inherit;
will-change: left, right;
@@ -93,6 +93,8 @@ Progressbg
.progressbg-text {
position: relative;
z-index: 1;
display: flex;
align-items: center;
@include text-truncate;
}

View File

@@ -9,7 +9,7 @@
z-index: 1;
padding: 0.25rem 0.75rem;
font-size: $card-ribbon-font-size;
font-weight: var(--#{$prefix}font-weight-bold);
font-weight: var(--#{$prefix}font-weight-semibold);
line-height: 1;
color: $white;
text-align: center;
@@ -25,7 +25,7 @@
&:before {
position: absolute;
inset-inline-end: 0;
inset-inline-end: 0;
bottom: 100%;
width: 0;
height: 0;
@@ -44,7 +44,7 @@
}
&.bg-#{$color}-lt {
border-color: rgba(var(--#{$prefix}#{$color}-rgb), 0.1) !important;
border-color: color-transparent(var(--#{$prefix}#{$color}), 0.1) !important;
}
}
}
@@ -65,7 +65,7 @@
&:before {
top: 0;
inset-inline-end: 100%;
inset-inline-end: 100%;
bottom: auto;
border-color: inherit;
border-top-color: transparent;
@@ -73,13 +73,13 @@
}
&.ribbon-start {
inset-inline-end: auto;
inset-inline-start: 0.75rem;
inset-inline-end: auto;
inset-inline-start: 0.75rem;
&:before {
top: 0;
inset-inline-end: 100%;
inset-inline-start: auto;
inset-inline-end: 100%;
inset-inline-start: auto;
}
}
}
@@ -92,7 +92,7 @@
&:before {
top: auto;
bottom: 100%;
inset-inline-start: 0;
inset-inline-start: 0;
border-color: inherit;
border-top-color: transparent;
border-inline-start-color: transparent;
@@ -111,7 +111,7 @@
&:after {
position: absolute;
top: 0;
inset-inline-end: 100%;
inset-inline-end: 100%;
display: block;
width: 0;
height: 0;
@@ -127,8 +127,8 @@
padding-inline-end: 0.5rem;
&:after {
inset-inline-end: auto;
inset-inline-start: 100%;
inset-inline-end: auto;
inset-inline-start: 100%;
border-inline-end-color: transparent;
border-inline-end-width: 0.5rem;
@@ -144,8 +144,8 @@
&:after {
top: 100%;
inset-inline-end: 0;
inset-inline-start: 0;
inset-inline-end: 0;
inset-inline-start: 0;
border-color: inherit;
border-width: 1rem;
border-top-width: 0;

View File

@@ -1,3 +1,5 @@
@use 'sass:map';
@keyframes status-pulsate-main {
40% {
transform: scale(1.25, 1.25);
@@ -50,7 +52,7 @@
padding: 0.25rem 0.75rem;
gap: 0.5rem;
color: var(--#{$prefix}status-color);
background: rgba(var(--#{$prefix}status-color-rgb), 0.1);
background: color-transparent(var(--#{$prefix}status-color), 0.1);
font-size: $font-size-base;
text-transform: none;
letter-spacing: normal;
@@ -74,10 +76,10 @@
color: var(--#{$prefix}body-color) !important;
}
@each $name, $color in $theme-colors {
@each $name, $color in map.merge($theme-colors, $social-colors) {
.status-#{$name} {
--#{$prefix}status-color: #{$color};
--#{$prefix}status-color-rgb: #{to-rgb($color)};
--#{$prefix}status-color: var(--#{$prefix}#{$name});
--#{$prefix}status-color-rgb: var(--#{$prefix}#{$name}-rgb);
}
}

View File

@@ -52,7 +52,7 @@
&:not(:last-child):after {
position: absolute;
inset-inline-start: 50%;
inset-inline-start: 50%;
width: 100%;
content: '';
transform: translateY(-50%);
@@ -67,7 +67,7 @@
content: '';
position: absolute;
top: 0;
inset-inline-start: 50%;
inset-inline-start: 50%;
z-index: 1;
box-sizing: content-box;
display: flex;
@@ -81,7 +81,7 @@
}
&.active {
font-weight: var(--#{$prefix}font-weight-bold);
font-weight: var(--#{$prefix}font-weight-semibold);
&:after {
background: var(--#{$prefix}steps-inactive-color);
@@ -137,7 +137,7 @@
&:before {
top: var(--#{$prefix}steps-dot-offset);
inset-inline-start: 0;
inset-inline-start: 0;
transform: translate(0, 0);
}
@@ -147,7 +147,7 @@
content: '';
transform: translateX(-50%);
top: var(--#{$prefix}steps-dot-offset);
inset-inline-start: calc(var(--#{$prefix}steps-dot-size) * 0.5);
inset-inline-start: calc(var(--#{$prefix}steps-dot-size) * 0.5);
width: var(--#{$prefix}steps-border-width);
height: calc(100% + 1rem);
}

View File

@@ -39,7 +39,7 @@
.switch-icon-b {
position: absolute;
top: 0;
inset-inline-start: 0;
inset-inline-start: 0;
opacity: 0;
}

View File

@@ -1,4 +1,6 @@
.table {
font: inherit;
thead {
th {
background: $table-th-bg;

View File

@@ -21,7 +21,7 @@
content: '';
position: absolute;
top: var(--#{$prefix}timeline-icon-size);
inset-inline-start: calc(var(--#{$prefix}timeline-icon-size) / 2);
inset-inline-start: calc(var(--#{$prefix}timeline-icon-size) / 2);
bottom: calc(-1 * var(--#{$prefix}page-padding));
width: var(--#{$prefix}border-width);
background-color: var(--#{$prefix}border-color);

View File

@@ -307,7 +307,7 @@ $text-variants: (
border-radius: var(--#{$prefix}border-radius);
line-height: calc(var(--#{$prefix}steps-item-size) - 2px);
font-size: var(--#{$prefix}font-size-h4);
font-weight: var(--#{$prefix}font-weight-bold);
font-weight: var(--#{$prefix}font-weight-semibold);
}
}

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