// // Copyright 2023 Google LLC // SPDX-License-Identifier: Apache-2.0 // // go/keep-sorted start @use 'sass:list'; @use 'sass:map'; // go/keep-sorted end // go/keep-sorted start @use '../../elevation/elevation'; @use '../../focus/focus-ring'; @use '../../internal/sass/string-ext'; @use '../../ripple/ripple'; @use '../../tokens'; // go/keep-sorted end @mixin theme($tokens) { $supported-tokens: list.join( tokens.$md-comp-tab-supported-tokens, ( 'primary-tab-container-shape-start-start', 'primary-tab-container-shape-start-end', 'primary-tab-container-shape-end-end', 'primary-tab-container-shape-end-start', 'secondary-tab-container-shape-start-start', 'secondary-tab-container-shape-start-end', 'secondary-tab-container-shape-end-end', 'secondary-tab-container-shape-end-start' ) ); @each $token, $value in $tokens { @if list.index($supported-tokens, $token) == null { @error 'Token `#{$token}` is not a supported token.'; } @if $value { --md-#{$token}: #{$value}; } } } @mixin styles() { // contains tokens for all variants and applied where needed $tokens: tokens.md-comp-tab-values(); :host { // apply primary-tokens by default $primary-prefix: 'primary-tab-'; @each $token, $value in $tokens { @if string-ext.has-prefix($token, $primary-prefix) { $token: string-ext.trim-prefix(#{$token}, $primary-prefix); --_#{$token}: var(--md-#{$primary-prefix}#{$token}, #{$value}); } } // Support logical shape properties --_container-shape-start-start: var( --md-primary-tab-container-shape-start-start, var(--_container-shape) ); --_container-shape-start-end: var( --md-primary-tab-container-shape-start-end, var(--_container-shape) ); --_container-shape-end-end: var( --md-primary-tab-container-shape-end-end, var(--_container-shape) ); --_container-shape-end-start: var( --md-primary-tab-container-shape-end-start, var(--_container-shape) ); display: inline-flex; outline: none; -webkit-tap-highlight-color: transparent; vertical-align: middle; @include ripple.theme( ( hover-color: var(--_hover-state-layer-color), hover-opacity: var(--_hover-state-layer-opacity), pressed-color: var(--_pressed-state-layer-color), pressed-opacity: var(--_pressed-state-layer-opacity), ) ); } md-focus-ring { @include focus-ring.theme( ( 'shape': 8px, ) ); } :host([selected]) md-focus-ring { margin-bottom: calc(var(--_active-indicator-height) + 1px); } .button { appearance: none; display: inline-flex; align-items: center; justify-content: center; border: none; outline: none; user-select: none; vertical-align: middle; background: none; text-decoration: none; width: 100%; position: relative; padding: 0; margin: 0; z-index: 0; // Ensure this is a stacking context so the indicator displays font: var(--_label-text-type); color: var(--_label-text-color); &::-moz-focus-inner { padding: 0; border: 0; } } .button::before { background: var(--_container-color); content: ''; inset: 0; position: absolute; z-index: -1; } .button::before, md-ripple { border-start-start-radius: var(--_container-shape-start-start); border-start-end-radius: var(--_container-shape-start-end); border-end-end-radius: var(--_container-shape-end-end); border-end-start-radius: var(--_container-shape-end-start); } .content { position: relative; box-sizing: border-box; display: inline-flex; flex-direction: column; align-items: center; justify-content: center; $_content-padding: 8px; // tabs are naturally sized up to their max height. max-height: calc(var(--_container-height) + 2 * $_content-padding); // min-height of touch target min-height: 48px; padding: $_content-padding calc(2 * $_content-padding); gap: 4px; } .content.inline-icon { flex-direction: row; } .indicator { position: absolute; box-sizing: border-box; z-index: -1; transform-origin: bottom left; background: var(--_active-indicator-color); border-radius: var(--_active-indicator-shape); height: var(--_active-indicator-height); inset: auto 0 0 0; // hidden unless the tab is selected opacity: 0; } // unselected states .button ::slotted([slot='icon']) { display: inline-flex; position: relative; writing-mode: horizontal-tb; fill: currentColor; color: var(--_icon-color); font-size: var(--_icon-size); width: var(--_icon-size); height: var(--_icon-size); } .button:hover { color: var(--_hover-label-text-color); cursor: pointer; } .button:hover ::slotted([slot='icon']) { color: var(--_hover-icon-color); } .button:focus { color: var(--_focus-label-text-color); } .button:focus ::slotted([slot='icon']) { color: var(--_focus-icon-color); } .button:active { color: var(--_pressed-label-text-color); outline: none; } .button:active ::slotted([slot='icon']) { color: var(--_pressed-icon-color); } // selected styling :host([selected]) .indicator { opacity: 1; } :host([selected]) .button { color: var(--_active-label-text-color); @include elevation.theme( ( level: var(--_container-elevation), ) ); @include ripple.theme( ( hover-color: var(--_active-hover-state-layer-color), hover-opacity: var(--_active-hover-state-layer-opacity), pressed-color: var(--_active-pressed-state-layer-color), pressed-opacity: var(--_active-pressed-state-layer-opacity), ) ); } :host([selected]) .button ::slotted([slot='icon']) { color: var(--_active-icon-color); } // selected states :host([selected]) .button:hover { color: var(--_active-hover-label-text-color); } :host([selected]) .button:hover ::slotted([slot='icon']) { color: var(--_active-hover-icon-color); } :host([selected]) .button:focus { color: var(--_active-focus-label-text-color); } :host([selected]) .button:focus ::slotted([slot='icon']) { color: var(--_active-focus-icon-color); } :host([selected]) .button:active { color: var(--_active-pressed-label-text-color); } :host([selected]) .button:active ::slotted([slot='icon']) { color: var(--_active-pressed-icon-color); } // secondary :host([variant~='secondary']) { // apply secondary-tab tokens $secondary-prefix: 'secondary-tab-'; @each $token, $value in $tokens { @if string-ext.has-prefix($token, $secondary-prefix) { $token: string-ext.trim-prefix(#{$token}, $secondary-prefix); --_#{$token}: var(--md-#{$secondary-prefix}#{$token}, #{$value}); } } // Support logical shape properties --_container-shape-start-start: var( --md-secondary-tab-container-shape-start-start, var(--_container-shape) ); --_container-shape-start-end: var( --md-secondary-tab-container-shape-start-end, var(--_container-shape) ); --_container-shape-end-end: var( --md-secondary-tab-container-shape-end-end, var(--_container-shape) ); --_container-shape-end-start: var( --md-secondary-tab-container-shape-end-start, var(--_container-shape) ); } :host([variant~='secondary']) .content { width: 100%; } :host([variant~='secondary']) .indicator { min-width: 100%; } :host, ::slotted(*) { white-space: nowrap; } @media (forced-colors: active) { :host, :host([variant]) { --_active-indicator-color: CanvasText; } } }