@use '../core/style/vendor-prefixes'; @use '../core/tokens/m2/mdc/tab-indicator' as tokens-mdc-tab-indicator; @use '../core/tokens/m2/mdc/secondary-navigation-tab' as tokens-mdc-secondary-navigation-tab; @use '../core/tokens/m2/mat/tab-header' as tokens-mat-tab-header; @use '../core/tokens/m2/mat/tab-header-with-background' as tokens-mat-tab-header-with-background; @use '../core/tokens/token-utils'; $mat-tab-animation-duration: 500ms !default; // Combines the various structural styles we need for the tab group and tab nav bar. @mixin structural-styles { .mdc-tab { min-width: 90px; padding: 0 24px; display: flex; flex: 1 0 auto; justify-content: center; box-sizing: border-box; border: none; outline: none; text-align: center; white-space: nowrap; cursor: pointer; z-index: 1; } .mdc-tab__content { display: flex; align-items: center; justify-content: center; height: inherit; pointer-events: none; } .mdc-tab__text-label { transition: 150ms color linear; display: inline-block; line-height: 1; z-index: 2; } .mdc-tab--active .mdc-tab__text-label { transition-delay: 100ms; } ._mat-animation-noopable .mdc-tab__text-label { transition: none; } .mdc-tab-indicator { display: flex; position: absolute; top: 0; left: 0; justify-content: center; width: 100%; height: 100%; pointer-events: none; z-index: 1; } .mdc-tab-indicator__content { transition: var(--mat-tab-animation-duration, 250ms) transform cubic-bezier(0.4, 0, 0.2, 1); transform-origin: left; opacity: 0; } .mdc-tab-indicator__content--underline { align-self: flex-end; box-sizing: border-box; width: 100%; border-top-style: solid; } .mdc-tab-indicator--active .mdc-tab-indicator__content { opacity: 1; } ._mat-animation-noopable, .mdc-tab-indicator--no-transition { .mdc-tab-indicator__content { transition: none; } } .mat-mdc-tab-ripple.mat-mdc-tab-ripple { position: absolute; top: 0; left: 0; bottom: 0; right: 0; pointer-events: none; } } @mixin tab { -webkit-tap-highlight-color: transparent; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-decoration: none; // Tabs might be `button` elements so we have to reset the user agent styling. background: none; @include token-utils.use-tokens( tokens-mdc-secondary-navigation-tab.$prefix, tokens-mdc-secondary-navigation-tab.get-token-slots() ) { @include token-utils.create-token-slot(height, container-height); } @include token-utils.use-tokens( tokens-mat-tab-header.$prefix, tokens-mat-tab-header.get-token-slots() ) { @include token-utils.create-token-slot(font-family, label-text-font); @include token-utils.create-token-slot(font-size, label-text-size); @include token-utils.create-token-slot(letter-spacing, label-text-tracking); @include token-utils.create-token-slot(line-height, label-text-line-height); @include token-utils.create-token-slot(font-weight, label-text-weight); } &.mdc-tab { // MDC's tabs stretch to fit the header by default, whereas stretching on our current ones // is an opt-in behavior. Also technically we don't need to combine the two classes, but // we need the extra specificity to avoid issues with CSS insertion order. flex-grow: 0; } .mdc-tab-indicator__content--underline { @include token-utils.use-tokens( tokens-mdc-tab-indicator.$prefix, tokens-mdc-tab-indicator.get-token-slots() ) { @include token-utils.create-token-slot(border-color, active-indicator-color); @include token-utils.create-token-slot(border-top-width, active-indicator-height); @include token-utils.create-token-slot(border-radius, active-indicator-shape); } } @include token-utils.use-tokens( tokens-mat-tab-header.$prefix, tokens-mat-tab-header.get-token-slots() ) { &:hover .mdc-tab__text-label { @include token-utils.create-token-slot(color, inactive-hover-label-text-color); } &:focus .mdc-tab__text-label { @include token-utils.create-token-slot(color, inactive-focus-label-text-color); } &.mdc-tab--active { .mdc-tab__text-label { @include token-utils.create-token-slot(color, active-label-text-color); } .mdc-tab__ripple::before, .mat-ripple-element { @include token-utils.create-token-slot(background-color, active-ripple-color); } &:hover { .mdc-tab__text-label { @include token-utils.create-token-slot(color, active-hover-label-text-color); } .mdc-tab-indicator__content--underline { @include token-utils.create-token-slot(border-color, active-hover-indicator-color); } } &:focus { .mdc-tab__text-label { @include token-utils.create-token-slot(color, active-focus-label-text-color); } .mdc-tab-indicator__content--underline { @include token-utils.create-token-slot(border-color, active-focus-indicator-color); } } } } &.mat-mdc-tab-disabled { // MDC doesn't support disabled tabs so we need to improvise. opacity: 0.4; // We use `pointer-events` to make the element unclickable when it's disabled, rather than // preventing the default action through JS, because we can't prevent the action reliably // due to other directives potentially registering their events earlier. This shouldn't cause // the user to click through, because we always have a header behind the tab. Furthermore, this // saves us some CSS, because we don't have to add `:not(.mat-mdc-tab-disabled)` to all the // hover and focus selectors. pointer-events: none; // We also need to prevent content from being clickable. .mdc-tab__content { pointer-events: none; } @include token-utils.use-tokens( tokens-mat-tab-header.$prefix, tokens-mat-tab-header.get-token-slots() ) { .mdc-tab__ripple::before, .mat-ripple-element { @include token-utils.create-token-slot(background-color, disabled-ripple-color); } } } // Used to render out the background tint when hovered/focused. Usually this is done by // MDC's ripple styles, however we're using our own ripples due to size concerns. .mdc-tab__ripple::before { content: ''; display: block; position: absolute; top: 0; left: 0; right: 0; bottom: 0; opacity: 0; pointer-events: none; @include token-utils.use-tokens( tokens-mat-tab-header.$prefix, tokens-mat-tab-header.get-token-slots() ) { @include token-utils.create-token-slot(background-color, inactive-ripple-color); } } .mdc-tab__text-label { @include token-utils.use-tokens( tokens-mat-tab-header.$prefix, tokens-mat-tab-header.get-token-slots() ) { @include token-utils.create-token-slot(color, inactive-label-text-color); } // We support projecting icons into the tab. These styles ensure that they're centered. display: inline-flex; align-items: center; } .mdc-tab__content { // Required for `fitInkBarToContent` to work. This used to be included with MDC's // `without-ripple` mixin, but that no longer appears to be the case with `static-styles`. // Since the latter is ~10kb smaller, we include this one extra style ourselves. position: relative; // MDC sets `pointer-events: none` on the content which prevents interactions with the // nested content. Re-enable it since we allow nesting any content in the tab (see #26195). pointer-events: auto; } // We need to handle the hover and focus indication ourselves, because we don't use MDC's ripple. &:hover .mdc-tab__ripple::before { opacity: 0.04; } &.cdk-program-focused, &.cdk-keyboard-focused { .mdc-tab__ripple::before { opacity: 0.12; } } .mat-ripple-element { opacity: 0.12; @include token-utils.use-tokens( tokens-mat-tab-header.$prefix, tokens-mat-tab-header.get-token-slots() ) { @include token-utils.create-token-slot(background-color, inactive-ripple-color); } } } // Structural styles for a tab header. Used by both `mat-tab-header` and `mat-tab-nav-bar`. // We need this styles on top of MDC's, because MDC doesn't support pagination like ours. @mixin paginated-tab-header { .mat-mdc-tab-header { display: flex; overflow: hidden; position: relative; flex-shrink: 0; } .mdc-tab-indicator .mdc-tab-indicator__content { transition-duration: var(--mat-tab-animation-duration, 250ms); } .mat-mdc-tab-header-pagination { @include vendor-prefixes.user-select(none); position: relative; display: none; justify-content: center; align-items: center; min-width: 32px; cursor: pointer; z-index: 2; -webkit-tap-highlight-color: transparent; touch-action: none; box-sizing: content-box; outline: 0; &::-moz-focus-inner { border: 0; } .mat-ripple-element { opacity: 0.12; @include token-utils.use-tokens( tokens-mat-tab-header.$prefix, tokens-mat-tab-header.get-token-slots() ) { @include token-utils.create-token-slot(background-color, inactive-ripple-color); } } .mat-mdc-tab-header-pagination-controls-enabled & { display: flex; } } // The pagination control that is displayed on the left side of the tab header. .mat-mdc-tab-header-pagination-before, .mat-mdc-tab-header-rtl .mat-mdc-tab-header-pagination-after { padding-left: 4px; .mat-mdc-tab-header-pagination-chevron { transform: rotate(-135deg); } } // The pagination control that is displayed on the right side of the tab header. .mat-mdc-tab-header-rtl .mat-mdc-tab-header-pagination-before, .mat-mdc-tab-header-pagination-after { padding-right: 4px; .mat-mdc-tab-header-pagination-chevron { transform: rotate(45deg); } } .mat-mdc-tab-header-pagination-chevron { border-style: solid; border-width: 2px 2px 0 0; height: 8px; width: 8px; @include token-utils.use-tokens( tokens-mat-tab-header.$prefix, tokens-mat-tab-header.get-token-slots() ) { @include token-utils.create-token-slot(border-color, pagination-icon-color); } } .mat-mdc-tab-header-pagination-disabled { box-shadow: none; cursor: default; pointer-events: none; .mat-mdc-tab-header-pagination-chevron { opacity: 0.4; } } .mat-mdc-tab-list { flex-grow: 1; position: relative; transition: transform 500ms cubic-bezier(0.35, 0, 0.25, 1); ._mat-animation-noopable & { transition: none; } } } // Structural styles for the element that wraps the paginated header items. @mixin paginated-tab-header-item-wrapper($parent) { display: flex; flex: 1 0 auto; // We need to set the parent here explicitly, in order to prevent the alignment // from any parent tab groups from propagating down to the children when nesting. // Note that these are used as inputs so they shouldn't be changed to `mat-mdc-`. [mat-align-tabs='center'] > #{$parent} & { justify-content: center; } [mat-align-tabs='end'] > #{$parent} & { justify-content: flex-end; } // Prevent the header from collapsing when it is a drop list. This is useful, // because its height may become zero once all the tabs are dragged out. // Note that ideally we would do this by default, rather than only in a drop // list, but it ended up being hugely breaking internally. .cdk-drop-list &, &.cdk-drop-list { @include token-utils.use-tokens( tokens-mdc-secondary-navigation-tab.$prefix, tokens-mdc-secondary-navigation-tab.get-token-slots() ) { @include token-utils.create-token-slot(min-height, container-height); } } } // Structural styles for the element that wraps the paginated container's content. // Include a selector for an inverted header if the header may be optionally positioned on the // bottom of the content. @mixin paginated-tab-header-container($inverted-header-selector: null) { display: flex; flex-grow: 1; overflow: hidden; z-index: 1; @include token-utils.use-tokens( tokens-mat-tab-header.$prefix, tokens-mat-tab-header.get-token-slots() ) { border-bottom-style: solid; @include token-utils.create-token-slot(border-bottom-width, divider-height); @include token-utils.create-token-slot(border-bottom-color, divider-color); @if ($inverted-header-selector) { #{$inverted-header-selector} & { border-bottom: none; border-top-style: solid; @include token-utils.create-token-slot(border-top-width, divider-height); @include token-utils.create-token-slot(border-top-color, divider-color); } } } } @mixin paginated-tab-header-with-background($header-selector, $tab-selector) { &.mat-tabs-with-background { @include token-utils.use-tokens( tokens-mat-tab-header-with-background.$prefix, tokens-mat-tab-header-with-background.get-token-slots() ) { // Note that these selectors target direct descendants so // that the styles don't apply to any nested tab groups. > #{$header-selector}, > .mat-mdc-tab-header-pagination { // Set background color for the tab group @include token-utils.create-token-slot(background-color, background-color); } // Note: this is only scoped to primary, because the legacy tabs had the incorrect behavior // where setting both a background and `mat-accent` would add the background, but keep // accent on the selected tab. There are some internal apps whose design depends on this now // so we have to replicate it here. &.mat-primary > #{$header-selector} { // Set labels to contrast against background #{$tab-selector} .mdc-tab__text-label { @include token-utils.create-token-slot(color, foreground-color); } .mdc-tab-indicator__content--underline { @include token-utils.create-token-slot(border-color, foreground-color); } } &:not(.mat-primary) > #{$header-selector} { #{$tab-selector}:not(.mdc-tab--active) { .mdc-tab__text-label { @include token-utils.create-token-slot(color, foreground-color); } .mdc-tab-indicator__content--underline { @include token-utils.create-token-slot(border-color, foreground-color); } } } > #{$header-selector}, > .mat-mdc-tab-header-pagination { .mat-mdc-tab-header-pagination-chevron, .mat-focus-indicator::before { @include token-utils.create-token-slot(border-color, foreground-color); } .mat-ripple-element, .mdc-tab__ripple::before { @include token-utils.create-token-slot(background-color, foreground-color); } .mat-mdc-tab-header-pagination-chevron { @include token-utils.create-token-slot(color, foreground-color); } } } } }