mirror of
https://github.com/material-components/material-web.git
synced 2026-03-09 00:09:23 +08:00
250 lines
7.0 KiB
SCSS
250 lines
7.0 KiB
SCSS
//
|
|
// Copyright 2021 Google LLC
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
// go/keep-sorted start
|
|
@use 'sass:list';
|
|
@use 'sass:map';
|
|
@use 'sass:meta';
|
|
// go/keep-sorted end
|
|
// go/keep-sorted start
|
|
@use './string-ext';
|
|
@use './var';
|
|
// go/keep-sorted end
|
|
|
|
/// Transform a user-provided `$theme` map's values into `var()` custom property
|
|
/// values.
|
|
///
|
|
/// Use this function in `theme-styles()` mixins to transform values into
|
|
/// custom property `var()` "slots" that can subsequently be styled via
|
|
/// `theme.emit-theme-vars()` in the `theme()` mixin by the user.
|
|
///
|
|
/// @example - scss
|
|
/// $light-theme: (
|
|
/// label-color: purple
|
|
/// );
|
|
///
|
|
/// @mixin theme-styles($theme) {
|
|
/// $theme: theme.create-theme-vars($theme, button);
|
|
///
|
|
/// .foo {
|
|
/// color: map.get($theme, label-color);
|
|
/// }
|
|
/// }
|
|
///
|
|
/// @example - css
|
|
/// .foo {
|
|
/// color: var(--md-button-label-color, purple);
|
|
/// }
|
|
///
|
|
/// @param {Map} $theme - The theme Map to transform values into custom property
|
|
/// `var()`s.
|
|
/// @param {String} $prefix - Component and variant prefix to prepend for each
|
|
/// token's custom property name.
|
|
/// @return {Map} The provided `$theme` Map whose values are replaced with the
|
|
/// `var()` custom properties.
|
|
@function create-theme-vars($theme, $prefix) {
|
|
@each $key, $value in $theme {
|
|
@if $value != null {
|
|
$token: combine-tokens($prefix, $key);
|
|
|
|
@if meta.type-of($value) == 'map' {
|
|
$value: create-theme-vars($value, $token);
|
|
} @else {
|
|
$value: var.create($token, $value);
|
|
}
|
|
|
|
$theme: map.set($theme, $key, $value);
|
|
}
|
|
}
|
|
|
|
@return $theme;
|
|
}
|
|
|
|
/// Emits CSS declaration values for a theme map that has been transformed with
|
|
/// `create-theme-vars()`.
|
|
///
|
|
/// Use this in `theme()` mixins to set user-provided token customizations.
|
|
///
|
|
/// @example - scss
|
|
/// $light-theme: (
|
|
/// label-color: purple
|
|
/// );
|
|
///
|
|
/// @mixin theme($theme) {
|
|
/// $theme: theme.create-theme-vars($theme, button);
|
|
///
|
|
/// @include theme.emit-theme-vars($theme);
|
|
/// }
|
|
///
|
|
/// .my-button {
|
|
/// @include theme((label-color: teal));
|
|
/// }
|
|
///
|
|
/// @example - css
|
|
/// .my-button {
|
|
/// --md-button-label-color: teal;
|
|
/// }
|
|
///
|
|
/// @param {Map} $theme - The theme Map to emit custom property declarations
|
|
/// for.
|
|
@mixin emit-theme-vars($theme) {
|
|
@each $token, $value in $theme {
|
|
@if $value {
|
|
@if meta.type-of($value) == 'map' {
|
|
@include emit-theme-vars($value);
|
|
} @else if not var.is-var($value) {
|
|
@error '"#{$token}": #{$value} is not a var(). Call create-theme-vars($theme) before emit-theme-vars($theme).';
|
|
} @else {
|
|
#{var.name($value)}: #{var.fallback($value)};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Combines one or more token parts into a token.
|
|
///
|
|
/// @example - scss
|
|
/// $key: combine(body, font-size);
|
|
/// // body-font-size
|
|
///
|
|
/// @param {String...} $parts - Arbitrary number of string token parts to
|
|
/// combine.
|
|
/// @return {String} A combined token string.
|
|
@function combine-tokens($parts...) {
|
|
// Allow extra keywords to be passed to other functions without impacting this
|
|
// function, which does not expect any keywords.
|
|
$unused: meta.keywords($parts);
|
|
$token: '';
|
|
@each $part in $parts {
|
|
@if $part {
|
|
@if $token == '' {
|
|
$token: $part;
|
|
} @else {
|
|
$token: #{$token}-#{$part};
|
|
}
|
|
}
|
|
}
|
|
|
|
@return $token;
|
|
}
|
|
|
|
/// Validates a theme's tokens and values and throws an error if incorrect
|
|
/// tokens are present or invalid values are provided.
|
|
///
|
|
/// Use this in `theme()` mixins to validate user-provided `$theme` maps before
|
|
/// providing the value to `theme.create-theme-vars()`.
|
|
///
|
|
/// @example - scss
|
|
/// @mixin theme($theme) {
|
|
/// $theme: theme.validate-theme($light-theme, $theme);
|
|
/// $theme: theme.create-theme-vars($theme, checkbox);
|
|
/// }
|
|
///
|
|
/// @throw If any tokens or values are invalid.
|
|
/// @param {Map} $reference-theme - A reference theme Map whose token keys will
|
|
/// be used to validate the user-provided theme.
|
|
/// @param {Map} $theme - User-provided theme Map to validate.
|
|
/// @return {Map} The validated user-provided theme Map.
|
|
@function validate-theme($reference-theme, $theme) {
|
|
$theme: _validate-theme-tokens(
|
|
map.keys($reference-theme),
|
|
$theme,
|
|
$require-all: false
|
|
);
|
|
|
|
@return $theme;
|
|
}
|
|
|
|
/// Validates a theme's tokens and throws an error if incorrect tokens are
|
|
/// present or any tokens are missing.
|
|
///
|
|
/// Use this in internal `theme-styles()` mixins to validate library-provided
|
|
/// `$theme` maps and ensure that all tokens are correct and present.
|
|
///
|
|
/// @example - scss
|
|
/// @mixin theme-styles($theme) {
|
|
/// $theme: theme.validate-theme-styles($light-theme, $theme);
|
|
/// $theme: theme.create-theme-vars($theme, checkbox);
|
|
/// }
|
|
///
|
|
/// @throw If any tokens are invalid or missing.
|
|
/// @param {Map|List} $reference-theme - A complete reference theme Map (or List
|
|
/// of tokens) to validate the theme.
|
|
/// @param {Map} $theme - The theme Map to validate.
|
|
/// @return {Map} The validated theme Map.
|
|
@function validate-theme-styles($reference-theme, $theme, $require-all: true) {
|
|
$valid-tokens: $reference-theme;
|
|
@if meta.type-of($reference-theme) == 'map' {
|
|
$valid-tokens: map.keys($reference-theme);
|
|
}
|
|
|
|
@return _validate-theme-tokens(
|
|
$valid-tokens,
|
|
$theme,
|
|
$require-all: $require-all
|
|
);
|
|
}
|
|
|
|
/// Validates a theme's tokens and throws an error if incorrect tokens are
|
|
/// present or optionally missing if `$require-all` is true.
|
|
///
|
|
/// @throw If any tokens are invalid or optionally missing.
|
|
/// @param {List} $valid-tokens - A List of token keys to validate the theme.
|
|
/// @param {Map} $theme - The theme Map to validate.
|
|
/// @param {Bool} $require-all [false] - If true, throw an error if the theme
|
|
/// is missing tokens from the list.
|
|
/// @return {Map} The validated theme Map.
|
|
@function _validate-theme-tokens($valid-tokens, $theme, $require-all: false) {
|
|
$missing-tokens: ();
|
|
$unsupported-tokens: ();
|
|
|
|
@each $token, $value in $theme {
|
|
@if list.index($valid-tokens, $token) == null {
|
|
$unsupported-tokens: list.append(
|
|
$unsupported-tokens,
|
|
$token,
|
|
$separator: comma
|
|
);
|
|
}
|
|
}
|
|
|
|
@if $require-all {
|
|
// TODO(b/203778922): Remove when type composite tokens are removed
|
|
$ignore-suffix: (
|
|
// Ignore composite font tokens
|
|
'-type'
|
|
);
|
|
|
|
@each $token in $valid-tokens {
|
|
$missing: map.get($theme, $token) == null;
|
|
@if $missing {
|
|
@each $suffix in $ignore-suffix {
|
|
@if string-ext.has-suffix($token, $suffix) {
|
|
$missing: false;
|
|
}
|
|
}
|
|
}
|
|
|
|
@if $missing {
|
|
$missing-tokens: list.append(
|
|
$missing-tokens,
|
|
$token,
|
|
$separator: comma
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
@if list.length($unsupported-tokens) > 0 {
|
|
@error 'The following tokens are invalid: #{$unsupported-tokens}.';
|
|
}
|
|
|
|
@if list.length($missing-tokens) > 0 {
|
|
@error 'The following required tokens are missing: #{$missing-tokens}.';
|
|
}
|
|
|
|
@return $theme;
|
|
}
|