498 lines
15 KiB
SCSS
498 lines
15 KiB
SCSS
|
|
@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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|