From 1d8379ae1a3c1b06082e163de68404bd28dd19e4 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Mon, 17 Jul 2023 20:35:09 +0200 Subject: [PATCH] refactor(material/sidenav): switch to tokens API (#27468) Reworks the sidenav to use the new tokens theming API. --- src/material/core/tokens/m2/mat/_sidenav.scss | 65 +++++++++++++ src/material/sidenav/_sidenav-theme.scss | 75 ++------------- src/material/sidenav/drawer.scss | 91 +++++++++++++++++-- 3 files changed, 156 insertions(+), 75 deletions(-) create mode 100644 src/material/core/tokens/m2/mat/_sidenav.scss diff --git a/src/material/core/tokens/m2/mat/_sidenav.scss b/src/material/core/tokens/m2/mat/_sidenav.scss new file mode 100644 index 000000000000..a17dfa6c7873 --- /dev/null +++ b/src/material/core/tokens/m2/mat/_sidenav.scss @@ -0,0 +1,65 @@ +@use 'sass:color'; +@use 'sass:map'; +@use 'sass:meta'; +@use '../../token-utils'; +@use '../../../theming/theming'; +@use '../../../style/sass-utils'; + +// The prefix used to generate the fully qualified name for tokens in this file. +$prefix: (mat, sidenav); + +// 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 ( + // Currently zero, but it appears to be relevant for M3. + // See: https://m3.material.io/components/navigation-drawer/overview + container-shape: 0, + ); +} + +// Tokens that can be configured through Angular Material's color theming API. +@function get-color-tokens($config) { + $is-dark: map.get($config, is-dark); + $foreground: map.get($config, foreground); + $background: map.get($config, background); + $scrim-opacity: 0.6; + $scrim-color: theming.get-color-from-palette($background, card, $scrim-opacity); + $fallback-scrim-color: if($is-dark, rgba(#fff, $scrim-opacity), rgba(#000, $scrim-opacity)); + + @return ( + container-divider-color: theming.get-color-from-palette($foreground, divider), + container-background-color: theming.get-color-from-palette($background, dialog), + container-text-color: theming.get-color-from-palette($foreground, text), + content-background-color: theming.get-color-from-palette($background, background), + content-text-color: theming.get-color-from-palette($foreground, text), + + // We use invert() here to have the darken the background color expected to be used. + // If the background is light, we use a dark backdrop. If the background is dark, we + // use a light backdrop. If the value isn't a color, Sass will throw an error so we + // fall back to something generic. + scrim-color: if(meta.type-of($scrim-color) == color, + color.invert($scrim-color), $fallback-scrim-color), + ); +} + +// 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 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/sidenav/_sidenav-theme.scss b/src/material/sidenav/_sidenav-theme.scss index 2d023548cef3..70407e8a8e63 100644 --- a/src/material/sidenav/_sidenav-theme.scss +++ b/src/material/sidenav/_sidenav-theme.scss @@ -1,77 +1,14 @@ -@use 'sass:color'; -@use 'sass:map'; -@use 'sass:meta'; -@use '../core/style/private'; @use '../core/theming/theming'; +@use '../core/tokens/m2/mat/sidenav' as tokens-mat-sidenav; +@use '../core/tokens/token-utils'; +@use '../core/style/sass-utils'; @mixin color($config-or-theme) { $config: theming.get-color-config($config-or-theme); - $primary: map.get($config, primary); - $accent: map.get($config, accent); - $warn: map.get($config, warn); - $background: map.get($config, background); - $foreground: map.get($config, foreground); - $drawer-background-color: theming.get-color-from-palette($background, dialog); - $drawer-container-background-color: theming.get-color-from-palette($background, background); - $drawer-push-background-color: theming.get-color-from-palette($background, dialog); - $drawer-side-border: solid 1px theming.get-color-from-palette($foreground, divider); - - .mat-drawer-container { - background-color: $drawer-container-background-color; - color: theming.get-color-from-palette($foreground, text); - } - - .mat-drawer { - background-color: $drawer-background-color; - color: theming.get-color-from-palette($foreground, text); - - &.mat-drawer-push { - background-color: $drawer-push-background-color; - } - - &:not(.mat-drawer-side) { - // The elevation of z-16 is noted in the design specifications. - // See https://material.io/design/components/navigation-drawer.html - @include private.private-theme-elevation(16, $config); - } - } - - .mat-drawer-side { - border-right: $drawer-side-border; - - &.mat-drawer-end { - border-left: $drawer-side-border; - border-right: none; - } - } - - [dir='rtl'] .mat-drawer-side { - border-left: $drawer-side-border; - border-right: none; - - &.mat-drawer-end { - border-left: none; - border-right: $drawer-side-border; - } - } - - .mat-drawer-backdrop.mat-drawer-shown { - $opacity: 0.6; - $backdrop-color: theming.get-color-from-palette($background, card, $opacity); - - @if (meta.type-of($backdrop-color) == color) { - // We use invert() here to have the darken the background color expected to be used. If the - // background is light, we use a dark backdrop. If the background is dark, - // we use a light backdrop. - background-color: color.invert($backdrop-color); - } - @else { - // If we couldn't resolve the backdrop color to a color value, fall back to using - // `opacity` to make it opaque since its end value could be a solid color. - background-color: $backdrop-color; - opacity: $opacity; - } + @include sass-utils.current-selector-or-root() { + @include token-utils.create-token-values(tokens-mat-sidenav.$prefix, + tokens-mat-sidenav.get-color-tokens($config)); } } diff --git a/src/material/sidenav/drawer.scss b/src/material/sidenav/drawer.scss index 1f4dd7d3e7da..794dbcc37854 100644 --- a/src/material/sidenav/drawer.scss +++ b/src/material/sidenav/drawer.scss @@ -1,7 +1,10 @@ @use '@angular/cdk'; +@use '../core/tokens/m2/mat/sidenav' as tokens-mat-sidenav; +@use '../core/tokens/token-utils'; @use '../core/style/variables'; @use '../core/style/layout-common'; +@use '../core/style/elevation'; $drawer-content-z-index: 1; $drawer-side-drawer-z-index: 2; @@ -25,6 +28,12 @@ $drawer-over-drawer-z-index: 4; // the application content does not get messed up with our own CSS. @include drawer-stacking-context(); + @include token-utils.use-tokens( + tokens-mat-sidenav.$prefix, tokens-mat-sidenav.get-token-slots()) { + @include token-utils.create-token-slot(color, content-text-color); + @include token-utils.create-token-slot(background-color, content-background-color); + } + box-sizing: border-box; -webkit-overflow-scrolling: touch; @@ -74,6 +83,11 @@ $drawer-over-drawer-z-index: 4; &.mat-drawer-shown { visibility: visible; + + @include token-utils.use-tokens( + tokens-mat-sidenav.$prefix, tokens-mat-sidenav.get-token-slots()) { + @include token-utils.create-token-slot(background-color, scrim-color); + } } .mat-drawer-transition & { @@ -108,7 +122,20 @@ $drawer-over-drawer-z-index: 4; .mat-drawer { $high-contrast-border: solid 1px currentColor; + // The elevation of z-16 is noted in the design specifications. + // See https://material.io/design/components/navigation-drawer.html + @include elevation.elevation(16); @include drawer-stacking-context($drawer-over-drawer-z-index); + @include token-utils.create-token-values( + tokens-mat-sidenav.$prefix, tokens-mat-sidenav.get-unthemable-tokens()); + + @include token-utils.use-tokens( + tokens-mat-sidenav.$prefix, tokens-mat-sidenav.get-token-slots()) { + @include token-utils.create-token-slot(color, container-text-color); + @include token-utils.create-token-slot(background-color, container-background-color); + @include token-utils.create-token-slot(border-top-right-radius, container-shape); + @include token-utils.create-token-slot(border-bottom-right-radius, container-shape); + } display: block; position: absolute; @@ -120,6 +147,7 @@ $drawer-over-drawer-z-index: 4; overflow-y: auto; // TODO(kara): revisit scrolling behavior for drawers transform: translate3d(-100%, 0, 0); + &, [dir='rtl'] &.mat-drawer-end { @include cdk.high-contrast(active, off) { border-right: $high-contrast-border; @@ -140,15 +168,34 @@ $drawer-over-drawer-z-index: 4; &.mat-drawer-end { right: 0; transform: translate3d(100%, 0, 0); + + @include token-utils.use-tokens( + tokens-mat-sidenav.$prefix, tokens-mat-sidenav.get-token-slots()) { + @include token-utils.create-token-slot(border-top-left-radius, container-shape); + @include token-utils.create-token-slot(border-bottom-left-radius, container-shape); + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } } [dir='rtl'] & { - transform: translate3d(100%, 0, 0); - - &.mat-drawer-end { - left: 0; - right: auto; - transform: translate3d(-100%, 0, 0); + @include token-utils.use-tokens( + tokens-mat-sidenav.$prefix, tokens-mat-sidenav.get-token-slots()) { + @include token-utils.create-token-slot(border-top-left-radius, container-shape); + @include token-utils.create-token-slot(border-bottom-left-radius, container-shape); + border-top-right-radius: 0; + border-bottom-right-radius: 0; + transform: translate3d(100%, 0, 0); + + &.mat-drawer-end { + @include token-utils.create-token-slot(border-top-right-radius, container-shape); + @include token-utils.create-token-slot(border-bottom-right-radius, container-shape); + border-top-left-radius: 0; + border-bottom-left-radius: 0; + left: 0; + right: auto; + transform: translate3d(-100%, 0, 0); + } } } @@ -164,6 +211,38 @@ $drawer-over-drawer-z-index: 4; } } +.mat-drawer-side { + box-shadow: none; + + @include token-utils.use-tokens( + tokens-mat-sidenav.$prefix, tokens-mat-sidenav.get-token-slots()) { + @include token-utils.create-token-slot(border-right-color, container-divider-color); + border-right-width: 1px; + border-right-style: solid; + + &.mat-drawer-end { + @include token-utils.create-token-slot(border-left-color, container-divider-color); + border-left-width: 1px; + border-left-style: solid; + border-right: none; + } + + [dir='rtl'] & { + @include token-utils.create-token-slot(border-left-color, container-divider-color); + border-left-width: 1px; + border-left-style: solid; + border-right: none; // Clears the default LTR border. + + &.mat-drawer-end { + @include token-utils.create-token-slot(border-right-color, container-divider-color); + border-right-width: 1px; + border-right-style: solid; + border-left: none; + } + } + } +} + // Note that this div isn't strictly necessary on all browsers, however we need it in // order to avoid a layout issue in Chrome. The issue is that in RTL mode the browser doesn't // account for the sidenav's scrollbar while positioning, which ends up pushing it partially