diff --git a/src/material/button/_icon-button-theme.scss b/src/material/button/_icon-button-theme.scss index 36ce417ed834..d0d7913457ae 100644 --- a/src/material/button/_icon-button-theme.scss +++ b/src/material/button/_icon-button-theme.scss @@ -1,41 +1,66 @@ @use 'sass:map'; +@use '@material/density/functions' as mdc-density-functions; @use '@material/icon-button/mixins' as mdc-icon-button; @use '@material/icon-button/icon-button-theme' as mdc-icon-button-theme; @use '@material/theme/theme-color' as mdc-theme-color; +@use '../core/tokens/m2/mdc/icon-button' as tokens-mdc-icon-button; @use './button-theme-private'; @use '../core/mdc-helpers/mdc-helpers'; @use '../core/theming/theming'; @use '../core/typography/typography'; +@mixin _ripple-color($color) { + --mat-mdc-button-persistent-ripple-color: #{$color}; + --mat-mdc-button-ripple-color: #{rgba($color, 0.1)}; +} + +@function _variable-safe-contrast-tone($value, $is-dark) { + @if ($value == 'dark' or $value == 'light' or type-of($value) == 'color') { + @return mdc-theme-color.contrast-tone($value); + } + + @return if($is-dark, 'light', 'dark'); +} + + @mixin color($config-or-theme) { $config: theming.get-color-config($config-or-theme); - @include mdc-helpers.using-mdc-theme($config) { - $is-dark: map.get($config, is-dark); - $on-surface: mdc-theme-color.prop-value(on-surface); - $disabled-color: rgba($on-surface, if($is-dark, 0.5, 0.38)); - - .mat-mdc-icon-button { - @include button-theme-private.ripple-theme-styles($config, false); - - &.mat-primary { - @include mdc-icon-button-theme.theme((icon-color: mdc-theme-color.prop-value(primary))); - } - - &.mat-accent { - @include mdc-icon-button-theme.theme((icon-color: mdc-theme-color.prop-value(secondary))); - } - - &.mat-warn { - @include mdc-icon-button-theme.theme((icon-color: (mdc-theme-color.prop-value(error)))); - } - - @include button-theme-private.apply-disabled-style() { - @include mdc-icon-button-theme.theme(( - icon-color: $disabled-color, - disabled-icon-color: $disabled-color, - )); - } + $color-tokens: tokens-mdc-icon-button.get-color-tokens($config); + $background-palette: map.get($config, background); + $surface: theming.get-color-from-palette($background-palette, card); + $is-dark: map.get($config, is-dark); + $on-surface: if(_variable-safe-contrast-tone($surface, $is-dark) == 'dark', #000, #fff); + + .mat-mdc-icon-button { + @include button-theme-private.ripple-theme-styles($config, false); + @include mdc-icon-button-theme.theme($color-tokens); + @include _ripple-color($on-surface); + + &.mat-primary { + $color: theming.get-color-from-palette(map.get($config, primary)); + @include mdc-icon-button-theme.theme((icon-color: $color)); + @include _ripple-color($color); + } + + &.mat-accent { + $color: theming.get-color-from-palette(map.get($config, accent)); + @include mdc-icon-button-theme.theme((icon-color: $color)); + @include _ripple-color($color); + } + + &.mat-warn { + $color: theming.get-color-from-palette(map.get($config, warn)); + @include mdc-icon-button-theme.theme((icon-color: $color)); + @include _ripple-color($color); + } + + @include button-theme-private.apply-disabled-style() { + $disabled-color: rgba($on-surface, if($is-dark, 0.5, 0.38)); + @include mdc-icon-button-theme.theme(( + icon-color: $disabled-color, + disabled-icon-color: $disabled-color, + )); } } } @@ -50,10 +75,25 @@ @mixin density($config-or-theme) { $density-scale: theming.get-density-config($config-or-theme); - // Use `mat-mdc-button-base` to increase the specificity over the button's structural styles. - .mat-mdc-icon-button.mat-mdc-button-base { - @include mdc-icon-button.density($density-scale, $query: mdc-helpers.$mdc-base-styles-query); - @include button-theme-private.touch-target-density($density-scale); + + .mat-mdc-icon-button { + // Manually apply the expected density theming, and include the padding + // as it was applied before + $calculated-size: mdc-density-functions.prop-value( + $density-config: ( + size: ( + default: 48px, + maximum: 48px, + minimum: 28px, + ), + ), + $density-scale: $density-scale, + $property-name: size, + ); + + @include mdc-icon-button-theme.theme(( + state-layer-size: $calculated-size, + )); } } diff --git a/src/material/button/icon-button.scss b/src/material/button/icon-button.scss index 694d2736e482..95a40ba69d24 100644 --- a/src/material/button/icon-button.scss +++ b/src/material/button/icon-button.scss @@ -1,29 +1,36 @@ -@use 'sass:map'; @use '@material/icon-button/icon-button' as mdc-icon-button; @use '@material/icon-button/icon-button-theme' as mdc-icon-button-theme; +@use '@material/theme/custom-properties' as mdc-custom-properties; + +@use '../core/tokens/m2/mdc/icon-button' as m2-mdc-icon-button; @use './button-base'; -@use '../core/mdc-helpers/mdc-helpers'; @use '../core/style/private'; -@include mdc-helpers.disable-mdc-fallback-declarations { - @include mdc-icon-button.without-ripple($query: mdc-helpers.$mdc-base-styles-query); +// The slots for tokens that will be configured in the theme can be emitted with no fallback. +@include mdc-custom-properties.configure($emit-fallback-values: false, $emit-fallback-vars: false) { + $token-slots: m2-mdc-icon-button.get-token-slots(); + + // Add the MDC component static styles. + @include mdc-icon-button.static-styles(); + + .mat-mdc-icon-button { + // Add the official slots for the MDC component. + @include mdc-icon-button-theme.theme-styles($token-slots); + + // Add default values for tokens that aren't outputted by the theming API. + @include mdc-icon-button-theme.theme(m2-mdc-icon-button.get-unthemable-tokens()); + } } .mat-mdc-icon-button { - @include mdc-helpers.disable-mdc-fallback-declarations { - $theme-overrides: button-base.mat-private-button-remove-ripple(( - icon-color: inherit, - // We don't change the color on focus/hover so exclude - // these styles both to reduce bundle size and specificity. - focus-icon-color: null, - hover-icon-color: null, - pressed-icon-color: null, - )); - - @include mdc-icon-button-theme.theme-styles( - map.merge(mdc-icon-button-theme.$light-theme, $theme-overrides)); - } + // Not all applications import the theming which would apply a default padding. + // TODO: Determine how to enforce theming exists, otherwise padding will be unset. + padding: 12px; + + // Icon size used to be placed on the host element. Now, in `theme-styles` it is placed on + // the unused `.mdc-button__icon` class. Explicitly set the font-size here. + font-size: var(--mdc-icon-button-icon-size); // Border radius is inherited by ripple to know its shape. Set to 50% so the ripple is round. border-radius: 50%; diff --git a/src/material/core/tokens/m2/mdc/_icon-button.scss b/src/material/core/tokens/m2/mdc/_icon-button.scss new file mode 100644 index 000000000000..dbf570d5796c --- /dev/null +++ b/src/material/core/tokens/m2/mdc/_icon-button.scss @@ -0,0 +1,80 @@ +@use 'sass:map'; +@use '../../token-utils'; + +// The prefix used to generate the fully qualified name for tokens in this file. +$prefix: (mdc, icon-button); + +// Tokens that can't be configured through Angular Material's current theming API, +// but may be in a future version of the theming API. +// +// Tokens that are available in MDC, but not used in Angular Material should be mapped to `null`. +// `null` indicates that we are intentionally choosing not to emit a slot or value for the token in +// our CSS. +@function get-unthemable-tokens() { + @return ( + // ============================================================================================= + // = TOKENS THAT SHOULD NOT BE CUSTOMIZABLE = + // ============================================================================================= + // Determines the size of the icon. Name is inaccurate - applies to the whole component, + // not just the state layer. + state-layer-size: 48px, + + // MDC's icon size applied to svg and img elements inside the component + icon-size: 24px, + + // Only applies to :disabled icons, but Angular Components uses [disabled] since :disabled + // wouldn't work on tags. + disabled-icon-color: black, + + // Angular version applies an opacity 1 with a color change, and this only applies with + // :disabled anyways. + disabled-icon-opacity: 0.38, + + // ============================================================================================= + // = TOKENS NOT USED IN ANGULAR MATERIAL = + // ============================================================================================= + // State layer is unused + focus-icon-color: null, + focus-state-layer-color: null, + focus-state-layer-opacity: null, + hover-icon-color: null, + hover-state-layer-color: null, + hover-state-layer-opacity: null, + pressed-icon-color: null, + pressed-state-layer-color: null, + pressed-state-layer-opacity: null, + + ); +} + +// Tokens that can be configured through Angular Material's color theming API. +@function get-color-tokens($config) { + @return ( + icon-color: inherit, + ); +} + +// Tokens that can be configured through Angular Material's typography theming API. +@function get-typography-tokens($config) { + @return (); +} + +// Tokens that can be configured through Angular Material's density theming API. +@function get-density-tokens($config) { + @return (); +} + +// 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 map.merge( + get-unthemable-tokens(), + map.merge( + get-color-tokens(token-utils.$placeholder-color-config), + map.merge( + get-typography-tokens(token-utils.$placeholder-typography-config), + get-density-tokens(token-utils.$placeholder-density-config) + ) + ) + ); +} diff --git a/src/material/core/tokens/tests/test-validate-tokens.scss b/src/material/core/tokens/tests/test-validate-tokens.scss index 707a8e5c7aca..7fc4e046375a 100644 --- a/src/material/core/tokens/tests/test-validate-tokens.scss +++ b/src/material/core/tokens/tests/test-validate-tokens.scss @@ -5,12 +5,14 @@ @use '@material/card/outlined-card-theme' as mdc-outlined-card-theme; @use '@material/checkbox/checkbox-theme' as mdc-checkbox-theme; @use '@material/circular-progress/circular-progress-theme' as mdc-circular-progress-theme; +@use '@material/icon-button/icon-button-theme' as mdc-icon-button-theme; @use '@material/linear-progress/linear-progress-theme' as mdc-linear-progress-theme; @use '@material/list/list-theme' as mdc-list-theme; @use '@material/theme/validate' as mdc-validate; @use '../m2/mdc/circular-progress' as tokens-mdc-circular-progress; @use '../m2/mdc/elevated-card' as tokens-mdc-elevated-card; +@use '../m2/mdc/icon-button' as tokens-mdc-icon-button; @use '../m2/mdc/checkbox' as tokens-mdc-checkbox; @use '../m2/mdc/linear-progress' as tokens-mdc-linear-progress; @use '../m2/mdc/list' as tokens-mdc-list; @@ -38,6 +40,11 @@ $slots: tokens-mdc-circular-progress.get-token-slots(), $reference: mdc-circular-progress-theme.$light-theme ); +@include validate-slots( + $component: 'm2.mdc.icon-button', + $slots: tokens-mdc-icon-button.get-token-slots(), + $reference: mdc-icon-button-theme.$light-theme +); @include validate-slots( $component: 'm2.mdc.elevated-card', $slots: tokens-mdc-elevated-card.get-token-slots(),