mirror of
https://github.com/tabler/tabler.git
synced 2026-10-12 13:17:00 +04:00
353 lines
14 KiB
SCSS
353 lines
14 KiB
SCSS
@use 'sass:math';
|
|
@use 'sass:string';
|
|
@use 'sass:list';
|
|
@use 'sass:map';
|
|
@use 'sass:color';
|
|
@use 'sass:meta';
|
|
@use '../settings' as *;
|
|
@use 'bootstrap/breakpoints' as *;
|
|
|
|
// Mixes two colours in `oklab` so tints and shades stay perceptually even, then
|
|
// clips the result to the sRGB gamut so every consumer (`luminance()`,
|
|
// `to-rgb()`, the emitted `rgb()` values) keeps working on legacy channels.
|
|
@function mix-color($color1, $color2, $weight: 50%) {
|
|
@return color.to-gamut(color.mix($color1, $color2, $weight, $method: oklab), $space: rgb, $method: clip);
|
|
}
|
|
|
|
// Runtime counterpart of `mix-color()`: returns a CSS `color-mix()` so the token
|
|
// follows `--tblr-*` overrides in the browser. Use it only where the result is
|
|
// emitted as-is; it cannot feed `color-contrast()`, `to-rgb()` or `luminance()`.
|
|
@function mix-color-css($color1, $color2, $weight: 50%) {
|
|
@return color-mix(in oklab, #{$color1} #{to-percentage($weight)}, #{$color2});
|
|
}
|
|
|
|
// Shifts lightness in `oklch` (perceptual, keeps hue and chroma) and clips the
|
|
// result to sRGB. `$amount` is a percentage added to the oklch L channel.
|
|
@function adjust-lightness($color, $amount) {
|
|
@return color.to-gamut(color.adjust($color, $lightness: $amount, $space: oklch), $space: rgb, $method: clip);
|
|
}
|
|
|
|
// Runtime counterpart of `adjust-lightness()`: relative color syntax, so the
|
|
// token follows `--tblr-*` overrides in the browser. Emit-only, like `mix-color-css()`.
|
|
//
|
|
// Only `l` goes through `calc()`. Safari before 18 resolves `h` to an angle, so
|
|
// `calc(h + 30)` is invalid there and needs `calc(h + 30deg)`; `l` and `c` are
|
|
// numbers in every browser. The origin must be a plain color: Safari before 18
|
|
// does not take `currentcolor`, a system color or `light-dark()` there.
|
|
@function adjust-lightness-css($color, $amount) {
|
|
$delta: math.div($amount, 100%);
|
|
$sign: if(sass($delta < 0): '-'; else: '+');
|
|
@return oklch(from #{$color} calc(l #{$sign} #{math.abs($delta)}) c h);
|
|
}
|
|
|
|
// Converts a given value to a percentage string.
|
|
//
|
|
// @param {Number} $value - The value to be converted to a percentage.
|
|
// @return {String} - The percentage representation of the value.
|
|
@function to-percentage($value) {
|
|
@if meta.type-of($value) == number and math.is-unitless($value) {
|
|
@return math.percentage($value);
|
|
}
|
|
@return $value;
|
|
}
|
|
|
|
// Generates a transparent version of the given color.
|
|
//
|
|
// @param {Color} $color - The base color to be made transparent.
|
|
// @param {Number} $alpha - The level of transparency, ranging from 0 (fully transparent) to 1 (fully opaque). Default is 1.
|
|
// @return {Color} - The resulting color with the specified transparency.
|
|
//
|
|
// Mixes in `oklab` so tints against an opaque background (e.g. alert backgrounds
|
|
// over `--bg-surface`) stay perceptually even; mixing with `transparent` gives
|
|
// the same result in every color space.
|
|
@function color-transparent($color, $alpha: 1, $background: transparent) {
|
|
@if $alpha == 1 or $alpha == 100% {
|
|
@return $color;
|
|
}
|
|
@return color-mix(in oklab, #{$color} #{to-percentage($alpha)}, $background);
|
|
}
|
|
|
|
// Focus ring colour of `$color`, as in Bootstrap v6: mixed with the page background
|
|
// instead of made transparent, so the ring is opaque and looks the same on any
|
|
// surface, and stronger in dark mode, where a 50% mix is too faint.
|
|
@function focus-ring-color($color) {
|
|
@return light-dark(color-transparent($color, calc(var(--focus-ring-opacity) * 100%), var(--body-bg)), color-transparent($color, calc(var(--focus-ring-opacity-dark) * 100%), var(--body-bg)));
|
|
}
|
|
|
|
// Paints a monochrome svg data uri (authored with `black`) in a Sass colour, for
|
|
// the few places that cannot use `mask-icon()`: a pseudo-element that already
|
|
// carries a background, or a form control without pseudo-elements. Pass a Sass
|
|
// colour, never `var()` — an svg image cannot resolve custom properties.
|
|
@function svg-icon-color($svg, $color) {
|
|
@return str-replace($svg, "'black'", "'#{$color}'");
|
|
}
|
|
|
|
// Bootstrap functions
|
|
//
|
|
// Utility mixins and functions for evaluating source code across our variables, maps, and mixins.
|
|
|
|
// Colors
|
|
@function to-rgb($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 color-transparent(var(--#{$identifier}-bg), calc(var(--#{$target}-opacity) * 100%));
|
|
}
|
|
@if $identifier == 'body' and $target == 'text' {
|
|
@return color-transparent(var(--#{$identifier}-color), calc(var(--#{$target}-opacity) * 100%));
|
|
}
|
|
@return color-transparent(var(--#{$identifier}), calc(var(--#{$target}-opacity) * 100%));
|
|
}
|
|
|
|
@function map-loop($map, $func, $args...) {
|
|
$_map: ();
|
|
|
|
@each $key, $value in $map {
|
|
// allow to pass the $key and $value of the map as an function argument
|
|
$_args: ();
|
|
@each $arg in $args {
|
|
$_args: list.append($_args, if(sass($arg == '$key'): $key; else: if(sass($arg == '$value'): $value; else: $arg)));
|
|
}
|
|
|
|
$_map: map.merge(
|
|
$_map,
|
|
(
|
|
$key: meta.call(meta.get-function($func), $_args...),
|
|
)
|
|
);
|
|
}
|
|
|
|
@return $_map;
|
|
}
|
|
// stylelint-enable scss/dollar-variable-pattern
|
|
|
|
// Internal Bootstrap function to turn maps into its negative variant.
|
|
// It prefixes the keys with `n` and makes the value negative.
|
|
@function negativify-map($map) {
|
|
$result: ();
|
|
@each $key, $value in $map {
|
|
@if $key != 0 {
|
|
$result: map.merge($result, ('n' + $key: (-$value)));
|
|
}
|
|
}
|
|
@return $result;
|
|
}
|
|
|
|
// Replace `$search` with `$replace` in `$string`
|
|
// Used on our SVG icon backgrounds for custom forms.
|
|
//
|
|
// @author Kitty Giraudel
|
|
// @param {String} $string - Initial string
|
|
// @param {String} $search - Substring to replace
|
|
// @param {String} $replace ('') - New value
|
|
// @return {String} - Updated string
|
|
@function str-replace($string, $search, $replace: '') {
|
|
$index: string.index($string, $search);
|
|
|
|
@if $index {
|
|
@return string.slice($string, 1, $index - 1) + $replace + str-replace(string.slice($string, $index + string.length($search)), $search, $replace);
|
|
}
|
|
|
|
@return $string;
|
|
}
|
|
|
|
// See https://codepen.io/kevinweber/pen/dXWoRw
|
|
//
|
|
// Requires the use of quotes around data URIs.
|
|
|
|
@function escape-svg($string) {
|
|
@if string.index($string, 'data:image/svg+xml') {
|
|
@each $char, $encoded in $escaped-characters {
|
|
// Do not escape the url brackets
|
|
@if string.index($string, 'url(') == 1 {
|
|
$string: url('#{str-replace(string.slice($string, 6, -3), $char, $encoded)}');
|
|
} @else {
|
|
$string: str-replace($string, $char, $encoded);
|
|
}
|
|
}
|
|
}
|
|
|
|
@return $string;
|
|
}
|
|
|
|
// Color contrast
|
|
// See https://github.com/twbs/bootstrap/pull/30168
|
|
|
|
// A list of pre-calculated numbers of pow(divide((divide($value, 255) + .055), 1.055), 2.4). (from 0 to 255)
|
|
// stylelint-disable-next-line scss/dollar-variable-default, scss/dollar-variable-pattern
|
|
$_luminance-list: 0.0008 0.001 0.0011 0.0013 0.0015 0.0017 0.002 0.0022 0.0025 0.0027 0.003 0.0033 0.0037 0.004 0.0044 0.0048 0.0052 0.0056 0.006 0.0065 0.007 0.0075 0.008 0.0086 0.0091 0.0097 0.0103 0.011 0.0116 0.0123 0.013 0.0137 0.0144 0.0152 0.016 0.0168 0.0176 0.0185 0.0194 0.0203 0.0212 0.0222 0.0232 0.0242
|
|
0.0252 0.0262 0.0273 0.0284 0.0296 0.0307 0.0319 0.0331 0.0343 0.0356 0.0369 0.0382 0.0395 0.0409 0.0423 0.0437 0.0452 0.0467 0.0482 0.0497 0.0513 0.0529 0.0545 0.0561 0.0578 0.0595 0.0612 0.063 0.0648 0.0666 0.0685 0.0704 0.0723 0.0742 0.0762 0.0782 0.0802 0.0823 0.0844 0.0865 0.0887 0.0908 0.0931 0.0953 0.0976
|
|
0.0999 0.1022 0.1046 0.107 0.1095 0.1119 0.1144 0.117 0.1195 0.1221 0.1248 0.1274 0.1301 0.1329 0.1356 0.1384 0.1413 0.1441 0.147 0.15 0.1529 0.1559 0.159 0.162 0.1651 0.1683 0.1714 0.1746 0.1779 0.1812 0.1845 0.1878 0.1912 0.1946 0.1981 0.2016 0.2051 0.2086 0.2122 0.2159 0.2195 0.2232 0.227 0.2307 0.2346 0.2384
|
|
0.2423 0.2462 0.2502 0.2542 0.2582 0.2623 0.2664 0.2705 0.2747 0.2789 0.2831 0.2874 0.2918 0.2961 0.3005 0.305 0.3095 0.314 0.3185 0.3231 0.3278 0.3325 0.3372 0.3419 0.3467 0.3515 0.3564 0.3613 0.3663 0.3712 0.3763 0.3813 0.3864 0.3916 0.3968 0.402 0.4072 0.4125 0.4179 0.4233 0.4287 0.4342 0.4397 0.4452 0.4508 0.4564
|
|
0.4621 0.4678 0.4735 0.4793 0.4851 0.491 0.4969 0.5029 0.5089 0.5149 0.521 0.5271 0.5333 0.5395 0.5457 0.552 0.5583 0.5647 0.5711 0.5776 0.5841 0.5906 0.5972 0.6038 0.6105 0.6172 0.624 0.6308 0.6376 0.6445 0.6514 0.6584 0.6654 0.6724 0.6795 0.6867 0.6939 0.7011 0.7084 0.7157 0.7231 0.7305 0.7379 0.7454 0.7529 0.7605
|
|
0.7682 0.7758 0.7835 0.7913 0.7991 0.807 0.8148 0.8228 0.8308 0.8388 0.8469 0.855 0.8632 0.8714 0.8796 0.8879 0.8963 0.9047 0.9131 0.9216 0.9301 0.9387 0.9473 0.956 0.9647 0.9734 0.9823 0.9911 1;
|
|
|
|
@function color-contrast($background, $color-contrast-dark: $color-contrast-dark, $color-contrast-light: $color-contrast-light, $min-contrast-ratio: $min-contrast-ratio) {
|
|
$foregrounds: $color-contrast-light, $color-contrast-dark, $white, $black;
|
|
$max-ratio: 0;
|
|
$max-ratio-color: null;
|
|
|
|
@each $color in $foregrounds {
|
|
$contrast-ratio: contrast-ratio($background, $color);
|
|
@if $contrast-ratio >= $min-contrast-ratio {
|
|
@return $color;
|
|
} @else if $contrast-ratio > $max-ratio {
|
|
$max-ratio: $contrast-ratio;
|
|
$max-ratio-color: $color;
|
|
}
|
|
}
|
|
|
|
@warn "Found no color leading to #{$min-contrast-ratio}:1 contrast ratio against #{$background}...";
|
|
|
|
@return $max-ratio-color;
|
|
}
|
|
|
|
@function contrast-ratio($background, $foreground: $color-contrast-light) {
|
|
$l1: luminance($background);
|
|
$l2: luminance(opaque($background, $foreground));
|
|
|
|
@return if(sass($l1 > $l2): divide($l1 + 0.05, $l2 + 0.05); else: divide($l2 + 0.05, $l1 + 0.05));
|
|
}
|
|
|
|
// Return WCAG2.2 relative luminance
|
|
// See https://www.w3.org/TR/WCAG/#dfn-relative-luminance
|
|
// See https://www.w3.org/TR/WCAG/#dfn-contrast-ratio
|
|
@function luminance($color) {
|
|
$rgb: (
|
|
'r': math.round(color.channel($color, 'red', $space: rgb)),
|
|
'g': math.round(color.channel($color, 'green', $space: rgb)),
|
|
'b': math.round(color.channel($color, 'blue', $space: rgb)),
|
|
);
|
|
|
|
@each $name, $value in $rgb {
|
|
$value: if(sass(divide($value, 255) < 0.04045): divide(divide($value, 255), 12.92); else: list.nth($_luminance-list, $value + 1));
|
|
$rgb: map.merge(
|
|
$rgb,
|
|
(
|
|
$name: $value,
|
|
)
|
|
);
|
|
}
|
|
|
|
@return (map.get($rgb, 'r') * 0.2126) + (map.get($rgb, 'g') * 0.7152) + (map.get($rgb, 'b') * 0.0722);
|
|
}
|
|
|
|
// Return opaque color
|
|
// opaque(#fff, rgba(0, 0, 0, .5)) => #808080
|
|
@function opaque($background, $foreground) {
|
|
@return color.mix(color.change($foreground, $alpha: 1), $background, color.channel($foreground, 'alpha') * 100%, $method: srgb);
|
|
}
|
|
|
|
// Tint a color: mix a color with white (in oklab, see `mix-color()`)
|
|
@function tint-color($color, $weight) {
|
|
@return mix-color(white, $color, $weight);
|
|
}
|
|
|
|
// Shade a color: mix a color with black (in oklab, see `mix-color()`)
|
|
@function shade-color($color, $weight) {
|
|
@return mix-color(black, $color, $weight);
|
|
}
|
|
|
|
// Shade the color if the weight is positive, else tint it
|
|
@function shift-color($color, $weight) {
|
|
@return if(sass($weight > 0): shade-color($color, $weight); else: tint-color($color, -$weight));
|
|
}
|
|
|
|
// Return valid calc
|
|
@function add($value1, $value2, $return-calc: true) {
|
|
@if $value1 == null {
|
|
@return $value2;
|
|
}
|
|
|
|
@if $value2 == null {
|
|
@return $value1;
|
|
}
|
|
|
|
@if meta.type-of($value1) == number and meta.type-of($value2) == number and math.compatible($value1, $value2) {
|
|
@return $value1 + $value2;
|
|
}
|
|
|
|
@return if(sass($return-calc == true): calc(#{$value1} + #{$value2}); else: $value1 + string.unquote(' + ') + $value2);
|
|
}
|
|
|
|
@function subtract($value1, $value2, $return-calc: true) {
|
|
@if $value1 == null and $value2 == null {
|
|
@return null;
|
|
}
|
|
|
|
@if $value1 == null {
|
|
@return -$value2;
|
|
}
|
|
|
|
@if $value2 == null {
|
|
@return $value1;
|
|
}
|
|
|
|
@if meta.type-of($value1) == number and meta.type-of($value2) == number and math.compatible($value1, $value2) {
|
|
@return $value1 - $value2;
|
|
}
|
|
|
|
@if meta.type-of($value2) != number {
|
|
$value2: string.unquote('(') + $value2 + string.unquote(')');
|
|
}
|
|
|
|
@return if(sass($return-calc == true): calc(#{$value1} - #{$value2}); else: $value1 + string.unquote(' - ') + $value2);
|
|
}
|
|
|
|
@function divide($dividend, $divisor, $precision: 10) {
|
|
$sign: if(sass($dividend > 0 and $divisor > 0 or $dividend < 0 and $divisor < 0): 1; else: -1);
|
|
$dividend: math.abs($dividend);
|
|
$divisor: math.abs($divisor);
|
|
@if $dividend == 0 {
|
|
@return 0;
|
|
}
|
|
@if $divisor == 0 {
|
|
@error "Cannot divide by 0";
|
|
}
|
|
$remainder: $dividend;
|
|
$result: 0;
|
|
$factor: 10;
|
|
@while ($remainder > 0 and $precision >= 0) {
|
|
$quotient: 0;
|
|
@while ($remainder >= $divisor) {
|
|
$remainder: $remainder - $divisor;
|
|
$quotient: $quotient + 1;
|
|
}
|
|
$result: $result * 10 + $quotient;
|
|
$factor: $factor * 0.1;
|
|
$remainder: $remainder * 10;
|
|
$precision: $precision - 1;
|
|
@if ($precision < 0 and $remainder >= $divisor * 5) {
|
|
$result: $result + 1;
|
|
}
|
|
}
|
|
$result: $result * $factor * $sign;
|
|
$dividend-unit: math.unit($dividend);
|
|
$divisor-unit: math.unit($divisor);
|
|
$unit-map: (
|
|
'px': 1px,
|
|
'rem': 1rem,
|
|
'em': 1em,
|
|
'%': 1%,
|
|
);
|
|
@if ($dividend-unit != $divisor-unit and map.has-key($unit-map, $dividend-unit)) {
|
|
$result: $result * map.get($unit-map, $dividend-unit);
|
|
}
|
|
@return $result;
|
|
}
|
|
|
|
// Seeds a component token from a Sass variable that has a `:root` scale
|
|
// counterpart. While the variable still holds `$default`, the token reads the
|
|
// scale (`var(--btn-input-padding-y)`) and follows it at runtime; once a
|
|
// project configured the variable from Sass, that value wins, as it did before
|
|
// the scale existed. `$token` is the bare name of the root custom property.
|
|
@function var-if-default($value, $default, $token) {
|
|
@if $value == $default {
|
|
@return var(#{$token});
|
|
}
|
|
@return $value;
|
|
}
|