|
| 1 | +@use 'sass:color'; |
| 2 | +@use 'sass:list'; |
| 3 | +@use 'sass:map'; |
| 4 | +@use 'sass:meta'; |
| 5 | +@use 'sass:math'; |
| 6 | +@use '../m2/palette' as m2-palette; |
| 7 | +@use '../m2/theming' as m2-theming; |
| 8 | +@use '../m2/typography' as m2-typography; |
| 9 | +@use './m3/definitions' as m3-token-definitions; |
| 10 | + |
| 11 | +// Indicates whether we're building internally. Used for backwards compatibility. |
| 12 | +$private-is-internal-build: false; |
| 13 | + |
| 14 | +$_placeholder-color-palette: m2-theming.define-palette(m2-palette.$red-palette); |
| 15 | + |
| 16 | +// Placeholder color config that can be passed to token getter functions when generating token |
| 17 | +// slots. |
| 18 | +$placeholder-color-config: ( |
| 19 | + primary: $_placeholder-color-palette, |
| 20 | + accent: $_placeholder-color-palette, |
| 21 | + warn: $_placeholder-color-palette, |
| 22 | + is-dark: false, |
| 23 | + foreground: m2-palette.$light-theme-foreground-palette, |
| 24 | + background: m2-palette.$light-theme-background-palette, |
| 25 | +); |
| 26 | + |
| 27 | +$_placeholder-typography-level-config: m2-typography.typography-config-level-from-mdc(body1); |
| 28 | + |
| 29 | +// Placeholder typography config that can be passed to token getter functions when generating token |
| 30 | +// slots. |
| 31 | +$placeholder-typography-config: ( |
| 32 | + font-family: 'Roboto, sans-serif', |
| 33 | + headline-1: $_placeholder-typography-level-config, |
| 34 | + headline-2: $_placeholder-typography-level-config, |
| 35 | + headline-3: $_placeholder-typography-level-config, |
| 36 | + headline-4: $_placeholder-typography-level-config, |
| 37 | + headline-5: $_placeholder-typography-level-config, |
| 38 | + headline-6: $_placeholder-typography-level-config, |
| 39 | + subtitle-1: $_placeholder-typography-level-config, |
| 40 | + subtitle-2: $_placeholder-typography-level-config, |
| 41 | + body-1: $_placeholder-typography-level-config, |
| 42 | + body-2: $_placeholder-typography-level-config, |
| 43 | + caption: $_placeholder-typography-level-config, |
| 44 | + button: $_placeholder-typography-level-config, |
| 45 | + overline: $_placeholder-typography-level-config, |
| 46 | + subheading-1: $_placeholder-typography-level-config, |
| 47 | + title: $_placeholder-typography-level-config, |
| 48 | +); |
| 49 | + |
| 50 | +// Placeholder density config that can be passed to token getter functions when generating token |
| 51 | +// slots. |
| 52 | +$placeholder-density-config: 0; |
| 53 | + |
| 54 | +$_tokens: null; |
| 55 | +$_component-prefix: null; |
| 56 | +$_system-fallbacks: null; |
| 57 | + |
| 58 | +/// Gets all the MDC token values for a specific component. This function serves as single |
| 59 | +/// point at which we directly reference a specific version of the MDC tokens. |
| 60 | +/// @param {String} $component Name of the component for which to get the tokens |
| 61 | +/// @param {Map} $systems The MDC system tokens |
| 62 | +/// @param {Boolean} $exclude-hardcoded Whether to exclude hardcoded token values |
| 63 | +/// @return {List} Map of token names to values |
| 64 | +@function get-mdc-tokens($component, $systems, $exclude-hardcoded) { |
| 65 | + $full-name: 'md-comp-' + $component + '-values'; |
| 66 | + $fn: meta.get-function($name: $full-name, $module: 'm3-token-definitions'); |
| 67 | + @return meta.call($fn, $systems, $exclude-hardcoded); |
| 68 | +} |
| 69 | + |
| 70 | +/// Gets the MDC tokens for the given prefix, M3 token values, and supported token slots. |
| 71 | +/// @param {List} $prefix The token prefix for the given tokens. |
| 72 | +/// @param {Map|(Map, Map)} $values A map of M3 token values for the given prefix. |
| 73 | +/// This param may also be a tuple of maps, the first one representing the default M3 token values, |
| 74 | +// and the second containing overrides for different color variants. |
| 75 | +// Single map example: |
| 76 | +// (token1: green, token2: 2px) |
| 77 | +// Tuple example: |
| 78 | +// ( |
| 79 | +// (token1: green, token2: 2px), |
| 80 | +// ( |
| 81 | +// secondary: (token1: blue), |
| 82 | +// error: (token1: red), |
| 83 | +// ) |
| 84 | +// ) |
| 85 | +/// @param {Map} $slots A map of token slots, with null value indicating the token is not supported. |
| 86 | +/// @param {String|null} $variant The name of the variant the token values are for. |
| 87 | +/// @return {Map} A map of fully qualified token names to values, for only the supported tokens. |
| 88 | +@function namespace-tokens($prefix, $values, $slots, $variant: null) { |
| 89 | + $result: (); |
| 90 | + @if $variant == null and meta.type-of($values) == 'list' and list.length($values == 2) { |
| 91 | + $variants: list.nth($values, 2); |
| 92 | + $values: list.nth($values, 1); |
| 93 | + @each $variant, $overrides in $variants { |
| 94 | + $result: map.merge($result, namespace-tokens($prefix, $overrides, $slots, $variant)); |
| 95 | + } |
| 96 | + } |
| 97 | + $used-token-names: map.keys(_filter-nulls(map.get($slots, $prefix))); |
| 98 | + $used-m3-tokens: _pick(_filter-nulls($values), $used-token-names); |
| 99 | + $prefix: if($variant == null, $prefix, list.append($prefix, $variant)); |
| 100 | + @return map.merge($result, ($prefix: $used-m3-tokens)); |
| 101 | +} |
| 102 | + |
| 103 | +/// Hardcode the given value, or null if hardcoded values are excluded. |
| 104 | +@function hardcode($value, $exclude-hardcoded) { |
| 105 | + @return if($exclude-hardcoded, null, $value); |
| 106 | +} |
| 107 | + |
| 108 | +/// Sets all of the standard typography tokens for the given token base name to the given typography |
| 109 | +/// level. |
| 110 | +/// @param {Map} $systems The MDC system tokens |
| 111 | +/// @param {String} $base-name The token base name to get the typography tokens for |
| 112 | +/// @param {String} $typography-level The typography level to base the token values on |
| 113 | +/// @return {Map} A map containing the typography tokens for the given base token name |
| 114 | +@function generate-typography-tokens($systems, $base-name, $typography-level) { |
| 115 | + $result: (); |
| 116 | + @each $prop in (font, line-height, size, tracking, weight) { |
| 117 | + $result: map.set($result, #{$base-name}-#{$prop}, |
| 118 | + map.get($systems, md-sys-typescale, #{$typography-level}-#{$prop})); |
| 119 | + } |
| 120 | + @return $result; |
| 121 | +} |
| 122 | + |
| 123 | +/// Maps the values in a map to new values using the given mapping function |
| 124 | +/// @param {Map} $map The maps whose values will be mapped to new values. |
| 125 | +/// @param {Function} $fn The value mapping function. |
| 126 | +/// @param {Map} A new map with its values updated using the mapping function. |
| 127 | +@function map-values($map, $fn) { |
| 128 | + $result: (); |
| 129 | + @each $key, $value in $map { |
| 130 | + $result: map.set($result, $key, meta.call($fn, $value)); |
| 131 | + } |
| 132 | + @return $result; |
| 133 | +} |
| 134 | + |
| 135 | +/// Renames the keys in a map |
| 136 | +/// @param {Map} $map The map whose keys should be renamed |
| 137 | +/// @param {Map} $rename-keys A map of original key to renamed key to apply to $map |
| 138 | +/// @return {Map} The result of applying the given key renames to the given map. |
| 139 | +@function rename-map-keys($map, $rename-keys) { |
| 140 | + $result: $map; |
| 141 | + @each $old-key-name, $new-key-name in $rename-keys { |
| 142 | + @if map.has-key($map, $old-key-name) { |
| 143 | + $result: map.set($result, $new-key-name, map.get($map, $old-key-name)); |
| 144 | + } |
| 145 | + } |
| 146 | + @return $result; |
| 147 | +} |
| 148 | + |
| 149 | +/// At the time of writing, some color tokens (e.g. disabled state) are defined as a solid color |
| 150 | +/// token and a separate opacity token. This function applies the opacity to the color and drops the |
| 151 | +/// opacity key from the map. Can be removed once b/213331407 is resolved. |
| 152 | +/// @param {Map} $tokens The map of tokens currently being generated |
| 153 | +/// @param {Map} $all-tokens A map of all tokens, including hardcoded values |
| 154 | +/// @param {List} $pairs Pairs of color token names and their opacities. Should be in the shape of |
| 155 | +/// `((color: 'color-key', opacity: 'opacity-key'))`. |
| 156 | +/// @return {Map} The initial tokens with the combined color values. |
| 157 | +@function combine-color-tokens($tokens, $opacity-lookup, $pairs) { |
| 158 | + $result: $tokens; |
| 159 | + |
| 160 | + @each $pair in $pairs { |
| 161 | + $color-key: map.get($pair, color); |
| 162 | + $opacity-key: map.get($pair, opacity); |
| 163 | + $color: map.get($tokens, $color-key); |
| 164 | + $opacity: map.get($opacity-lookup, $opacity-key); |
| 165 | + |
| 166 | + @if(meta.type-of($color) == 'color') { |
| 167 | + $result: map.remove($result, $opacity-key); |
| 168 | + $result: map.set($result, $color-key, rgba($color, $opacity)); |
| 169 | + } |
| 170 | + @else if($color != null) { |
| 171 | + $result: map.remove($result, $opacity-key); |
| 172 | + $combined-color: #{color-mix(in srgb, #{$color} #{($opacity * 100) + '%'}, transparent)}; |
| 173 | + $result: map.set($result, $color-key, $combined-color); |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + @return $result; |
| 178 | +} |
| 179 | + |
| 180 | +/// Inherited function from MDC that computes which contrast tone to use on top of a color. |
| 181 | +/// This is used only in a narrow set of use cases when generating M2 button tokens to maintain |
| 182 | +/// backwards compatibility. |
| 183 | +/// @param {Color} $value Color for which we're calculating the contrast tone. |
| 184 | +/// @param {Boolean} $is-dark Whether the current theme is dark. |
| 185 | +/// @return {Map} Either `dark` or `light`. |
| 186 | +@function contrast-tone($value, $is-dark) { |
| 187 | + @if ($value == 'dark') { |
| 188 | + @return 'light'; |
| 189 | + } |
| 190 | + |
| 191 | + @if ($value == 'light') { |
| 192 | + @return 'dark'; |
| 193 | + } |
| 194 | + |
| 195 | + // Fallback if the app is using a non-color palette (e.g. CSS variable based). |
| 196 | + @if (meta.type-of($value) != 'color') { |
| 197 | + @return if($is-dark, 'light', 'dark'); |
| 198 | + } |
| 199 | + |
| 200 | + $minimum-contrast: 3.1; |
| 201 | + $light-contrast: _contrast($value, #fff); |
| 202 | + $dark-contrast: _contrast($value, rgba(0, 0, 0, 0.87)); |
| 203 | + |
| 204 | + @if ($light-contrast < $minimum-contrast) and ($dark-contrast > $light-contrast) { |
| 205 | + @return 'dark'; |
| 206 | + } |
| 207 | + |
| 208 | + @return 'light'; |
| 209 | +} |
| 210 | + |
| 211 | +@function _linear-channel-value($channel-value) { |
| 212 | + $normalized-channel-value: math.div($channel-value, 255); |
| 213 | + |
| 214 | + @if ($normalized-channel-value < 0.03928) { |
| 215 | + @return math.div($normalized-channel-value, 12.92); |
| 216 | + } |
| 217 | + |
| 218 | + @return math.pow(math.div($normalized-channel-value + 0.055, 1.055), 2.4); |
| 219 | +} |
| 220 | + |
| 221 | +// Calculate the luminance for a color. |
| 222 | +// See https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests |
| 223 | +@function _luminance($color) { |
| 224 | + $red: _linear-channel-value(color.red($color)); |
| 225 | + $green: _linear-channel-value(color.green($color)); |
| 226 | + $blue: _linear-channel-value(color.blue($color)); |
| 227 | + |
| 228 | + @return 0.2126 * $red + 0.7152 * $green + 0.0722 * $blue; |
| 229 | +} |
| 230 | + |
| 231 | +// Calculate the contrast ratio between two colors. |
| 232 | +// See https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests |
| 233 | +@function _contrast($back, $front) { |
| 234 | + $back-lum: _luminance($back) + 0.05; |
| 235 | + $fore-lum: _luminance($front) + 0.05; |
| 236 | + |
| 237 | + @return math.div(math.max($back-lum, $fore-lum), math.min($back-lum, $fore-lum)); |
| 238 | +} |
| 239 | + |
| 240 | +/// Picks a submap containing only the given keys out the given map. |
| 241 | +/// @param {Map} $map The map to pick from. |
| 242 | +/// @param {List} $keys The map keys to pick. |
| 243 | +/// @return {Map} A submap containing only the given keys. |
| 244 | +@function _pick($map, $keys) { |
| 245 | + $result: (); |
| 246 | + @each $key in $keys { |
| 247 | + @if map.has-key($map, $key) { |
| 248 | + $result: map.set($result, $key, map.get($map, $key)); |
| 249 | + } |
| 250 | + } |
| 251 | + @return $result; |
| 252 | +} |
| 253 | + |
| 254 | + |
| 255 | +/// Filters keys with a null value out of the map. |
| 256 | +/// @param {Map} $map The map to filter. |
| 257 | +/// @return {Map} The given map with all of the null keys filtered out. |
| 258 | +@function _filter-nulls($map) { |
| 259 | + $result: (); |
| 260 | + @each $key, $val in $map { |
| 261 | + @if $val != null { |
| 262 | + $result: map.set($result, $key, $val); |
| 263 | + } |
| 264 | + } |
| 265 | + @return $result; |
| 266 | +} |
0 commit comments