diff --git a/src/material/core/tokens/_token-utils.scss b/src/material/core/tokens/_token-utils.scss index b9fc21118b48..61ee283c4782 100644 --- a/src/material/core/tokens/_token-utils.scss +++ b/src/material/core/tokens/_token-utils.scss @@ -93,6 +93,19 @@ $_component-prefix: null; } } +// Returns the name of a token including the current prefix. Intended to be used in calculations +// involving tokens. `create-token-slot` should be used when outputting tokens. +@function get-token-variable($token) { + @if $_component-prefix == null or $_tokens == null { + @error '`get-token-variable` must be used within `use-tokens`'; + } + @if not map.has-key($_tokens, $token) { + @error 'Token #{$token} does not exist. Configured tokens are: #{map.keys($_tokens)}'; + } + + @return mdc-custom-properties.create-varname('#{$_component-prefix}-#{$token}'); +} + @mixin create-token-values($prefix, $tokens) { @include _configure-token-prefix($prefix...) { @include mdc-keys.declare-custom-properties($tokens, $_component-prefix); diff --git a/src/material/core/tokens/m2/mat/_stepper.scss b/src/material/core/tokens/m2/mat/_stepper.scss new file mode 100644 index 000000000000..4ec35f2eb566 --- /dev/null +++ b/src/material/core/tokens/m2/mat/_stepper.scss @@ -0,0 +1,118 @@ +@use 'sass:map'; +@use '../../token-utils'; +@use '../../../theming/theming'; +@use '../../../typography/typography-utils'; +@use '../../../style/sass-utils'; + +// The prefix used to generate the fully qualified name for tokens in this file. +$prefix: (mat, stepper); + +// Tokens that can't be configured through Angular Material's current theming API, +// but may be in a future version of the theming API. +@function get-unthemable-tokens() { + @return (); +} + +// Tokens that can be configured through Angular Material's color theming API. +@function get-color-tokens($config) { + $foreground: map.get($config, foreground); + $background: map.get($config, background); + $primary: map.get($config, primary); + $warn: map.get($config, warn); + + @return map.merge(private-get-color-palette-color-tokens($primary), ( + // Background for stepper container. + container-color: theming.get-color-from-palette($background, card), + // Color of the line indicator connecting the steps. + line-color: theming.get-color-from-palette($foreground, divider), + // Background color of the header while hovered. + header-hover-state-layer-color: theming.get-color-from-palette($background, hover), + // Background color of the header while focused. + header-focus-state-layer-color: theming.get-color-from-palette($background, hover), + // Color of the text inside the step header. + header-label-text-color: theming.get-color-from-palette($foreground, secondary-text), + // Color for the "optional" label in the step header. + header-optional-label-text-color: theming.get-color-from-palette($foreground, secondary-text), + // Color of the header text when a step is selected. + header-selected-state-label-text-color: theming.get-color-from-palette($foreground, text), + // Color of the header text when a step is in an error state. + header-error-state-label-text-color: theming.get-color-from-palette($warn, text), + // Background color of the header icon. + header-icon-background-color: theming.get-color-from-palette($foreground, secondary-text), + // Foreground color of the header icon in the error state. + header-error-state-icon-foreground-color: theming.get-color-from-palette($warn, text), + // Background color of the header icon in the error state. + header-error-state-icon-background-color: transparent, + )); +} + +// Tokens that can be configured through Angular Material's typography theming API. +@function get-typography-tokens($config) { + @return ( + // Font family of the entire stepper. + container-text-font: typography-utils.font-family($config), + // Font family of the text inside the step header. + header-label-text-font: typography-utils.font-family($config, body-1) or + typography-utils.font-family($config), + // Size of the text inside the step header. + header-label-text-size: typography-utils.font-size($config, body-1), + // Weight of the text inside the step header. + header-label-text-weight: typography-utils.font-weight($config, body-1), + // Color of the header text when a step is in an error state. + header-error-state-label-text-size: typography-utils.font-size($config, body-2), + // Size of the header text in the selected state. + header-selected-state-label-text-size: typography-utils.font-size($config, body-2), + // Weight of the header text in the selected state. + header-selected-state-label-text-weight: typography-utils.font-weight($config, body-2), + ); +} + +// Tokens that can be configured through Angular Material's density theming API. +@function get-density-tokens($config) { + $scale: theming.clamp-density($config, -4); + $height-config: ( + 0: 72px, + -1: 68px, + -2: 64px, + -3: 60px, + -4: 42px, + ); + + @return ( + header-height: map.get($height-config, $scale), + ); +} + +// Generates the tokens used to theme the stepper based on a palette. +@function private-get-color-palette-color-tokens($palette) { + $active-state-foreground: theming.get-color-from-palette($palette, default-contrast); + $active-state-background: theming.get-color-from-palette($palette); + + @return ( + // Foreground color of the header icon. + header-icon-foreground-color: theming.get-color-from-palette($palette, default-contrast), + // Background color of the header icon in the selected state. + header-selected-state-icon-background-color: $active-state-background, + // Foreground color of the header icon in the selected state. + header-selected-state-icon-foreground-color: $active-state-foreground, + // Background color of the header icon in the selected state. + header-done-state-icon-background-color: $active-state-background, + // Foreground color of the header icon in the selected state. + header-done-state-icon-foreground-color: $active-state-foreground, + // Background color of the header icon in the editing state. + header-edit-state-icon-background-color: $active-state-background, + // Foreground color of the header icon in the editing state. + header-edit-state-icon-foreground-color: $active-state-foreground, + ); +} + +// Combines the tokens generated by the above functions into a single map with placeholder values. +// This is used to create token slots. +@function get-token-slots() { + @return sass-utils.deep-merge-all( + get-unthemable-tokens(), + get-color-tokens(token-utils.$placeholder-color-config), + get-typography-tokens(token-utils.$placeholder-typography-config), + get-density-tokens(token-utils.$placeholder-density-config) + ); +} diff --git a/src/material/stepper/_stepper-theme.scss b/src/material/stepper/_stepper-theme.scss index 6f6fb1c502e3..6e490c5d469c 100644 --- a/src/material/stepper/_stepper-theme.scss +++ b/src/material/stepper/_stepper-theme.scss @@ -2,142 +2,38 @@ @use 'sass:math'; @use '../core/theming/theming'; @use '../core/typography/typography'; -@use '../core/typography/typography-utils'; @use '../core/density/private/compatibility'; +@use '../core/style/sass-utils'; +@use '../core/tokens/token-utils'; +@use '../core/tokens/m2/mat/stepper' as tokens-mat-stepper; @use './stepper-variables'; @mixin color($config-or-theme) { $config: theming.get-color-config($config-or-theme); - $foreground: map.get($config, foreground); - $background: map.get($config, background); - $primary: map.get($config, primary); - $accent: map.get($config, accent); - $warn: map.get($config, warn); - .mat-step-header { - &.cdk-keyboard-focused, - &.cdk-program-focused, - &:hover:not([aria-disabled]), - &:hover[aria-disabled='false'] { - background-color: theming.get-color-from-palette($background, hover); - } - - &:hover[aria-disabled='true'] { - cursor: default; - } + @include sass-utils.current-selector-or-root() { + @include token-utils.create-token-values(tokens-mat-stepper.$prefix, + tokens-mat-stepper.get-color-tokens($config)); - // On touch devices the :hover state will linger on the element after a tap. - // Reset it via `@media` after the declaration, because the media query isn't - // supported by all browsers yet. - @media (hover: none) { - &:hover { - background: none; - } + .mat-step-header.mat-accent { + @include token-utils.create-token-values(tokens-mat-stepper.$prefix, + tokens-mat-stepper.private-get-color-palette-color-tokens(map.get($config, accent))); } - .mat-step-label, - .mat-step-optional { - // TODO(josephperrott): Update to using a corrected disabled-text contrast - // instead of secondary-text. - color: theming.get-color-from-palette($foreground, secondary-text); + .mat-step-header.mat-warn { + @include token-utils.create-token-values(tokens-mat-stepper.$prefix, + tokens-mat-stepper.private-get-color-palette-color-tokens(map.get($config, warn))); } - - .mat-step-icon { - // TODO(josephperrott): Update to using a corrected disabled-text contrast - // instead of secondary-text. - background-color: theming.get-color-from-palette($foreground, secondary-text); - color: theming.get-color-from-palette($primary, default-contrast); - } - - .mat-step-icon-selected, - .mat-step-icon-state-done, - .mat-step-icon-state-edit { - background-color: theming.get-color-from-palette($primary); - color: theming.get-color-from-palette($primary, default-contrast); - } - - &.mat-accent { - .mat-step-icon { - color: theming.get-color-from-palette($accent, default-contrast); - } - - .mat-step-icon-selected, - .mat-step-icon-state-done, - .mat-step-icon-state-edit { - background-color: theming.get-color-from-palette($accent); - color: theming.get-color-from-palette($accent, default-contrast); - } - } - - &.mat-warn { - .mat-step-icon { - color: theming.get-color-from-palette($warn, default-contrast); - } - - .mat-step-icon-selected, - .mat-step-icon-state-done, - .mat-step-icon-state-edit { - background-color: theming.get-color-from-palette($warn); - color: theming.get-color-from-palette($warn, default-contrast); - } - } - - .mat-step-icon-state-error { - background-color: transparent; - color: theming.get-color-from-palette($warn, text); - } - - .mat-step-label.mat-step-label-active { - color: theming.get-color-from-palette($foreground, text); - } - - .mat-step-label.mat-step-label-error { - color: theming.get-color-from-palette($warn, text); - } - } - - .mat-stepper-horizontal, .mat-stepper-vertical { - background-color: theming.get-color-from-palette($background, card); - } - - .mat-stepper-vertical-line::before { - border-left-color: theming.get-color-from-palette($foreground, divider); - } - - .mat-horizontal-stepper-header::before, - .mat-horizontal-stepper-header::after, - .mat-stepper-horizontal-line { - border-top-color: theming.get-color-from-palette($foreground, divider); } } @mixin typography($config-or-theme) { $config: typography.private-typography-to-2014-config( theming.get-typography-config($config-or-theme)); - .mat-stepper-vertical, .mat-stepper-horizontal { - font-family: typography-utils.font-family($config); - } - - .mat-step-label { - font: { - size: typography-utils.font-size($config, body-1); - weight: typography-utils.font-weight($config, body-1); - }; - } - - .mat-step-sub-label-error { - font-weight: normal; - } - - .mat-step-label-error { - font-size: typography-utils.font-size($config, body-2); - } - .mat-step-label-selected { - font: { - size: typography-utils.font-size($config, body-2); - weight: typography-utils.font-weight($config, body-2); - }; + @include sass-utils.current-selector-or-root() { + @include token-utils.create-token-values(tokens-mat-stepper.$prefix, + tokens-mat-stepper.get-typography-tokens($config)); } } @@ -147,34 +43,9 @@ $density-scale, height); $vertical-padding: math.div($height - stepper-variables.$label-header-height, 2); - @include compatibility.private-density-legacy-compatibility() { - .mat-horizontal-stepper-header { - height: $height; - } - - .mat-stepper-label-position-bottom .mat-horizontal-stepper-header, - .mat-vertical-stepper-header { - padding: $vertical-padding stepper-variables.$side-gap; - } - - // Ensures that the vertical lines for the step content exceed into the step - // headers with a given distance (`$mat-stepper-line-gap`) to the step icon. - .mat-stepper-vertical-line::before { - top: stepper-variables.$line-gap - $vertical-padding; - bottom: stepper-variables.$line-gap - $vertical-padding; - } - - // Ensures that the horizontal lines for the step header are centered vertically. - .mat-stepper-label-position-bottom .mat-horizontal-stepper-header { - &::after, &::before { - top: $vertical-padding + math.div(stepper-variables.$label-header-height, 2); - } - } - - // Ensures that the horizontal line for the step content is aligned centered vertically. - .mat-stepper-label-position-bottom .mat-stepper-horizontal-line { - top: $vertical-padding + math.div(stepper-variables.$label-header-height, 2); - } + @include sass-utils.current-selector-or-root() { + @include token-utils.create-token-values(tokens-mat-stepper.$prefix, + tokens-mat-stepper.get-density-tokens($density-scale)); } } diff --git a/src/material/stepper/step-header.scss b/src/material/stepper/step-header.scss index 8447fc3f2f08..746be3dbf50a 100644 --- a/src/material/stepper/step-header.scss +++ b/src/material/stepper/step-header.scss @@ -1,5 +1,6 @@ @use '@angular/cdk'; - +@use '../core/tokens/m2/mat/stepper' as tokens-mat-stepper; +@use '../core/tokens/token-utils'; @use '../core/style/layout-common'; @use './stepper-variables'; @@ -17,6 +18,33 @@ content: ''; } + &:hover[aria-disabled='true'] { + cursor: default; + } + + @include token-utils.use-tokens( + tokens-mat-stepper.$prefix, tokens-mat-stepper.get-token-slots()) { + + &:hover:not([aria-disabled]), + &:hover[aria-disabled='false'] { + @include token-utils.create-token-slot(background-color, header-hover-state-layer-color); + } + + &.cdk-keyboard-focused, + &.cdk-program-focused { + @include token-utils.create-token-slot(background-color, header-focus-state-layer-color); + } + } + + // On touch devices the :hover state will linger on the element after a tap. + // Reset it via `@media` after the declaration, because the media query isn't + // supported by all browsers yet. + @media (hover: none) { + &:hover { + background: none; + } + } + @include cdk.high-contrast(active, off) { outline: solid 1px; @@ -39,9 +67,18 @@ } } -.mat-step-optional, +.mat-step-optional { + font-size: stepper-variables.$step-sub-label-font-size; + + @include token-utils.use-tokens( + tokens-mat-stepper.$prefix, tokens-mat-stepper.get-token-slots()) { + @include token-utils.create-token-slot(color, header-optional-label-text-color); + } +} + .mat-step-sub-label-error { font-size: stepper-variables.$step-sub-label-font-size; + font-weight: normal; } .mat-step-icon { @@ -50,6 +87,12 @@ width: stepper-variables.$label-header-height; flex-shrink: 0; position: relative; + + @include token-utils.use-tokens( + tokens-mat-stepper.$prefix, tokens-mat-stepper.get-token-slots()) { + @include token-utils.create-token-slot(color, header-icon-foreground-color); + @include token-utils.create-token-slot(background-color, header-icon-background-color); + } } .mat-step-icon-content { @@ -70,10 +113,20 @@ width: stepper-variables.$step-header-icon-size; } -.mat-step-icon-state-error .mat-icon { - font-size: stepper-variables.$step-header-icon-size + 8; - height: stepper-variables.$step-header-icon-size + 8; - width: stepper-variables.$step-header-icon-size + 8; +.mat-step-icon-state-error { + @include token-utils.use-tokens( + tokens-mat-stepper.$prefix, tokens-mat-stepper.get-token-slots()) { + @include token-utils.create-token-slot(background-color, + header-error-state-icon-background-color); + @include token-utils.create-token-slot(color, + header-error-state-icon-foreground-color); + } + + .mat-icon { + font-size: stepper-variables.$step-header-icon-size + 8; + height: stepper-variables.$step-header-icon-size + 8; + width: stepper-variables.$step-header-icon-size + 8; + } } .mat-step-label { @@ -83,6 +136,28 @@ text-overflow: ellipsis; min-width: stepper-variables.$label-min-width; vertical-align: middle; + + @include token-utils.use-tokens( + tokens-mat-stepper.$prefix, tokens-mat-stepper.get-token-slots()) { + @include token-utils.create-token-slot(font-family, header-label-text-font); + @include token-utils.create-token-slot(font-size, header-label-text-size); + @include token-utils.create-token-slot(font-weight, header-label-text-weight); + @include token-utils.create-token-slot(color, header-label-text-color); + + &.mat-step-label-active { + @include token-utils.create-token-slot(color, header-selected-state-label-text-color); + } + + &.mat-step-label-error { + @include token-utils.create-token-slot(color, header-error-state-label-text-color); + @include token-utils.create-token-slot(font-size, header-error-state-label-text-size); + } + + &.mat-step-label-selected { + @include token-utils.create-token-slot(font-size, header-selected-state-label-text-size); + @include token-utils.create-token-slot(font-weight, header-selected-state-label-text-weight); + } + } } .mat-step-text-label { @@ -96,3 +171,33 @@ @include layout-common.fill; pointer-events: none; } + +.mat-step-icon-selected { + @include token-utils.use-tokens( + tokens-mat-stepper.$prefix, tokens-mat-stepper.get-token-slots()) { + @include token-utils.create-token-slot(background-color, + header-selected-state-icon-background-color); + @include token-utils.create-token-slot(color, + header-selected-state-icon-foreground-color); + } +} + +.mat-step-icon-state-done { + @include token-utils.use-tokens( + tokens-mat-stepper.$prefix, tokens-mat-stepper.get-token-slots()) { + @include token-utils.create-token-slot(background-color, + header-done-state-icon-background-color); + @include token-utils.create-token-slot(color, + header-done-state-icon-foreground-color); + } +} + +.mat-step-icon-state-edit { + @include token-utils.use-tokens( + tokens-mat-stepper.$prefix, tokens-mat-stepper.get-token-slots()) { + @include token-utils.create-token-slot(background-color, + header-edit-state-icon-background-color); + @include token-utils.create-token-slot(color, + header-edit-state-icon-foreground-color); + } +} diff --git a/src/material/stepper/stepper.scss b/src/material/stepper/stepper.scss index 5d2a70d19970..0386e3b49f1e 100644 --- a/src/material/stepper/stepper.scss +++ b/src/material/stepper/stepper.scss @@ -1,11 +1,24 @@ -@use '@angular/cdk'; @use 'sass:math'; - +@use '@angular/cdk'; +@use '../core/tokens/m2/mat/stepper' as tokens-mat-stepper; +@use '../core/tokens/token-utils'; @use './stepper-variables'; +// Gets the `calc` expression for the vertical padding of the stepper header. +@function _get-vertical-padding-calc() { + $height: var(#{token-utils.get-token-variable(header-height)}); + @return calc(calc(#{$height} - #{stepper-variables.$label-header-height}) / 2); +} + .mat-stepper-vertical, .mat-stepper-horizontal { display: block; + + @include token-utils.use-tokens( + tokens-mat-stepper.$prefix, tokens-mat-stepper.get-token-slots()) { + @include token-utils.create-token-slot(font-family, container-text-font); + @include token-utils.create-token-slot(background, container-color); + } } .mat-horizontal-stepper-header-container { @@ -30,10 +43,19 @@ margin: 0 stepper-variables.$line-gap - stepper-variables.$side-gap; min-width: stepper-variables.$line-gap + stepper-variables.$side-gap; - .mat-stepper-label-position-bottom & { - margin: 0; - min-width: 0; - position: relative; + @include token-utils.use-tokens( + tokens-mat-stepper.$prefix, tokens-mat-stepper.get-token-slots()) { + @include token-utils.create-token-slot(border-top-color, line-color); + + .mat-stepper-label-position-bottom & { + $vertical-padding: _get-vertical-padding-calc(); + margin: 0; + min-width: 0; + position: relative; + + // Ensures that the horizontal line for the step content is aligned centered vertically. + top: calc(#{$vertical-padding} + #{math.div(stepper-variables.$label-header-height, 2)}); + } } } @@ -65,6 +87,27 @@ } } + @include token-utils.use-tokens( + tokens-mat-stepper.$prefix, tokens-mat-stepper.get-token-slots()) { + $vertical-padding: _get-vertical-padding-calc(); + @include token-utils.create-token-slot(height, header-height); + + &::before, + &::after { + @include token-utils.create-token-slot(border-top-color, line-color); + } + + .mat-stepper-label-position-bottom & { + padding: #{$vertical-padding} stepper-variables.$side-gap; + + &::before, + &::after { + // Ensures that the horizontal lines for the step header are centered vertically. + top: calc(#{$vertical-padding} + #{math.div(stepper-variables.$label-header-height, 2)}); + } + } + } + .mat-stepper-label-position-bottom & { box-sizing: border-box; flex-direction: column; @@ -110,6 +153,11 @@ // We can't use `max-height` here, because it breaks the flexbox centering in IE. height: stepper-variables.$label-header-height; + @include token-utils.use-tokens( + tokens-mat-stepper.$prefix, tokens-mat-stepper.get-token-slots()) { + padding: #{_get-vertical-padding-calc()} stepper-variables.$side-gap; + } + .mat-step-icon { margin-right: stepper-variables.$vertical-stepper-content-margin - stepper-variables.$side-gap; @@ -178,6 +226,19 @@ border-left-width: stepper-variables.$line-width; border-left-style: solid; + @include token-utils.use-tokens( + tokens-mat-stepper.$prefix, tokens-mat-stepper.get-token-slots()) { + $vertical-padding: _get-vertical-padding-calc(); + $vertical-offset: calc(#{stepper-variables.$line-gap} - #{$vertical-padding}); + + @include token-utils.create-token-slot(border-left-color, line-color); + + // Ensures that the vertical lines for the step content exceed into the step + // headers with a given distance (`$mat-stepper-line-gap`) to the step icon. + top: $vertical-offset; + bottom: $vertical-offset; + } + [dir='rtl'] & { left: auto; right: 0;