From 3b8bf55c42be677b2056981f2a54ae5ffad63448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kuna?= <1282324+codecalm@users.noreply.github.com> Date: Wed, 23 Sep 2026 13:21:22 +0200 Subject: [PATCH] Write shared color tokens once to cut 72 kB from tabler.min.css (#3141) --- .changeset/colors-smaller-css.md | 5 ++ core/scss/_utilities.scss | 82 +++++++++++++----- core/scss/bootstrap/_list-group.scss | 42 ++++++--- core/scss/ui/_buttons.scss | 122 ++++++++++++++++----------- core/scss/ui/_patterns.scss | 55 ++++++++---- 5 files changed, 204 insertions(+), 102 deletions(-) create mode 100644 .changeset/colors-smaller-css.md diff --git a/.changeset/colors-smaller-css.md b/.changeset/colors-smaller-css.md new file mode 100644 index 000000000..9fc9f89f2 --- /dev/null +++ b/.changeset/colors-smaller-css.md @@ -0,0 +1,5 @@ +--- +"@tabler/core": patch +--- + +Updated the button, list group, `bg-pattern-*` and color utility styles to cut 72 kB from `tabler.min.css`. diff --git a/core/scss/_utilities.scss b/core/scss/_utilities.scss index 2233fe0e7..873063c79 100644 --- a/core/scss/_utilities.scss +++ b/core/scss/_utilities.scss @@ -1,3 +1,4 @@ +@use 'sass:list'; @use 'sass:map'; @use 'settings' as *; @use 'variables' as *; @@ -21,6 +22,25 @@ $utilities-border-colors: map-loop( 'border' ) !default; +// `utils/_colors.scss` emits `.text-*`, `.bg-*` and `.border-*` for these colors +// later in the file, with the same specificity, which always overrides the +// utilities generated here. The API only keeps what that file does not +// cover: the `--bg-opacity: 1` / `--border-opacity: 1` reset, which has to +// stay ahead of the `.bg-opacity-*` / `.border-opacity-*` classes. +$utilities-colors-overridden-text: map.keys($theme-colors); +$utilities-colors-overridden: list.join(map.keys($theme-colors), ('white'), comma); + +// `(color: 1)` for every overridden color the given utility map generates. +@function utilities-opacity-reset($utility-colors) { + $map: (); + @each $color in $utilities-colors-overridden { + @if map.has-key($utility-colors, $color) { + $map: map.merge($map, (#{$color}: 1)); + } + } + @return $map; +} + // Utilities $utilities: () !default; @@ -201,7 +221,13 @@ $utilities: map.merge( local-vars: ( 'border-opacity': 1, ), - values: $utilities-border-colors, + values: map.remove($utilities-border-colors, $utilities-colors-overridden...), + ), + 'border-color-opacity': ( + css-var: true, + css-variable-name: border-opacity, + class: border, + values: utilities-opacity-reset($utilities-border-colors), ), 'subtle-border-color': ( property: border-color, @@ -687,20 +713,23 @@ $utilities: map.merge( local-vars: ( 'text-opacity': 1, ), - values: map.merge( - $utilities-text-colors, - ( - 'muted': var(--secondary-color), - // deprecated - 'black-50': color-transparent(var(--black), 0.5), - // deprecated - 'white-50': color-transparent(var(--white), 0.5), - // deprecated - 'body-secondary': var(--secondary-color), - 'body-tertiary': var(--tertiary-color), - 'body-emphasis': var(--emphasis-color), - 'reset': inherit, - ) + values: map.remove( + map.merge( + $utilities-text-colors, + ( + 'muted': var(--secondary-color), + // deprecated + 'black-50': color-transparent(var(--black), 0.5), + // deprecated + 'white-50': color-transparent(var(--white), 0.5), + // deprecated + 'body-secondary': var(--secondary-color), + 'body-tertiary': var(--tertiary-color), + 'body-emphasis': var(--emphasis-color), + 'reset': inherit, + ) + ), + $utilities-colors-overridden-text... ), ), 'text-opacity': ( @@ -772,15 +801,24 @@ $utilities: map.merge( local-vars: ( 'bg-opacity': 1, ), - values: map.merge( - $utilities-bg-colors, - ( - 'transparent': transparent, - 'body-secondary': color-transparent(var(--secondary-bg), calc(var(--bg-opacity) * 100%)), - 'body-tertiary': color-transparent(var(--tertiary-bg), calc(var(--bg-opacity) * 100%)), - ) + values: map.remove( + map.merge( + $utilities-bg-colors, + ( + 'transparent': transparent, + 'body-secondary': color-transparent(var(--secondary-bg), calc(var(--bg-opacity) * 100%)), + 'body-tertiary': color-transparent(var(--tertiary-bg), calc(var(--bg-opacity) * 100%)), + ) + ), + $utilities-colors-overridden... ), ), + 'background-color-opacity': ( + css-var: true, + css-variable-name: bg-opacity, + class: bg, + values: utilities-opacity-reset($utilities-bg-colors), + ), 'bg-opacity': ( css-var: true, class: bg-opacity, diff --git a/core/scss/bootstrap/_list-group.scss b/core/scss/bootstrap/_list-group.scss index b9b284937..b64c4827e 100644 --- a/core/scss/bootstrap/_list-group.scss +++ b/core/scss/bootstrap/_list-group.scss @@ -1,3 +1,4 @@ +@use 'sass:list'; @use 'sass:map'; // Base class @@ -190,22 +191,35 @@ // and the subtle/emphasis maps only cover the latter, so upstream's version // left `.list-group-item-azure` and twelve more reading tokens nobody defines. // The tint also matches the `bg-{color}-lt` utilities used elsewhere. +// Each color class only points the --list-group-variant* tokens at its palette +// colors; one shared rule turns them into the list group tokens, so the token +// list is written once instead of once per color. +$list-group-variant-selectors: (); + @each $state in map.keys($theme-colors) { + $list-group-variant-selectors: list.append($list-group-variant-selectors, '.list-group-item-#{$state}', $separator: comma); + .list-group-item-#{$state} { - --list-group-color: var(--#{$state}); - --list-group-bg: var(--#{$state}-lt); - --list-group-border-color: #{color-transparent(var(--#{$state}), $list-group-variant-border-alpha)}; - --list-group-action-color: var(--#{$state}); - --list-group-action-hover-color: var(--#{$state}); - --list-group-action-hover-bg: #{color-transparent(var(--#{$state}), $list-group-variant-hover-alpha)}; - --list-group-action-active-color: var(--#{$state}-fg); - --list-group-action-active-bg: var(--#{$state}); - // The selected row keeps the tint: `.list-group-item.active` paints its own - // neutral highlight, so a solid fill here would only show through as text - // on a background it was never matched against. - --list-group-active-color: var(--#{$state}); - --list-group-active-bg: var(--#{$state}-lt); - --list-group-active-border-color: #{color-transparent(var(--#{$state}), $list-group-variant-border-alpha)}; + --list-group-variant: var(--#{$state}); + --list-group-variant-lt: var(--#{$state}-lt); + --list-group-variant-fg: var(--#{$state}-fg); } } + +#{$list-group-variant-selectors} { + --list-group-color: var(--list-group-variant); + --list-group-bg: var(--list-group-variant-lt); + --list-group-border-color: #{color-transparent(var(--list-group-variant), $list-group-variant-border-alpha)}; + --list-group-action-color: var(--list-group-variant); + --list-group-action-hover-color: var(--list-group-variant); + --list-group-action-hover-bg: #{color-transparent(var(--list-group-variant), $list-group-variant-hover-alpha)}; + --list-group-action-active-color: var(--list-group-variant-fg); + --list-group-action-active-bg: var(--list-group-variant); + // The selected row keeps the tint: `.list-group-item.active` paints its own + // neutral highlight, so a solid fill here would only show through as text + // on a background it was never matched against. + --list-group-active-color: var(--list-group-variant); + --list-group-active-bg: var(--list-group-variant-lt); + --list-group-active-border-color: #{color-transparent(var(--list-group-variant), $list-group-variant-border-alpha)}; +} // scss-docs-end list-group-modifiers diff --git a/core/scss/ui/_buttons.scss b/core/scss/ui/_buttons.scss index fc060feea..3cd428b85 100644 --- a/core/scss/ui/_buttons.scss +++ b/core/scss/ui/_buttons.scss @@ -1,3 +1,4 @@ +@use 'sass:list'; @use 'sass:map'; // @@ -93,64 +94,87 @@ --btn-hover-color: var(--body-color); } -@each $color, $value in map.merge($theme-colors, $social-colors) { - .btn-#{$color} { - @if $color == 'dark' { - --btn-border-color: var(--dark-mode-border-color); - --btn-hover-border-color: var(--dark-mode-border-active-color); - --btn-active-border-color: var(--dark-mode-border-active-color); - } @else { - --btn-border-color: transparent; - --btn-hover-border-color: transparent; - --btn-active-border-color: transparent; +// Each color class only points the --btn-variant* tokens at its palette +// colors; the shared rules below turn them into the button tokens. The rules +// are grouped by variant (solid, outline, ghost) rather than by color, so each +// token list is written once instead of once per color. +$btn-colors: map.keys(map.merge($theme-colors, $social-colors)); + +@function btn-color-selectors($patterns...) { + $selectors: (); + @each $pattern in $patterns { + @each $color in $btn-colors { + $selectors: list.append($selectors, str-replace($pattern, '{c}', '#{$color}'), $separator: comma); } - - --btn-color: var(--#{$color}-fg, #{$white}); - --btn-bg: var(--#{$color}); - --btn-hover-color: var(--#{$color}-fg); - --btn-hover-bg: var(--#{$color}-darken); - --btn-active-color: var(--#{$color}-fg); - --btn-active-bg: var(--#{$color}-darken); - --btn-disabled-bg: var(--#{$color}); - --btn-disabled-color: var(--#{$color}-fg); - --btn-box-shadow: var(--shadow-input); } + @return $selectors; +} +@each $color in $btn-colors { + .btn-#{$color}, .btn-outline-#{$color}, - .btn-outline.btn-#{$color} { - --btn-color: var(--#{$color}); - --btn-bg: transparent; - --btn-border-color: var(--#{$color}); - --btn-hover-color: var(--#{$color}-fg); - --btn-hover-border-color: transparent; - --btn-hover-bg: var(--#{$color}); - --btn-active-color: var(--#{$color}-fg); - --btn-active-bg: var(--#{$color}); - --btn-active-border-color: var(--#{$color}); - --btn-disabled-color: var(--#{$color}); - --btn-disabled-border-color: var(--#{$color}); + .btn-ghost-#{$color} { + --btn-variant: var(--#{$color}); + --btn-variant-fg: var(--#{$color}-fg); + --btn-variant-darken: var(--#{$color}-darken); } +} - .btn-ghost-#{$color}, - .btn-ghost.btn-#{$color} { - --btn-color: var(--#{$color}); - --btn-bg: transparent; - --btn-border-color: transparent; - --btn-hover-color: var(--#{$color}-fg); - --btn-hover-bg: var(--#{$color}); - --btn-hover-border-color: var(--#{$color}); - --btn-active-color: var(--#{$color}-fg); - --btn-active-bg: var(--#{$color}); - --btn-active-border-color: transparent; - --btn-active-shadow: #{$btn-active-box-shadow}; - --btn-disabled-color: var(--#{$color}); - --btn-disabled-bg: transparent; - --btn-disabled-border-color: transparent; - --gradient: none; - --btn-box-shadow: none; +#{btn-color-selectors('.btn-{c}')} { + --btn-border-color: transparent; + --btn-hover-border-color: transparent; + --btn-active-border-color: transparent; + --btn-color: var(--btn-variant-fg, #{$white}); + --btn-bg: var(--btn-variant); + --btn-hover-color: var(--btn-variant-fg); + --btn-hover-bg: var(--btn-variant-darken); + --btn-active-color: var(--btn-variant-fg); + --btn-active-bg: var(--btn-variant-darken); + --btn-disabled-bg: var(--btn-variant); + --btn-disabled-color: var(--btn-variant-fg); + --btn-box-shadow: var(--shadow-input); +} + +@if map.has-key($theme-colors, 'dark') { + .btn-dark { + --btn-border-color: var(--dark-mode-border-color); + --btn-hover-border-color: var(--dark-mode-border-active-color); + --btn-active-border-color: var(--dark-mode-border-active-color); } } +#{btn-color-selectors('.btn-outline-{c}', '.btn-outline.btn-{c}')} { + --btn-color: var(--btn-variant); + --btn-bg: transparent; + --btn-border-color: var(--btn-variant); + --btn-hover-color: var(--btn-variant-fg); + --btn-hover-border-color: transparent; + --btn-hover-bg: var(--btn-variant); + --btn-active-color: var(--btn-variant-fg); + --btn-active-bg: var(--btn-variant); + --btn-active-border-color: var(--btn-variant); + --btn-disabled-color: var(--btn-variant); + --btn-disabled-border-color: var(--btn-variant); +} + +#{btn-color-selectors('.btn-ghost-{c}', '.btn-ghost.btn-{c}')} { + --btn-color: var(--btn-variant); + --btn-bg: transparent; + --btn-border-color: transparent; + --btn-hover-color: var(--btn-variant-fg); + --btn-hover-bg: var(--btn-variant); + --btn-hover-border-color: var(--btn-variant); + --btn-active-color: var(--btn-variant-fg); + --btn-active-bg: var(--btn-variant); + --btn-active-border-color: transparent; + --btn-active-shadow: #{$btn-active-box-shadow}; + --btn-disabled-color: var(--btn-variant); + --btn-disabled-bg: transparent; + --btn-disabled-border-color: transparent; + --gradient: none; + --btn-box-shadow: none; +} + // // Button sizes // diff --git a/core/scss/ui/_patterns.scss b/core/scss/ui/_patterns.scss index 3d97c1d2e..91212e617 100644 --- a/core/scss/ui/_patterns.scss +++ b/core/scss/ui/_patterns.scss @@ -8,26 +8,37 @@ background-clip: padding-box; } -.bg-pattern-transparent { +// The shared tokens are written once for every pattern through @extend; a +// pattern with a different opacity only overrides --pattern-opacity. +%pattern-base { @include pattern-base(); +} + +@mixin pattern-opacity($opacity) { + --pattern-opacity: calc(var(--pattern-opacity-factor, #{$pattern-opacity-factor}) * #{$opacity}); +} + +.bg-pattern-transparent { + @extend %pattern-base; background-image: repeating-conic-gradient(var(--pattern-fill) 0% 25%, transparent 0% 50%); background-size: calc(var(--pattern-size) * 2) calc(var(--pattern-size) * 2); } .bg-pattern-diagonal { - @include pattern-base(); + @extend %pattern-base; background-image: repeating-linear-gradient(-45deg, var(--pattern-fill) 0 1px, transparent 0 50%); background-size: var(--pattern-size) var(--pattern-size); } .bg-pattern-diagonal-2 { - @include pattern-base(); + @extend %pattern-base; background-image: repeating-linear-gradient(45deg, var(--pattern-fill) 0 1px, transparent 0 50%); background-size: var(--pattern-size) var(--pattern-size); } .bg-pattern-dots { - @include pattern-base(20%); + @extend %pattern-base; + @include pattern-opacity(20%); --pattern-dot: max(1px, calc(var(--pattern-size) * 0.1)); background-image: radial-gradient(circle, var(--pattern-fill) var(--pattern-dot), transparent calc(var(--pattern-dot) + 0.5px)), radial-gradient(circle, var(--pattern-fill) var(--pattern-dot), transparent calc(var(--pattern-dot) + 0.5px)); background-position: @@ -37,7 +48,8 @@ } .bg-pattern-rectangles { - @include pattern-base(3%); + @extend %pattern-base; + @include pattern-opacity(3%); background-image: repeating-linear-gradient(45deg, var(--pattern-fill) 25%, transparent 25%, transparent 75%, var(--pattern-fill) 75%, var(--pattern-fill)), repeating-linear-gradient(45deg, var(--pattern-fill) 25%, transparent 25%, transparent 75%, var(--pattern-fill) 75%, var(--pattern-fill)); background-position: -1px -1px, @@ -46,21 +58,24 @@ } .bg-pattern-lines { - @include pattern-base(6%); + @extend %pattern-base; + @include pattern-opacity(6%); background-image: repeating-linear-gradient(0deg, var(--pattern-fill), var(--pattern-fill) 1px, transparent 1px, transparent); background-position: -1px -1px; background-size: var(--pattern-size) var(--pattern-size); } .bg-pattern-lines-vertical { - @include pattern-base(6%); + @extend %pattern-base; + @include pattern-opacity(6%); background-image: repeating-linear-gradient(90deg, var(--pattern-fill), var(--pattern-fill) 1px, transparent 1px, transparent); background-position: -1px -1px; background-size: var(--pattern-size) var(--pattern-size); } .bg-pattern-grid { - @include pattern-base(6%); + @extend %pattern-base; + @include pattern-opacity(6%); background-image: linear-gradient(to right, var(--pattern-fill) 1px, transparent 1px), linear-gradient(to bottom, var(--pattern-fill) 1px, transparent 1px); background-position: -1px -1px, @@ -69,14 +84,15 @@ } .bg-pattern-grid-diagonal { - @include pattern-base(); + @extend %pattern-base; background-image: linear-gradient(45deg, transparent 49%, var(--pattern-fill) 49%, var(--pattern-fill) 51%, transparent 51%), linear-gradient(-45deg, transparent 49%, var(--pattern-fill) 49%, var(--pattern-fill) 51%, transparent 51%); background-position: center center; background-size: calc(var(--pattern-size) * 2) calc(var(--pattern-size) * 2); } .bg-pattern-blueprint { - @include pattern-base(6%); + @extend %pattern-base; + @include pattern-opacity(6%); --pattern-line: var(--pattern-fill); background-image: linear-gradient(var(--pattern-line) 2px, transparent 2px), linear-gradient(90deg, var(--pattern-line) 2px, transparent 2px), linear-gradient(var(--pattern-line) 1px, transparent 1px), linear-gradient(90deg, var(--pattern-line) 1px, transparent 1px); background-position: @@ -92,7 +108,7 @@ } .bg-pattern-cross-dots { - @include pattern-base(); + @extend %pattern-base; background-image: radial-gradient(var(--pattern-fill) 1px, transparent 1px), radial-gradient(var(--pattern-fill) 1px, transparent 1px); background-position: 0 0, @@ -101,7 +117,7 @@ } .bg-pattern-circles { - @include pattern-base(); + @extend %pattern-base; background-image: radial-gradient(circle at center top, transparent 9%, var(--pattern-fill) 10%, var(--pattern-fill) 15%, transparent 16%), radial-gradient(circle at center bottom, transparent 9%, var(--pattern-fill) 10%, var(--pattern-fill) 15%, transparent 16%), radial-gradient(circle at right center, transparent 9%, var(--pattern-fill) 10%, var(--pattern-fill) 15%, transparent 16%), radial-gradient(circle at left center, transparent 9%, var(--pattern-fill) 10%, var(--pattern-fill) 15%, transparent 16%); @@ -110,19 +126,22 @@ } .bg-pattern-diagonal-stripes { - @include pattern-base(5%); + @extend %pattern-base; + @include pattern-opacity(5%); background-image: repeating-linear-gradient(45deg, transparent, transparent var(--pattern-size), var(--pattern-fill) var(--pattern-size), var(--pattern-fill) calc(2 * var(--pattern-size))); background-repeat: no-repeat; } .bg-pattern-diagonal-stripes-2 { - @include pattern-base(5%); + @extend %pattern-base; + @include pattern-opacity(5%); background-image: repeating-linear-gradient(-45deg, transparent, transparent var(--pattern-size), var(--pattern-fill) var(--pattern-size), var(--pattern-fill) calc(2 * var(--pattern-size))); background-repeat: no-repeat; } .bg-pattern-zigzag { - @include pattern-base(5%); + @extend %pattern-base; + @include pattern-opacity(5%); background: linear-gradient(135deg, var(--pattern-fill) 25%, transparent 25%) calc(-1 * var(--pattern-size)) 0, linear-gradient(225deg, var(--pattern-fill) 25%, transparent 25%) calc(-1 * var(--pattern-size)) 0, @@ -133,12 +152,14 @@ } .bg-pattern-vertical-stripes { - @include pattern-base(5%); + @extend %pattern-base; + @include pattern-opacity(5%); background-image: repeating-linear-gradient(90deg, transparent, transparent var(--pattern-size), var(--pattern-fill) var(--pattern-size), var(--pattern-fill) calc(2 * var(--pattern-size))); } .bg-pattern-horizontal-stripes { - @include pattern-base(5%); + @extend %pattern-base; + @include pattern-opacity(5%); background-image: repeating-linear-gradient(0deg, transparent, transparent var(--pattern-size), var(--pattern-fill) var(--pattern-size), var(--pattern-fill) calc(2 * var(--pattern-size))); background-position: -1px -1px; }