mirror of
https://github.com/tabler/tabler.git
synced 2026-08-05 19:03:18 +04:00
Update contributing guidelines to clarify custom property naming conventions and add postcss-prefix-custom-properties dependency for improved CSS processing.
This commit is contained in:
+9
-1
@@ -31,9 +31,11 @@ import { basename, join, resolve } from 'node:path'
|
||||
import { compile as compileSass } from 'sass'
|
||||
import postcss, { type Result } from 'postcss'
|
||||
import autoprefixer from 'autoprefixer'
|
||||
import prefixCustomProperties from 'postcss-prefix-custom-properties'
|
||||
import rtlcss from 'rtlcss'
|
||||
import CleanCSS from 'clean-css'
|
||||
import { addBanner } from '../shared/banner/index.mjs'
|
||||
import { cssVarIgnore, cssVarPrefix, inlineValueComments } from './css-var-prefix'
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
const flags = args.filter((arg) => arg.startsWith('--'))
|
||||
@@ -47,6 +49,7 @@ const withMinify = flags.includes('--minify')
|
||||
const withBanner = flags.includes('--banner')
|
||||
|
||||
const mapOptions = { inline: false, annotation: true, sourcesContent: true }
|
||||
|
||||
const written: string[] = []
|
||||
const pendingWrites: { file: string; content: string }[] = []
|
||||
|
||||
@@ -68,7 +71,12 @@ async function compile(entry: string): Promise<{ outFile: string; result: Result
|
||||
// Banner goes in before postcss runs, so the map it generates already counts
|
||||
// the banner's lines — and so does the rtl map chained onto it below.
|
||||
const input = withBanner ? addBanner(css, outFile) : css
|
||||
const result = await postcss([autoprefixer({ cascade: false })]).process(input, {
|
||||
// Prefixing is its own pass, ahead of the one that generates the map:
|
||||
// postcss maps generated positions back to *its own input*, so renaming
|
||||
// inside the map-generating pass would leave every mapping — and the
|
||||
// embedded sourcesContent — describing css that no longer exists on disk.
|
||||
const { css: prefixed } = await postcss([inlineValueComments, prefixCustomProperties({ prefix: cssVarPrefix, ignore: cssVarIgnore })]).process(input, { from: outFile, to: outFile, map: false })
|
||||
const result = await postcss([autoprefixer({ cascade: false })]).process(prefixed, {
|
||||
from: outFile,
|
||||
to: outFile,
|
||||
map: mapOptions,
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// The `--tblr-` custom-property prefix, applied at build time.
|
||||
//
|
||||
// Custom properties are authored bare in scss (`--card-bg`) rather than through
|
||||
// a Sass interpolation on every declaration and every var() call; the public
|
||||
// prefix is added here, in the css pipeline (see build-css.ts).
|
||||
//
|
||||
// Lives in its own module so scss/tests/css-var-prefix.test.mjs asserts against
|
||||
// the same configuration the build uses.
|
||||
import type { Root } from 'postcss'
|
||||
|
||||
export const cssVarPrefix = 'tblr-'
|
||||
|
||||
// Names that must survive unprefixed. Everything here belongs to a third-party
|
||||
// stylesheet we theme, and those libraries read their variables by their own
|
||||
// name — prefixing one detaches the theming with no error anywhere, in css or
|
||||
// at build time. Add an entry whenever a vendor override introduces a new
|
||||
// foreign name.
|
||||
export const cssVarIgnore = [
|
||||
/^--tblr-/, // already prefixed, e.g. names that cannot round-trip postcss (see $navbar-light-icon-color)
|
||||
/^--bs-/, // bootstrap
|
||||
/^--fc-/, // fullcalendar
|
||||
/^--gl-/, // star-rating.js
|
||||
/^--litepicker-/, // litepicker
|
||||
/^--plyr-/, // plyr
|
||||
/^--ts-/, // tom-select
|
||||
'--section-bg', // marketing sections, unprefixed since it was introduced
|
||||
]
|
||||
|
||||
// postcss parks a comment trailing a declaration value in `raws.value.raw` and
|
||||
// hands plugins the cleaned `value`, so any plugin that writes `decl.value`
|
||||
// back drops the comment. That silently ate every `/*rtl:ignore*/` marker — the
|
||||
// ones rtlcss reads to leave a declaration alone in the .rtl build. Folding raw
|
||||
// into value first costs nothing (it stringifies identically) and leaves the
|
||||
// comment in the value proper, where postcss-value-parser round-trips it.
|
||||
export const inlineValueComments = {
|
||||
postcssPlugin: 'tabler-inline-value-comments',
|
||||
Once(root: Root) {
|
||||
root.walkDecls((decl) => {
|
||||
if (decl.raws.value) decl.value = decl.raws.value.raw
|
||||
})
|
||||
},
|
||||
}
|
||||
Vendored
+5
@@ -5,6 +5,11 @@ declare module 'rtlcss' {
|
||||
export default function rtlcss(config?: unknown): Plugin
|
||||
}
|
||||
|
||||
declare module 'postcss-prefix-custom-properties' {
|
||||
import type { Plugin } from 'postcss'
|
||||
export default function prefixCustomProperties(options?: { prefix?: string; ignore?: (string | RegExp)[] }): Plugin
|
||||
}
|
||||
|
||||
declare module 'clean-css' {
|
||||
interface MinifyResult {
|
||||
styles: string
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tabler/core": minor
|
||||
---
|
||||
|
||||
Removed the `$prefix` Sass variable and its `--#{$prefix}` interpolations; the `--tblr-` custom property prefix is now applied at build time by `postcss-prefix-custom-properties`. Compiled CSS is unchanged, but the prefix can no longer be customised by overriding `$prefix` before importing the Sass sources.
|
||||
+1
-1
@@ -32,7 +32,7 @@ Do not edit `dist/` folders - they are generated by the build.
|
||||
|
||||
## Code style
|
||||
|
||||
- Use Bootstrap 5 conventions and Tabler's CSS custom properties pattern: `--#{$prefix}component-property`.
|
||||
- Use Bootstrap 5 conventions and Tabler's CSS custom properties pattern: `--component-property`. Write custom properties without a prefix — the build adds the public `--tblr-` prefix. Names owned by a third-party library you are theming must be listed in `cssVarIgnore` in `.build/css-var-prefix.ts` so they stay untouched.
|
||||
- Write documentation in simple English: short sentences, common words, direct instructions.
|
||||
- Ensure changes work in all supported browsers (see the [browser support docs](https://docs.tabler.io/ui/getting-started/browser-support/)).
|
||||
|
||||
|
||||
@@ -1541,7 +1541,10 @@ $navbar-light-active-color: var(--body-color) !default;
|
||||
$navbar-light-hover-color: var(--body-color) !default;
|
||||
$navbar-light-disabled-color: var(--disabled-color) !default;
|
||||
$navbar-light-active-bg: rgba(0, 0, 0, 0.2) !default;
|
||||
$navbar-light-icon-color: color-transparent(var(--body-color), 0.75) !default;
|
||||
// Spelled with the literal prefix: this value is url-encoded into the svg data
|
||||
// uri below, so the build-time prefixing in .build/build-css.ts never sees a
|
||||
// `var()` here to rewrite (it arrives as `var%28--body-color%29`).
|
||||
$navbar-light-icon-color: color-transparent(var(--tblr-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: color-transparent(var(--emphasis-color), 0.15) !default;
|
||||
$navbar-light-brand-hover-color: $navbar-light-active-color !default;
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`css custom-property prefixing > leaves vendor-owned names alone and prefixes everything else 1`] = `
|
||||
[
|
||||
"--fc-border-color",
|
||||
"--fc-daygrid-event-dot-width",
|
||||
"--fc-event-bg-color",
|
||||
"--fc-event-border-color",
|
||||
"--fc-event-text-color",
|
||||
"--gl-star-color",
|
||||
"--gl-star-color-inactive",
|
||||
"--gl-star-size",
|
||||
"--litepicker-button-next-month-color",
|
||||
"--litepicker-button-next-month-color-hover",
|
||||
"--litepicker-button-prev-month-color",
|
||||
"--litepicker-button-prev-month-color-hover",
|
||||
"--litepicker-container-months-color-bg",
|
||||
"--litepicker-day-color",
|
||||
"--litepicker-day-color-hover",
|
||||
"--litepicker-is-end-color-bg",
|
||||
"--litepicker-is-in-range-color",
|
||||
"--litepicker-is-start-color-bg",
|
||||
"--litepicker-is-today-color",
|
||||
"--litepicker-month-header-color",
|
||||
"--litepicker-month-weekday-color",
|
||||
"--plyr-color-main",
|
||||
"--tblr-active-bg",
|
||||
"--tblr-backdrop-bg",
|
||||
"--tblr-bg-forms",
|
||||
"--tblr-bg-surface",
|
||||
"--tblr-bg-surface-inverted",
|
||||
"--tblr-bg-surface-secondary",
|
||||
"--tblr-body-bg",
|
||||
"--tblr-body-color",
|
||||
"--tblr-border-color",
|
||||
"--tblr-border-color-translucent",
|
||||
"--tblr-border-radius",
|
||||
"--tblr-border-style",
|
||||
"--tblr-border-width",
|
||||
"--tblr-card-bg",
|
||||
"--tblr-font-sans-serif",
|
||||
"--tblr-font-size-h3",
|
||||
"--tblr-font-size-h5",
|
||||
"--tblr-font-weight-medium",
|
||||
"--tblr-font-weight-semibold",
|
||||
"--tblr-gray-500",
|
||||
"--tblr-icon-size",
|
||||
"--tblr-info",
|
||||
"--tblr-light",
|
||||
"--tblr-primary",
|
||||
"--tblr-primary-200",
|
||||
"--tblr-primary-lt",
|
||||
"--tblr-secondary",
|
||||
"--tblr-secondary-bg",
|
||||
"--tblr-shadow-card",
|
||||
"--tblr-shadow-dropdown",
|
||||
"--tblr-shadow-input",
|
||||
"--tblr-tertiary",
|
||||
"--tblr-text-inverted",
|
||||
"--tblr-yellow",
|
||||
"--ts-pr-caret",
|
||||
"--ts-pr-clear-button",
|
||||
]
|
||||
`;
|
||||
@@ -1,7 +1,7 @@
|
||||
// Tests for the CSS custom-property builders in scss/mixins/_functions.scss:
|
||||
// `varify()` and `rgba-css-var()`. Both read the global `$prefix`, which lives
|
||||
// in scss/_settings.scss (already `@use`d by functions.scss), so a plain `@use`
|
||||
// of the functions module is enough — no fixtures.
|
||||
// `varify()` and `rgba-css-var()`. A plain `@use` of the functions module is
|
||||
// enough — no fixtures. Both emit unprefixed names: the `--tblr-` prefix is
|
||||
// added later, by the css pipeline (.build/css-var-prefix.ts).
|
||||
|
||||
@use 'sass:meta';
|
||||
@use '../mixins/functions' as *;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Output tests for the `generate-utility` mixin from
|
||||
// scss/mixins/bootstrap/_utilities.scss — the engine behind Tabler's utility
|
||||
// classes. `@use "../mixins/bootstrap/utilities"` pulls in the real
|
||||
// settings/variables chain, so `$prefix` = "tblr-" and
|
||||
// `$enable-important-utilities` = true (hence the `!important` in the output).
|
||||
// settings/variables chain, so `$enable-important-utilities` = true (hence the
|
||||
// `!important` in the output). Custom properties are expected unprefixed: the
|
||||
// `--tblr-` prefix is added later, by the css pipeline (.build/css-var-prefix.ts).
|
||||
//
|
||||
// Each `it` feeds the mixin a hand-built utility map and asserts the generated
|
||||
// classes, covering the branches that shape the output: map vs list values,
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
//
|
||||
// `@use "../mixins/mixins"` transitively pulls in the real settings/variables
|
||||
// chain (all relative to that file, so no extra load paths are needed), which
|
||||
// means the assertions run against real values — `$prefix` = "tblr-", the real
|
||||
// `$h5-font-size`, etc. — instead of fixtures.
|
||||
// means the assertions run against real values — the real `$h5-font-size`,
|
||||
// etc. — instead of fixtures. Custom properties are expected unprefixed: the
|
||||
// `--tblr-` prefix is added later, by the css pipeline (.build/css-var-prefix.ts).
|
||||
|
||||
@use '../mixins/mixins' as *;
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// Guards the build-time `--tblr-` prefixing configured in .build/css-var-prefix.ts.
|
||||
//
|
||||
// Custom properties are authored bare in scss, so a name Tabler owns and a name
|
||||
// a vendor library owns look identical in source — only `cssVarIgnore` tells
|
||||
// them apart. Get that wrong and nothing fails: the vendor's variable quietly
|
||||
// becomes `--tblr-fc-border-color`, the library never reads it, and the theming
|
||||
// is simply gone.
|
||||
//
|
||||
// tabler-vendors.scss is where every foreign name lives, so its full set of
|
||||
// custom-property names is snapshotted. A vendor override added without the
|
||||
// matching `cssVarIgnore` entry shows up in the snapshot diff as a `--tblr-`
|
||||
// name that plainly belongs to someone else.
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { readdirSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { compile as compileSass } from 'sass'
|
||||
import postcss from 'postcss'
|
||||
import prefixCustomProperties from 'postcss-prefix-custom-properties'
|
||||
import { cssVarIgnore, cssVarPrefix, inlineValueComments } from '../../../.build/css-var-prefix.ts'
|
||||
|
||||
const scssDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
|
||||
// The same two passes build-css.ts runs before autoprefixer.
|
||||
async function buildCustomPropertyNames(entry) {
|
||||
const { css } = compileSass(path.join(scssDir, entry), { loadPaths: ['node_modules'], style: 'expanded' })
|
||||
const result = await postcss([
|
||||
inlineValueComments,
|
||||
prefixCustomProperties({ prefix: cssVarPrefix, ignore: cssVarIgnore }),
|
||||
]).process(css, { from: undefined })
|
||||
|
||||
const names = new Set()
|
||||
result.root.walkDecls((decl) => {
|
||||
if (decl.prop.startsWith('--')) names.add(decl.prop)
|
||||
for (const [, name] of decl.value.matchAll(/var\(\s*(--[\w-]+)/g)) names.add(name)
|
||||
})
|
||||
result.root.walkAtRules('property', (atRule) => names.add(atRule.params.trim()))
|
||||
|
||||
return [...names].sort()
|
||||
}
|
||||
|
||||
describe('css custom-property prefixing', () => {
|
||||
it('leaves vendor-owned names alone and prefixes everything else', async () => {
|
||||
await expect(buildCustomPropertyNames('tabler-vendors.scss')).resolves.toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('has no dead entries in the ignore list', async () => {
|
||||
// A pattern matching nothing means the vendor it protected is gone, or the
|
||||
// name drifted — either way the entry no longer guards anything.
|
||||
// Every entry point, since foreign names are spread across the bundles.
|
||||
const entries = readdirSync(scssDir).filter((file) => file.endsWith('.scss') && !file.startsWith('_'))
|
||||
const perEntry = await Promise.all(entries.map(buildCustomPropertyNames))
|
||||
const all = [...new Set(perEntry.flat())]
|
||||
|
||||
for (const pattern of cssVarIgnore) {
|
||||
const matches =
|
||||
pattern instanceof RegExp ? all.some((name) => pattern.test(name)) : all.includes(pattern)
|
||||
expect(matches, `unused cssVarIgnore entry: ${pattern}`).toBe(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -47,6 +47,7 @@
|
||||
"nodemon": "^3.1.14",
|
||||
"pnpm": "10.34.4",
|
||||
"postcss": "^8.5.23",
|
||||
"postcss-prefix-custom-properties": "^0.1.0",
|
||||
"prettier": "^3.9.6",
|
||||
"prettier-plugin-astro": "^0.14.1",
|
||||
"rtlcss": "^4.3.0",
|
||||
|
||||
Generated
+13
@@ -60,6 +60,9 @@ importers:
|
||||
postcss:
|
||||
specifier: ^8.5.23
|
||||
version: 8.5.23
|
||||
postcss-prefix-custom-properties:
|
||||
specifier: ^0.1.0
|
||||
version: 0.1.0(postcss@8.5.23)
|
||||
prettier:
|
||||
specifier: ^3.9.6
|
||||
version: 3.9.6
|
||||
@@ -3673,6 +3676,11 @@ packages:
|
||||
engines: {node: '>=18.12'}
|
||||
hasBin: true
|
||||
|
||||
postcss-prefix-custom-properties@0.1.0:
|
||||
resolution: {integrity: sha512-sAKQSRw+KTl9G549Q9awV/ra06fOZNEIVIIRTwiSTbvkV1t8z7WPqNQ5XBbxF1fV2mp0yQFObavc5J/GxP0a6g==}
|
||||
peerDependencies:
|
||||
postcss: ^8.4.0
|
||||
|
||||
postcss-scss@4.0.9:
|
||||
resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==}
|
||||
engines: {node: '>=12.0'}
|
||||
@@ -8464,6 +8472,11 @@ snapshots:
|
||||
|
||||
pnpm@10.34.4: {}
|
||||
|
||||
postcss-prefix-custom-properties@0.1.0(postcss@8.5.23):
|
||||
dependencies:
|
||||
postcss: 8.5.23
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
postcss-scss@4.0.9(postcss@8.5.23):
|
||||
dependencies:
|
||||
postcss: 8.5.23
|
||||
|
||||
Reference in New Issue
Block a user