// // Copyright 2016 Google Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // Selector '.mdc-*' should only be used in this project. // stylelint-disable selector-class-pattern @use 'sass:math'; @use '@material/elevation/mixins' as elevation-mixins; @use '@material/feature-targeting/feature-targeting'; @use '@material/ripple/ripple'; @use '@material/ripple/ripple-theme'; @use '@material/rtl/mixins' as rtl-mixins; @use '@material/theme/theme-color'; @use '@material/theme/theme'; @use '@material/touch-target/mixins' as touch-target-mixins; @use '@material/typography/typography'; @use '@material/shape/mixins' as shape-mixins; @use '@material/shape/functions' as shape-functions; @use '@material/density/functions' as density-functions; @use './variables'; @use '@material/elevation/functions' as elevation-functions; $ripple-target: '.mdc-button__ripple'; @mixin core-styles($query: feature-targeting.all()) { @include without-ripple($query); @include ripple($query); } // This API is intended for use by frameworks that may want to separate the ripple-related styles from the other // button styles. It is recommended that most users use `mdc-button-core-styles` instead. @mixin without-ripple($query: feature-targeting.all()) { $feat-color: feature-targeting.create-target($query, color); $feat-structure: feature-targeting.create-target($query, structure); @include touch-target-mixins.wrapper($query); // COPYBARA_COMMENT_THIS_LINE // prettier-ignore @include elevation-mixins.overlay-common($query); // COPYBARA_COMMENT_THIS_LINE // postcss-bem-linter: define button .mdc-button { @include base_($query); @include shape-radius(variables.$shape-radius, $query: $query); @include density(variables.$density-scale, $query: $query); @include container-fill-color(transparent, $query); @include disabled-container-fill-color(transparent, $query); // The icon CSS class overrides styles defined in the .material-icons CSS // class, which is loaded separately so the order of CSS definitions is not // guaranteed. Therefore, increase specifity by nesting this class to ensure // overrides apply. .mdc-button__icon { @include feature-targeting.targets($feat-structure) { @include icon_; } } .mdc-button__touch { @include touch-target-mixins.touch-target($query: $query); } @include ink-color(primary, $query); @include disabled-ink-color(variables.$disabled-ink-color, $query); } .mdc-button__label + .mdc-button__icon { @include feature-targeting.targets($feat-structure) { @include icon-trailing_; } } // stylelint-disable-next-line selector-no-qualifying-type svg.mdc-button__icon { @include feature-targeting.targets($feat-structure) { @include icon-svg_; } } .mdc-button--raised, .mdc-button--unelevated, .mdc-button--outlined { .mdc-button__icon { @include feature-targeting.targets($feat-structure) { // Icons inside contained buttons have different styles due to increased button padding @include icon-contained_; } } .mdc-button__label + .mdc-button__icon { @include feature-targeting.targets($feat-structure) { @include icon-contained-trailing_; } } } .mdc-button--raised, .mdc-button--unelevated { @include filled_($query); } .mdc-button--raised { @include raised_($query); } .mdc-button--outlined { @include outlined_($query); } .mdc-button--touch { @include touch-target-mixins.margin( $component-height: variables.$height, $query: $query ); } // postcss-bem-linter: end } // This API is intended for use by frameworks that may want to separate the ripple-related styles from the other // button styles. It is recommended that most users use `mdc-button-core-styles` instead. @mixin ripple($query: feature-targeting.all()) { $feat-structure: feature-targeting.create-target($query, structure); @include ripple.common($query); // COPYBARA_COMMENT_THIS_LINE .mdc-button { @include ripple.surface($query: $query, $ripple-target: $ripple-target); @include ripple.radius-bounded( $query: $query, $ripple-target: $ripple-target ); @include ripple-states($color: primary, $query: $query); #{$ripple-target} { @include feature-targeting.targets($feat-structure) { position: absolute; // Ripple needs content-box as the box sizing and box-sizing: border-box // is often set as a default, so we override that here. box-sizing: content-box; width: 100%; height: 100%; overflow: hidden; } } // Ripple targets inside outlined buttons set their own `top`/`left`, // depending on the border width. &:not(.mdc-button--outlined) #{$ripple-target} { @include feature-targeting.targets($feat-structure) { top: 0; left: 0; } } } .mdc-button--raised, .mdc-button--unelevated { @include ripple-states($color: on-primary, $query: $query); } } /// /// Sets ripple color for button. /// @mixin ripple-states($color, $query: feature-targeting.all()) { @include ripple-theme.states( $color: $color, $query: $query, $ripple-target: $ripple-target ); } @mixin filled-accessible( $container-fill-color, $query: feature-targeting.all() ) { $fill-tone: theme-color.tone($container-fill-color); @include container-fill-color($container-fill-color, $query); @if ($fill-tone == 'dark') { @include ink-color(text-primary-on-dark, $query); @include ripple-states($color: text-primary-on-dark, $query: $query); } @else { @include ink-color(text-primary-on-light, $query); @include ripple-states($color: text-primary-on-light, $query: $query); } } /// /// Sets the container fill color to the given color for an enabled button. /// @param {Color} $color - The desired container fill color. /// @mixin container-fill-color($color, $query: feature-targeting.all()) { // :not(:disabled) is used to support link styled as button // as link does not support :enabled style &:not(:disabled) { @include container-fill-color_($color, $query: $query); } } /// /// Sets the container fill color to the given color for a disabled button. /// @param {Color} $color - The desired container fill color. /// @mixin disabled-container-fill-color($color, $query: feature-targeting.all()) { &:disabled { @include container-fill-color_($color, $query: $query); } } /// /// Sets the outline color to the given color for an enabled button. /// @param {Color} $color - The desired outline color. /// @mixin outline-color($color, $query: feature-targeting.all()) { &:not(:disabled) { @include outline-color_($color, $query: $query); } } /// /// Sets the outline color to the given color for a disabled button. /// @param {Color} $color - The desired outline color. /// @mixin disabled-outline-color($color, $query: feature-targeting.all()) { &:disabled { @include outline-color_($color, $query: $query); } } /// /// Sets the icon color to the given color for an enabled button. /// @param {Color} $color - The desired icon color. /// @mixin icon-color($color, $query: feature-targeting.all()) { &:not(:disabled) { @include icon-color_($color, $query: $query); } } /// /// Sets the icon color to the given color for a disabled button. /// @param {Color} $color - The desired icon color. /// @mixin disabled-icon-color($color, $query: feature-targeting.all()) { &:disabled { @include icon-color_($color, $query: $query); } } /// /// Sets the ink color to the given color for an enabled button, /// and sets the icon color to the given color unless `mdc-button-icon-color` /// is also used. /// @param {Color} $color - The desired ink color. /// @mixin ink-color($color, $query: feature-targeting.all()) { &:not(:disabled) { @include ink-color_($color, $query: $query); } } /// /// Sets the ink color to the given color for a disabled button, /// and sets the icon color to the given color unless `mdc-button-icon-color` /// is also used. /// @param {Color} $color - The desired ink color. /// @mixin disabled-ink-color($color, $query: feature-targeting.all()) { &:disabled { @include ink-color_($color, $query: $query); } } /// /// Sets density scale for button. /// /// @param {Number | String} $density-scale - Density scale value for component. Supported density scale values `-3`, /// `-2`, `-1`, `0`. /// @mixin density($density-scale, $query: feature-targeting.all()) { $height: density-functions.prop-value( $density-config: variables.$density-config, $density-scale: $density-scale, $property-name: height, ); @include height($height, $query: $query); @if $density-scale != 0 { @include touch-target-reset_($query: $query); } } /// /// Resets touch target-related styles. This is called from the density mixin to /// automatically remove the increased touch target, since dense components /// don't have the same default a11y requirements. /// @access private /// @mixin touch-target-reset_($query: feature-targeting.all()) { $feat-structure: feature-targeting.create-target($query, structure); @include feature-targeting.targets($feat-structure) { margin-top: 0; margin-bottom: 0; } .mdc-button__touch { @include feature-targeting.targets($feat-structure) { display: none; } } } /// /// Sets custom height for button. /// @param {Number} $height - Height of button in `px`. /// @mixin height($height, $query: feature-targeting.all()) { $feat-structure: feature-targeting.create-target($query, structure); @include feature-targeting.targets($feat-structure) { height: $height; } } @mixin shape-radius( $radius, $rtl-reflexive: false, $density-scale: variables.$density-scale, $query: feature-targeting.all() ) { $height: density-functions.prop-value( $density-config: variables.$density-config, $density-scale: $density-scale, $property-name: height, ); @include shape-mixins.radius( $radius, $rtl-reflexive, $component-height: $height, $query: $query ); #{$ripple-target} { @include shape-mixins.radius( $radius, $rtl-reflexive, $component-height: $height, $query: $query ); } } /// /// Sets horizontal padding to the given number. /// @param {Number} $padding /// @mixin horizontal-padding($padding, $query: feature-targeting.all()) { $feat-structure: feature-targeting.create-target($query, structure); @include feature-targeting.targets($feat-structure) { // $padding should be a single value; enforce it by specifying all 4 sides in the output padding: 0 $padding 0 $padding; } } @mixin outline-width( $outline-width, $padding: variables.$contained-horizontal-padding, $query: feature-targeting.all() ) { $feat-structure: feature-targeting.create-target($query, structure); // Note: Adjust padding to maintain consistent width with non-outlined buttons $padding-value: math.max($padding - $outline-width, 0); @include horizontal-padding($padding-value, $query); @include feature-targeting.targets($feat-structure) { border-width: $outline-width; } #{$ripple-target} { @include feature-targeting.targets($feat-structure) { top: -$outline-width; left: -$outline-width; border: $outline-width solid transparent; } } .mdc-button__touch { @include feature-targeting.targets($feat-structure) { left: -$outline-width; width: calc(100% + 2 * #{$outline-width}); } } } /// /// Sets the button label to overflow as ellipsis /// @mixin label-overflow-ellipsis($query: feature-targeting.all()) { .mdc-button__label { @include typography.overflow-ellipsis($query: $query); } } @mixin base_($query) { $feat-color: feature-targeting.create-target($query, color); $feat-structure: feature-targeting.create-target($query, structure); @include typography.typography(button, $query); @include horizontal-padding(variables.$horizontal-padding, $query); @include elevation-mixins.overlay-surface-position($query: $query); @include elevation-mixins.overlay-dimensions(100%, $query: $query); @include feature-targeting.targets($feat-structure) { display: inline-flex; // position: relative; already set in mdc-elevation-overlay-surface-position align-items: center; justify-content: center; box-sizing: border-box; min-width: 64px; border: none; outline: none; /* @alternate */ line-height: inherit; user-select: none; -webkit-appearance: none; // Even though `visible` is the default, IE 11 computes the property as // `hidden` in some cases, unless it's explicitly defined here. overflow: visible; vertical-align: middle; } &::-moz-focus-inner { @include feature-targeting.targets($feat-structure) { padding: 0; border: 0; } } // postcss-bem-linter: ignore &:active { @include feature-targeting.targets($feat-structure) { outline: none; } } &:hover { @include feature-targeting.targets($feat-structure) { cursor: pointer; } } &:disabled { @include feature-targeting.targets($feat-structure) { cursor: default; pointer-events: none; } } } @mixin icon_ { @include rtl-mixins.reflexive-box(margin, right, 8px); display: inline-block; width: 18px; height: 18px; font-size: 18px; vertical-align: top; } @mixin icon-trailing_ { @include rtl-mixins.reflexive-box(margin, left, 8px); } @mixin icon-svg_ { fill: currentColor; } @mixin icon-contained_ { @include rtl-mixins.reflexive-property(margin, -4px, 8px); } @mixin icon-contained-trailing_ { @include rtl-mixins.reflexive-property(margin, 8px, -4px); } @mixin outlined_($query) { $feat-structure: feature-targeting.create-target($query, structure); @include outline-width(variables.$outlined-border-width, $query: $query); @include outline-color(variables.$outline-color, $query); @include disabled-outline-color(variables.$disabled-container-color, $query); @include feature-targeting.targets($feat-structure) { border-style: solid; } } @mixin filled_($query) { @include horizontal-padding(variables.$contained-horizontal-padding, $query); @include container-fill-color(primary, $query); @include ink-color(on-primary, $query); @include disabled-container-fill-color( variables.$disabled-container-color, $query ); @include disabled-ink-color(variables.$disabled-ink-color, $query); } @mixin raised_($query) { $feat-animation: feature-targeting.create-target($query, animation); $feat-color: feature-targeting.create-target($query, color); @include elevation-mixins.elevation(2, $query: $query); &:hover, &:focus { @include elevation-mixins.elevation(4, $query: $query); } &:active { @include elevation-mixins.elevation(8, $query: $query); } &:disabled { @include elevation-mixins.elevation(0, $query: $query); } @include feature-targeting.targets($feat-animation) { transition: elevation-functions.transition-value(); } } /// /// Sets the container fill color to the given color. This mixin should be /// wrapped in a selector that qualifies button state. /// @access private /// @mixin container-fill-color_($color, $query: feature-targeting.all()) { $feat-color: feature-targeting.create-target($query, color); @include feature-targeting.targets($feat-color) { @include theme.prop(background-color, $color); } } /// /// Sets the outline color to the given color. This mixin should be /// wrapped in a selector that qualifies button state. /// @access private /// @mixin outline-color_($color, $query: feature-targeting.all()) { $feat-color: feature-targeting.create-target($query, color); @include feature-targeting.targets($feat-color) { @include theme.prop(border-color, $color); } } /// /// Sets the icon color to the given color. This mixin should be /// wrapped in a selector that qualifies button state. /// @access private /// @mixin icon-color_($color, $query: feature-targeting.all()) { $feat-color: feature-targeting.create-target($query, color); .mdc-button__icon { @include feature-targeting.targets($feat-color) { @include theme.prop(color, $color); } } } /// /// Sets the ink color to the given color. This mixin should be /// wrapped in a selector that qualifies button state. /// @access private /// @mixin ink-color_($color, $query: feature-targeting.all()) { $feat-color: feature-targeting.create-target($query, color); @include feature-targeting.targets($feat-color) { @include theme.prop(color, $color); } }