Elizabeth Mitchell 6c2aef6901 chore(all): add keep-sorted comments to Sass imports
PiperOrigin-RevId: 509583504
2023-02-14 11:10:23 -08:00

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;
}