fix(tabs)!: split md-tab into md-primary-tab and md-secondary-tab

BREAKING CHANGE: remove `variant` attributes and change `md-tab` to `md-primary-tab`, or `md-secondary-tab` if using `variant="secondary"

PiperOrigin-RevId: 561077231
This commit is contained in:
Elizabeth Mitchell 2023-08-29 10:47:23 -07:00 committed by Copybara-Service
parent 5ba348dfd0
commit 5b13b5c05b
25 changed files with 749 additions and 556 deletions

6
all.ts
View File

@ -50,7 +50,8 @@ import './select/outlined-select.js';
import './select/select-option.js';
import './slider/slider.js';
import './switch/switch.js';
import './tabs/tab.js';
import './tabs/primary-tab.js';
import './tabs/secondary-tab.js';
import './tabs/tabs.js';
import './textfield/filled-text-field.js';
import './textfield/outlined-text-field.js';
@ -96,7 +97,8 @@ export * from './select/outlined-select.js';
export * from './select/select-option.js';
export * from './slider/slider.js';
export * from './switch/switch.js';
export * from './tabs/tab.js';
export * from './tabs/primary-tab.js';
export * from './tabs/secondary-tab.js';
export * from './tabs/tabs.js';
export * from './textfield/filled-text-field.js';
export * from './textfield/outlined-text-field.js';

View File

@ -18,7 +18,8 @@ import '@material/web/list/list.js';
import '@material/web/list/list-item.js';
import '@material/web/progress/circular-progress.js';
import '@material/web/tabs/tabs.js';
import '@material/web/tabs/tab.js';
import '@material/web/tabs/primary-tab.js';
import '@material/web/tabs/secondary-tab.js';
import '@material/web/iconbutton/icon-button.js';
import '@material/web/iconbutton/filled-icon-button.js';
import '@material/web/iconbutton/filled-tonal-icon-button.js';

View File

@ -34,7 +34,7 @@ import './progress/linear-progress.js';
import './radio/radio.js';
import './select/outlined-select.js';
import './select/select-option.js';
import './tabs/tab.js';
import './tabs/primary-tab.js';
import './tabs/tabs.js';
import './textfield/outlined-text-field.js';
// go/keep-sorted end
@ -61,7 +61,7 @@ export * from './progress/linear-progress.js';
export * from './radio/radio.js';
export * from './select/outlined-select.js';
export * from './select/select-option.js';
export * from './tabs/tab.js';
export * from './tabs/primary-tab.js';
export * from './tabs/tabs.js';
export * from './textfield/outlined-text-field.js';
// go/keep-sorted end

View File

@ -12,7 +12,11 @@ dirname: tabs
<!--*
# Document freshness: For more information, see go/fresh-source.
freshness: { owner: 'ajakubowicz' reviewed: '2023-08-01' }
freshness: {
owner: 'lizmitchell'
owner: 'ajakubowicz'
reviewed: '2023-08-25'
}
tag: 'docType:reference'
*-->
@ -44,6 +48,11 @@ related content that are at the same level of hierarchy.
* [Source code](https://github.com/material-components/material-web/tree/main/tabs)
<!-- {.external} -->
## Types
1. [Primary tabs](#primary-tabs)
1. [Secondary tabs](#secondary-tabs)
<!-- catalog-only-start -->
<!--
@ -58,44 +67,30 @@ related content that are at the same level of hierarchy.
## Usage
There are two tabs variants: `primary` (default), and `secondary`.
Tabs contain multiple primary or secondary tab children. Use the same type of
tab in a tab bar.
Tabs consist of a parent `<md-tabs>` tag, containing multiple `<md-tab>`
children.
![A primary and secondary variant tab bar stacked on each other. Primar tabs
show: "Keyboard" and "Guitar". Secondary tabs are: "Travel", "Hotel", and
"Activities".](images/tabs/usage.png "Primary and Secondary tabs")
<!-- no-catalog-start -->
<!-- TODO: add image -->
<!-- no-catalog-end -->
<!-- TODO: catalog-include "figures/<component>/usage.html" -->
```html
<md-tabs>
<md-tab>
<md-icon aria-hidden="true" slot="icon">piano</md-icon>
Keyboard
</md-tab>
<md-tab>
<md-icon aria-hidden="true" slot="icon">tune</md-icon>
Guitar
</md-tab>
<md-primary-tab>Video</md-primary-tab>
<md-primary-tab>Photos</md-primary-tab>
<md-primary-tab>Audio</md-primary-tab>
</md-tabs>
<md-tabs variant="secondary">
<md-tab inline-icon>
<md-icon aria-hidden="true" slot="icon">flight</md-icon>
Travel
</md-tab>
<md-tab inline-icon>
<md-icon aria-hidden="true" slot="icon">hotel</md-icon>
Hotel
</md-tab>
<md-tab inline-icon>
<md-icon aria-hidden="true" slot="icon">hiking</md-icon>
Activities
</md-tab>
<md-tabs>
<md-secondary-tab>Birds</md-secondary-tab>
<md-secondary-tab>Cats</md-secondary-tab>
<md-secondary-tab>Dogs</md-secondary-tab>
</md-tabs>
```
### Selection
To observe changes to tab selections, add an event listener to `<md-tabs>`,
listening for the `change` event.
@ -107,20 +102,82 @@ tabs.addEventListener('change', (event: Event) => {
});
```
### Scrollable
### Icons
When a set of tabs cannot fit on screen or container, use scrollable tabs.
Scrollable tabs can use longer text labels and a larger number of tabs. They are
best used for browsing on touch interfaces.
Tabs may optionally show an icon.
![Tabs truncated horizontally showing "Tab 1", "Tab 2", "Tab 3", and "Ta".](images/tabs/scrollable.png)
Icons communicate the type of content within a tab. Icons should be simple and
recognizable.
<!-- no-catalog-start -->
<!-- TODO: add image -->
<!-- no-catalog-end -->
<!-- TODO: catalog-include "figures/<component>/usage.html" -->
```html
<md-tabs>
<md-tab>Tab 1</md-tab>
<md-tab>Tab 2</md-tab>
<md-tab>Tab 3</md-tab>
<md-tab>Tab 4</md-tab>
<md-primary-tab>
<md-icon slot="icon">piano</md-icon>
Keyboard
</md-primary-tab>
<md-primary-tab>
<md-icon slot="icon">tune</md-icon>
Guitar
</md-primary-tab>
</md-tabs>
```
## Primary tabs
<!-- go/md-primary-tab -->
Primary tabs are placed at the top of the content pane under a top app bar. They
display the main content destinations.
<!-- no-catalog-start -->
<!-- TODO: add image -->
<!-- no-catalog-end -->
<!-- TODO: catalog-include "figures/<component>/usage.html" -->
```html
<md-tabs>
<md-primary-tab>
<md-icon slot="icon">piano</md-icon>
Keyboard
</md-primary-tab>
<md-primary-tab>
<md-icon slot="icon">tune</md-icon>
Guitar
</md-primary-tab>
</md-tabs>
```
## Secondary tabs
<!-- go/md-secondary-tab -->
Secondary tabs are used within a content area to further separate related
content and establish hierarchy.
<!-- no-catalog-start -->
<!-- TODO: add image -->
<!-- no-catalog-end -->
<!-- TODO: catalog-include "figures/<component>/usage.html" -->
```html
<md-tabs>
<md-secondary-tab inline-icon>
<md-icon slot="icon">flight</md-icon>
Travel
</md-secondary-tab>
<md-secondary-tab inline-icon>
<md-icon slot="icon">hotel</md-icon>
Hotel
</md-secondary-tab>
<md-secondary-tab inline-icon>
<md-icon slot="icon">hiking</md-icon>
Activities
</md-secondary-tab>
</md-tabs>
```
@ -131,21 +188,20 @@ best used for browsing on touch interfaces.
Tabs supports [Material theming](../theming.md) and can be customized in terms
of color, typography, and shape.
### Tokens
### Primary tab tokens
Token | Default value
----------------------------------------- | -----------------------------------
`--md-primary-tab-container-color` | `--md-sys-color-surface`
`--md-secondary-tab-container-color` | `--md-sys-color-surface`
`--md-primary-tab-label-text-type` | `500 0.875rem/1.25rem Roboto`
`--md-primary-tab-active-indicator-color` | `--md-sys-color-primary`
`--md-primary-tab-icon-color` | `--md-sys-color-on-surface-variant`
`--md-primary-tab-container-shape` | `0px`
* [All tokens](https://github.com/material-components/material-web/blob/main/tokens/_md-comp-tab.scss)
* [All tokens](https://github.com/material-components/material-web/blob/main/tokens/_md-comp-primary-tab.scss)
<!-- {.external} -->
### Example
### Primary tab example
<!-- no-catalog-start -->
@ -166,8 +222,46 @@ Token | Default value
</style>
<md-tabs>
<md-tab>Tab 1</md-tab>
<md-tab>Tab 2</md-tab>
<md-tab>Tab 3</md-tab>
<md-primary-tab>Tab 1</md-primary-tab>
<md-primary-tab>Tab 2</md-primary-tab>
<md-primary-tab>Tab 3</md-primary-tab>
</md-tabs>
```
### Secondary tab tokens
Token | Default value
------------------------------------------- | -------------
`--md-secondary-tab-container-color` | `--md-sys-color-surface`
`--md-secondary-tab-label-text-type` | `500 0.875rem/1.25rem Roboto`
`--md-secondary-tab-active-indicator-color` | `--md-sys-color-primary`
`--md-secondary-tab-icon-color` | `--md-sys-color-on-surface-variant`
`--md-secondary-tab-container-shape` | `0px`
* [All tokens](https://github.com/material-components/material-web/blob/main/tokens/_md-comp-secondary-tab.scss)
<!-- {.external} -->
### Secondary tab example
<!-- no-catalog-start -->
<!-- TODO: add image -->
<!-- no-catalog-end -->
```html
<style>
:root {
/* System tokens */
--md-sys-color-surface: #f7faf9;
--md-sys-color-primary: #005353;
/* Component tokens */
--md-secondary-tab-label-text-type: 0.8em cursive, system-ui;
}
</style>
<md-tabs>
<md-secondary-tab>Tab 1</md-secondary-tab>
<md-secondary-tab>Tab 2</md-secondary-tab>
<md-secondary-tab>Tab 3</md-secondary-tab>
</md-tabs>
```

View File

@ -3,4 +3,4 @@
// SPDX-License-Identifier: Apache-2.0
//
@forward './internal/tab' show theme;
@forward './internal/primary-tab' show theme;

6
tabs/_secondary-tab.scss Normal file
View File

@ -0,0 +1,6 @@
//
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
//
@forward './internal/secondary-tab' show theme;

View File

@ -7,6 +7,8 @@
import '@material/web/icon/icon.js';
import '@material/web/iconbutton/icon-button.js';
import '@material/web/tabs/tabs.js';
import '@material/web/tabs/primary-tab.js';
import '@material/web/tabs/secondary-tab.js';
import {MaterialStoryInit} from './material-collection.js';
import {MdTabs} from '@material/web/tabs/tabs.js';
@ -53,25 +55,24 @@ const primary: MaterialStoryInit<StoryKnobs> = {
return html`
<md-tabs
variant="primary"
.selected=${knobs.selected}
.selectOnFocus=${knobs.selectOnFocus}
>
<md-tab .inlineIcon=${inlineIcon}>
<md-primary-tab .inlineIcon=${inlineIcon}>
${tabContent('piano', 'Keyboard')}
</md-tab>
<md-tab .inlineIcon=${inlineIcon}>
</md-primary-tab>
<md-primary-tab .inlineIcon=${inlineIcon}>
${tabContent('tune', 'Guitar')}
</md-tab>
<md-tab .inlineIcon=${inlineIcon}>
</md-primary-tab>
<md-primary-tab .inlineIcon=${inlineIcon}>
${tabContent('graphic_eq', 'Drums')}
</md-tab>
<md-tab .inlineIcon=${inlineIcon}>
</md-primary-tab>
<md-primary-tab .inlineIcon=${inlineIcon}>
${tabContent('speaker', 'Bass')}
</md-tab>
<md-tab .inlineIcon=${inlineIcon}>
</md-primary-tab>
<md-primary-tab .inlineIcon=${inlineIcon}>
${tabContent('nightlife', 'Saxophone')}
</md-tab>
</md-primary-tab>
</md-tabs>`;
}
};
@ -85,22 +86,21 @@ const secondary: MaterialStoryInit<StoryKnobs> = {
return html`
<md-tabs
variant="secondary"
.selected=${knobs.selected}
.selectOnFocus=${knobs.selectOnFocus}
>
<md-tab .inlineIcon=${inlineIcon}>
<md-secondary-tab .inlineIcon=${inlineIcon}>
${tabContent('flight', 'Travel')}
</md-tab>
<md-tab .inlineIcon=${inlineIcon}>
</md-secondary-tab>
<md-secondary-tab .inlineIcon=${inlineIcon}>
${tabContent('hotel', 'Hotel')}
</md-tab>
<md-tab .inlineIcon=${inlineIcon}>
</md-secondary-tab>
<md-secondary-tab .inlineIcon=${inlineIcon}>
${tabContent('hiking', 'Activities')}
</md-tab>
<md-tab .inlineIcon=${inlineIcon}>
</md-secondary-tab>
<md-secondary-tab .inlineIcon=${inlineIcon}>
${tabContent('restaurant', 'Food')}
</md-tab>
</md-secondary-tab>
</md-tabs>`;
}
};
@ -115,26 +115,25 @@ const scrolling: MaterialStoryInit<StoryKnobs> = {
return html`
<md-tabs
class="scrolling"
variant="primary"
.selected=${knobs.selected}
.selectOnFocus=${knobs.selectOnFocus}
>
${new Array(10).fill(html`
<md-tab .inlineIcon=${inlineIcon}>
<md-primary-tab .inlineIcon=${inlineIcon}>
${tabContent('piano', 'Keyboard')}
</md-tab>
<md-tab .inlineIcon=${inlineIcon}>
</md-primary-tab>
<md-primary-tab .inlineIcon=${inlineIcon}>
${tabContent('tune', 'Guitar')}
</md-tab>
<md-tab .inlineIcon=${inlineIcon}>
</md-primary-tab>
<md-primary-tab .inlineIcon=${inlineIcon}>
${tabContent('graphic_eq', 'Drums')}
</md-tab>
<md-tab .inlineIcon=${inlineIcon}>
</md-primary-tab>
<md-primary-tab .inlineIcon=${inlineIcon}>
${tabContent('speaker', 'Bass')}
</md-tab>
<md-tab .inlineIcon=${inlineIcon}>
</md-primary-tab>
<md-primary-tab .inlineIcon=${inlineIcon}>
${tabContent('nightlife', 'Saxophone')}
</md-tab>
</md-primary-tab>
`)}
</md-tabs>`;
}
@ -177,22 +176,21 @@ const custom: MaterialStoryInit<StoryKnobs> = {
return html`
<md-tabs
class="custom"
variant="primary"
.selected=${knobs.selected}
.selectOnFocus=${knobs.selectOnFocus}
>
<md-tab .inlineIcon=${inlineIcon}>
<md-primary-tab .inlineIcon=${inlineIcon}>
${tabContent('flight', 'Travel')}
</md-tab>
<md-tab .inlineIcon=${inlineIcon}>
</md-primary-tab>
<md-primary-tab .inlineIcon=${inlineIcon}>
${tabContent('hotel', 'Hotel')}
</md-tab>
<md-tab .inlineIcon=${inlineIcon}>
</md-primary-tab>
<md-primary-tab .inlineIcon=${inlineIcon}>
${tabContent('hiking', 'Activities')}
</md-tab>
<md-tab .inlineIcon=${inlineIcon}>
</md-primary-tab>
<md-primary-tab .inlineIcon=${inlineIcon}>
${tabContent('restaurant', 'Food')}
</md-tab>
</md-primary-tab>
</md-tabs>`;
}
};
@ -229,58 +227,54 @@ const primaryAndSecondary: MaterialStoryInit<StoryKnobs> = {
return html`
<div>
<md-tabs
variant="primary"
.selected=${knobs.selected}
.selectOnFocus=${knobs.selectOnFocus}
@change=${handlePrimaryTabsChange}
>
<md-tab .inlineIcon=${inlineIcon}>
<md-primary-tab .inlineIcon=${inlineIcon}>
${tabContent('videocam', 'Movies')}
</md-tab>
<md-tab .inlineIcon=${inlineIcon}>
</md-primary-tab>
<md-primary-tab .inlineIcon=${inlineIcon}>
${tabContent('photo', 'Photos')}
</md-tab>
<md-tab .inlineIcon=${inlineIcon}>
</md-primary-tab>
<md-primary-tab .inlineIcon=${inlineIcon}>
${tabContent('audiotrack', 'Music')}
</md-tab>
</md-primary-tab>
</md-tabs>
<div>
<md-tabs
variant="secondary"
.selected=${knobs.selected}
.selectOnFocus=${knobs.selectOnFocus}
@change=${handleSecondaryTabsChange}
>
<md-tab >Star Wars</md-tab>
<md-tab>Avengers</md-tab>
<md-tab>Jaws</md-tab>
<md-tab>Frozen</md-tab>
<md-secondary-tab>Star Wars</md-secondary-tab>
<md-secondary-tab>Avengers</md-secondary-tab>
<md-secondary-tab>Jaws</md-secondary-tab>
<md-secondary-tab>Frozen</md-secondary-tab>
</md-tabs>
<div class="content"></div>
<md-tabs
hidden
variant="secondary"
.selected=${knobs.selected}
.selectOnFocus=${knobs.selectOnFocus}
@change=${handleSecondaryTabsChange}
>
<md-tab>Yosemite</md-tab>
<md-tab>Mona Lisa</md-tab>
<md-tab>Swiss Alps</md-tab>
<md-tab>Niagra Falls</md-tab>
<md-secondary-tab>Yosemite</md-secondary-tab>
<md-secondary-tab>Mona Lisa</md-secondary-tab>
<md-secondary-tab>Swiss Alps</md-secondary-tab>
<md-secondary-tab>Niagra Falls</md-secondary-tab>
</md-tabs>
<div hidden class="content"></div>
<md-tabs
hidden
variant="secondary"
.selected=${knobs.selected}
.selectOnFocus=${knobs.selectOnFocus}
@change=${handleSecondaryTabsChange}
>
<md-tab>Rock</md-tab>
<md-tab>Ambient</md-tab>
<md-tab>Soundscapes</md-tab>
<md-tab>White Noise</md-tab>
<md-secondary-tab>Rock</md-secondary-tab>
<md-secondary-tab>Ambient</md-secondary-tab>
<md-secondary-tab>Soundscapes</md-secondary-tab>
<md-secondary-tab>White Noise</md-secondary-tab>
</md-tabs>
<div hidden class="content"></div>
</div>
@ -303,7 +297,7 @@ const dynamic: MaterialStoryInit<StoryKnobs> = {
function addTab(event: Event) {
const tabs = getTabs(event);
const count = tabs.childElementCount;
const tab = document.createElement('md-tab');
const tab = document.createElement('md-primary-tab');
tab.textContent = `Tab ${count + 1}`;
if (tabs.selectedItem !== undefined) {
tabs.selectedItem.after(tab);
@ -353,19 +347,18 @@ const dynamic: MaterialStoryInit<StoryKnobs> = {
</div>
<md-tabs
class="scrolling"
variant="primary"
.selected=${knobs.selected}
.selectOnFocus=${knobs.selectOnFocus}
>
<md-tab .inlineIcon=${inlineIcon}>
<md-primary-tab .inlineIcon=${inlineIcon}>
Tab 1
</md-tab>
<md-tab .inlineIcon=${inlineIcon}>
</md-primary-tab>
<md-primary-tab .inlineIcon=${inlineIcon}>
Tab 2
</md-tab>
<md-tab .inlineIcon=${inlineIcon}>
</md-primary-tab>
<md-primary-tab .inlineIcon=${inlineIcon}>
Tab 3
</md-tab>
</md-primary-tab>
</md-tabs>`;
}
};

View File

@ -0,0 +1,61 @@
//
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
//
// go/keep-sorted start
@use 'sass:list';
// go/keep-sorted end
// go/keep-sorted start
@use '../../tokens';
// go/keep-sorted end
@mixin theme($tokens) {
$supported-tokens: list.join(
tokens.$md-comp-primary-tab-supported-tokens,
(
'container-shape-start-start',
'container-shape-start-end',
'container-shape-end-end',
'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-primary-tab-#{$token}: #{$value};
}
}
}
@mixin styles() {
$tokens: tokens.md-comp-primary-tab-values();
:host {
@each $token, $value in $tokens {
--_#{$token}: var(--md-primary-tab-#{$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)
);
}
}

View File

@ -0,0 +1,69 @@
//
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
//
// go/keep-sorted start
@use 'sass:list';
// go/keep-sorted end
// go/keep-sorted start
@use '../../tokens';
// go/keep-sorted end
@mixin theme($tokens) {
$supported-tokens: list.join(
tokens.$md-comp-secondary-tab-supported-tokens,
(
'container-shape-start-start',
'container-shape-start-end',
'container-shape-end-end',
'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-secondary-tab-#{$token}: #{$value};
}
}
}
@mixin styles() {
$tokens: tokens.md-comp-secondary-tab-values();
:host {
@each $token, $value in $tokens {
--_#{$token}: var(--md-secondary-tab-#{$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)
);
}
.content {
width: 100%;
}
.indicator {
min-width: 100%;
}
}

View File

@ -15,64 +15,8 @@
@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;
@ -264,53 +208,14 @@
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;
.indicator {
background: CanvasText;
}
}
}

View File

@ -0,0 +1,10 @@
//
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
//
// go/keep-sorted start
@use './primary-tab';
// go/keep-sorted end
@include primary-tab.styles;

View File

@ -0,0 +1,12 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {Tab} from './tab.js';
/**
* A primary tab component.
*/
export class PrimaryTab extends Tab {}

View File

@ -0,0 +1,10 @@
//
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
//
// go/keep-sorted start
@use './secondary-tab';
// go/keep-sorted end
@include secondary-tab.styles;

View File

@ -0,0 +1,12 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {Tab} from './tab.js';
/**
* A secondary tab component.
*/
export class SecondaryTab extends Tab {}

View File

@ -26,11 +26,6 @@ export interface Tabs extends HTMLElement {
previousSelectedItem?: Tab;
}
/**
* Tab variant can be `primary` or `secondary`.
*/
export type TabVariant = 'primary'|'secondary';
/**
* Tab component.
*/
@ -43,10 +38,8 @@ export class Tab extends LitElement {
static override shadowRootOptions:
ShadowRootInit = {mode: 'open', delegatesFocus: true};
/**
* Styling variant to display, 'primary' (default) or 'secondary'.
*/
@property({reflect: true}) variant: TabVariant = 'primary';
/** @private indicates that the element is a tab for `<md-tabs>` */
static readonly isTab = true;
/**
* Whether or not the tab is `selected`.

View File

@ -9,7 +9,7 @@ import '../../divider/divider.js';
import {html, isServer, LitElement, PropertyValues} from 'lit';
import {property, queryAssignedElements, state} from 'lit/decorators.js';
import {Tab, TabVariant} from './tab.js';
import {Tab} from './tab.js';
const NAVIGATION_KEYS = new Map([
['default', new Set(['Home', 'End'])],
@ -47,11 +47,6 @@ export class Tabs extends LitElement {
delegatesFocus: true
};
/**
* Styling variant to display, 'primary' (default) or 'secondary'.
*/
@property({reflect: true}) variant: TabVariant = 'primary';
/**
* Index of the selected item.
*/
@ -66,8 +61,11 @@ export class Tabs extends LitElement {
private previousSelected = -1;
private readonly scrollMargin = 48;
@queryAssignedElements({selector: 'md-tab', flatten: true})
private readonly items!: Tab[];
@queryAssignedElements({flatten: true})
private readonly maybeTabItems!: HTMLElement[];
private get items(): Tab[] {
return this.maybeTabItems.filter(isTab);
}
// this tracks if items have changed, which triggers rendering so they can
// be kept in sync
@ -211,16 +209,14 @@ export class Tabs extends LitElement {
}
protected override async updated(changed: PropertyValues) {
const itemsOrVariantChanged =
changed.has('itemsDirty') || changed.has('variant');
const itemsChanged = changed.has('itemsDirty');
// sync state with items.
if (itemsOrVariantChanged) {
if (itemsChanged) {
this.items.forEach((item, i) => {
item.selected = this.selected === i;
item.variant = this.variant;
});
}
if (itemsOrVariantChanged || changed.has('selected')) {
if (itemsChanged || changed.has('selected')) {
if (this.previousSelectedItem !== this.selectedItem) {
this.previousSelectedItem?.removeAttribute(this.selectedAttribute);
this.selectedItem?.setAttribute(this.selectedAttribute, '');
@ -242,7 +238,7 @@ export class Tabs extends LitElement {
return html`
<div class="tabs">
<slot @slotchange=${this.handleSlotChange} @click=${
this.handleItemClick}></slot>
this.handleItemClick}></slot>
</div>
<md-divider part="divider"></md-divider>
`;
@ -296,3 +292,7 @@ export class Tabs extends LitElement {
this.scrollTo({behavior, top: 0, left: to});
}
}
function isTab(element: HTMLElement): element is Tab {
return 'isTab' in element.constructor && element.constructor.isTab === true;
}

27
tabs/primary-tab.ts Normal file
View File

@ -0,0 +1,27 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {customElement} from 'lit/decorators.js';
import {PrimaryTab} from './internal/primary-tab.js';
import {styles as primaryStyles} from './internal/primary-tab-styles.css.js';
import {styles as sharedStyles} from './internal/tab-styles.css.js';
declare global {
interface HTMLElementTagNameMap {
'md-primary-tab': MdPrimaryTab;
}
}
// TODO(b/267336507): add docs
/**
* @summary Tab allow users to display a tab within a Tabs.
*
*/
@customElement('md-primary-tab')
export class MdPrimaryTab extends PrimaryTab {
static override styles = [sharedStyles, primaryStyles];
}

27
tabs/secondary-tab.ts Normal file
View File

@ -0,0 +1,27 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {customElement} from 'lit/decorators.js';
import {SecondaryTab} from './internal/secondary-tab.js';
import {styles as secondaryStyles} from './internal/secondary-tab-styles.css.js';
import {styles as sharedStyles} from './internal/tab-styles.css.js';
declare global {
interface HTMLElementTagNameMap {
'md-secondary-tab': MdSecondaryTab;
}
}
// TODO(b/267336507): add docs
/**
* @summary Tab allow users to display a tab within a Tabs.
*
*/
@customElement('md-secondary-tab')
export class MdSecondaryTab extends SecondaryTab {
static override styles = [sharedStyles, secondaryStyles];
}

View File

@ -1,28 +0,0 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {customElement} from 'lit/decorators.js';
import {Tab} from './internal/tab.js';
import {styles} from './internal/tab-styles.css.js';
export {TabVariant} from './internal/tab.js';
declare global {
interface HTMLElementTagNameMap {
'md-tab': MdTab;
}
}
// TODO(b/267336507): add docs
/**
* @summary Tab allow users to display a tab within a Tabs.
*
*/
@customElement('md-tab')
export class MdTab extends Tab {
static override styles = [styles];
}

View File

@ -4,15 +4,11 @@
* SPDX-License-Identifier: Apache-2.0
*/
import './tab.js';
import {customElement} from 'lit/decorators.js';
import {Tabs} from './internal/tabs.js';
import {styles} from './internal/tabs-styles.css.js';
export {MdTab, TabVariant} from './tab.js';
declare global {
interface HTMLElementTagNameMap {
'md-tabs': MdTabs;

View File

@ -10,7 +10,8 @@ import {Environment} from '../testing/environment.js';
import {createTokenTests} from '../testing/tokens.js';
import {TabsHarness} from './harness.js';
import {MdTab} from './tab.js';
import {MdPrimaryTab} from './primary-tab.js';
import {MdSecondaryTab} from './secondary-tab.js';
import {MdTabs} from './tabs.js';
interface TabsTestProps {
@ -22,17 +23,16 @@ function getTabsTemplate(props?: TabsTestProps) {
<md-tabs
.selected=${props?.selected ?? 0}
>
<md-tab>A</md-tab>
<md-tab>B</md-tab>
<md-tab>C</md-tab>
<md-primary-tab>A</md-primary-tab>
<md-primary-tab>B</md-primary-tab>
<md-primary-tab>C</md-primary-tab>
</md-tabs>`;
}
describe('<md-tabs>', () => {
const env = new Environment();
async function setupTest(
props?: TabsTestProps, template = getTabsTemplate) {
async function setupTest(props?: TabsTestProps, template = getTabsTemplate) {
const root = env.render(template(props));
await env.waitForStability();
const tab = root.querySelector<MdTabs>('md-tabs')!;
@ -42,7 +42,8 @@ describe('<md-tabs>', () => {
describe('.styles', () => {
createTokenTests(MdTabs.styles);
createTokenTests(MdTab.styles);
createTokenTests(MdPrimaryTab.styles);
createTokenTests(MdSecondaryTab.styles);
});
describe('properties', () => {
@ -85,7 +86,7 @@ describe('<md-tabs>', () => {
it('maintains selection when tabs are mutated', async () => {
const {harness} = await setupTest({selected: 1});
expect(harness.element.selectedItem.textContent).toBe('B');
const tab = document.createElement('md-tab');
const tab = document.createElement('md-primary-tab');
tab.textContent = 'tab';
// add before selected
harness.element.prepend(tab);
@ -105,4 +106,4 @@ describe('<md-tabs>', () => {
expect(harness.element.selectedItem.textContent).toBe('B');
});
});
});
});

View File

@ -42,12 +42,13 @@
md-comp-outlined-segmented-button-*;
@forward './md-comp-outlined-select' as md-comp-outlined-select-*;
@forward './md-comp-outlined-text-field' as md-comp-outlined-text-field-*;
@forward './md-comp-primary-tab' as md-comp-primary-tab-*;
@forward './md-comp-radio' as md-comp-radio-*;
@forward './md-comp-ripple' as md-comp-ripple-*;
@forward './md-comp-secondary-tab' as md-comp-secondary-tab-*;
@forward './md-comp-slider' as md-comp-slider-*;
@forward './md-comp-suggestion-chip' as md-comp-suggestion-chip-*;
@forward './md-comp-switch' as md-comp-switch-*;
@forward './md-comp-tab' as md-comp-tab-*;
@forward './md-comp-test-table' as md-comp-test-table-*;
@forward './md-comp-text-button' as md-comp-text-button-*;
@forward './md-ref-palette' as md-ref-palette-*;

View File

@ -0,0 +1,134 @@
//
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
//
// go/keep-sorted start
@use 'sass:map';
// go/keep-sorted end
// go/keep-sorted start
@use './md-sys-color';
@use './md-sys-elevation';
@use './md-sys-shape';
@use './md-sys-state';
@use './md-sys-typescale';
@use './v0_172/md-comp-primary-navigation-tab';
@use './values';
// go/keep-sorted end
$_default: (
'md-sys-color': md-sys-color.values-light(),
'md-sys-elevation': md-sys-elevation.values(),
'md-sys-shape': md-sys-shape.values(),
'md-sys-state': md-sys-state.values(),
'md-sys-typescale': md-sys-typescale.values(),
);
$supported-tokens: (
// go/keep-sorted start
'active-focus-icon-color',
'active-focus-label-text-color',
'active-hover-icon-color',
'active-hover-label-text-color',
'active-hover-state-layer-color',
'active-hover-state-layer-opacity',
'active-icon-color',
'active-indicator-color',
'active-indicator-height',
'active-indicator-shape',
'active-label-text-color',
'active-pressed-icon-color',
'active-pressed-label-text-color',
'active-pressed-state-layer-color',
'active-pressed-state-layer-opacity',
'container-color',
'container-elevation',
'container-height',
'container-shape',
'focus-icon-color',
'focus-label-text-color',
'hover-icon-color',
'hover-label-text-color',
'hover-state-layer-color',
'hover-state-layer-opacity',
'icon-color',
'icon-size',
'label-text-color',
'label-text-type',
'pressed-icon-color',
'pressed-label-text-color',
'pressed-state-layer-color',
'pressed-state-layer-opacity',
// go/keep-sorted end
);
$unsupported-tokens: (
// include an icon and the size will adjust;
// height is 48 and it's 64 with icon
'with-icon-and-label-text-container-height',
'with-label-text-label-text-font',
'with-label-text-label-text-line-height',
'with-label-text-label-text-size',
'with-label-text-label-text-tracking',
'with-label-text-label-text-weight',
'active-focus-state-layer-color',
'active-focus-state-layer-opacity',
'focus-state-layer-color',
'focus-state-layer-opacity'
);
@function values($deps: $_default, $exclude-hardcoded-values: false) {
$tokens: md-comp-primary-navigation-tab.values(
$deps,
$exclude-hardcoded-values: $exclude-hardcoded-values
);
// TODO(b/271876162): remove when tokens compiler emits typescale tokens
$tokens: map.merge(
$tokens,
(
'label-text-type': map.get($deps, 'md-sys-typescale', 'title-small'),
)
);
@return values.validate(
$tokens,
$supported-tokens: $supported-tokens,
$unsupported-tokens: $unsupported-tokens,
$renamed-tokens: (
// rename inactive-
'inactive-focus-state-layer-color': 'focus-state-layer-color',
'inactive-focus-state-layer-opacity': 'focus-state-layer-opacity',
'inactive-hover-state-layer-color': 'hover-state-layer-color',
'inactive-hover-state-layer-opacity': 'hover-state-layer-opacity',
'inactive-pressed-state-layer-color': 'pressed-state-layer-color',
'inactive-pressed-state-layer-opacity': 'pressed-state-layer-opacity',
// rename with-icon- and inactive-
'with-icon-active-focus-icon-color': 'active-focus-icon-color',
'with-icon-active-hover-icon-color': 'active-hover-icon-color',
'with-icon-active-icon-color': 'active-icon-color',
'with-icon-active-pressed-icon-color': 'active-pressed-icon-color',
'with-icon-icon-size': 'icon-size',
'with-icon-inactive-focus-icon-color': 'focus-icon-color',
'with-icon-inactive-hover-icon-color': 'hover-icon-color',
'with-icon-inactive-icon-color': 'icon-color',
'with-icon-inactive-pressed-icon-color': 'pressed-icon-color',
// rename with-label-text- and inactive-
'with-label-text-active-focus-label-text-color':
'active-focus-label-text-color',
'with-label-text-active-hover-label-text-color':
'active-hover-label-text-color',
'with-label-text-active-label-text-color': 'active-label-text-color',
'with-label-text-active-pressed-label-text-color':
'active-pressed-label-text-color',
'with-label-text-inactive-focus-label-text-color':
'focus-label-text-color',
'with-label-text-inactive-hover-label-text-color':
'hover-label-text-color',
'with-label-text-inactive-label-text-color': 'label-text-color',
'with-label-text-inactive-pressed-label-text-color':
'pressed-label-text-color',
'with-label-text-label-text-type': 'label-text-type'
)
);
}

View File

@ -0,0 +1,139 @@
//
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
//
// go/keep-sorted start
@use 'sass:map';
// go/keep-sorted end
// go/keep-sorted start
@use './md-sys-color';
@use './md-sys-elevation';
@use './md-sys-shape';
@use './md-sys-state';
@use './md-sys-typescale';
@use './v0_172/md-comp-secondary-navigation-tab';
@use './values';
// go/keep-sorted end
$_default: (
'md-sys-color': md-sys-color.values-light(),
'md-sys-elevation': md-sys-elevation.values(),
'md-sys-shape': md-sys-shape.values(),
'md-sys-state': md-sys-state.values(),
'md-sys-typescale': md-sys-typescale.values(),
);
$supported-tokens: (
// go/keep-sorted start
'active-focus-icon-color',
'active-focus-label-text-color',
'active-hover-icon-color',
'active-hover-label-text-color',
'active-hover-state-layer-color',
'active-hover-state-layer-opacity',
'active-icon-color',
'active-indicator-color',
'active-indicator-height',
'active-indicator-shape',
'active-label-text-color',
'active-pressed-icon-color',
'active-pressed-label-text-color',
'active-pressed-state-layer-color',
'active-pressed-state-layer-opacity',
'container-color',
'container-elevation',
'container-height',
'container-shape',
'focus-icon-color',
'focus-label-text-color',
'hover-icon-color',
'hover-label-text-color',
'hover-state-layer-color',
'hover-state-layer-opacity',
'icon-color',
'icon-size',
'label-text-color',
'label-text-type',
'pressed-icon-color',
'pressed-label-text-color',
'pressed-state-layer-color',
'pressed-state-layer-opacity',
// go/keep-sorted end
);
$unsupported-tokens: (
// include an icon and the size will adjust;
// height is 48 and it's 64 with icon
'container-shadow-color',
'label-text-font',
'label-text-line-height',
'label-text-size',
'label-text-tracking',
'label-text-weight',
'focus-state-layer-color',
'focus-state-layer-opacity'
);
@function values($deps: $_default, $exclude-hardcoded-values: false) {
$tokens: md-comp-secondary-navigation-tab.values(
$deps,
$exclude-hardcoded-values: $exclude-hardcoded-values
);
// TODO(b/271876162): remove when tokens compiler emits typescale tokens
$tokens: map.merge(
$tokens,
(
'label-text-type': map.get($deps, 'md-sys-typescale', 'title-small'),
)
);
$tokens: _add-missing-secondary-tokens($tokens);
$tokens: values.validate(
$tokens,
$supported-tokens: $supported-tokens,
$unsupported-tokens: $unsupported-tokens,
$renamed-tokens: (
'inactive-label-text-color': 'label-text-color',
'with-icon-active-icon-color': 'active-icon-color',
'with-icon-focus-icon-color': 'focus-icon-color',
'with-icon-hover-icon-color': 'hover-icon-color',
'with-icon-icon-size': 'icon-size',
'with-icon-inactive-icon-color': 'icon-color',
'with-icon-pressed-icon-color': 'pressed-icon-color',
)
);
@return $tokens;
}
// add missing secondary tokens to match primary variant.
@function _add-missing-secondary-tokens($tokens) {
$tokens: map.merge(
$tokens,
(
'active-focus-icon-color': map.get($tokens, 'icon-color'),
'active-focus-label-text-color':
map.get($tokens, 'active-label-text-color'),
'active-hover-icon-color': map.get($tokens, 'icon-color'),
'active-hover-label-text-color':
map.get($tokens, 'active-label-text-color'),
'active-hover-state-layer-color':
map.get($tokens, 'hover-state-layer-color'),
'active-hover-state-layer-opacity':
map.get($tokens, 'hover-state-layer-opacity'),
'active-icon-color': map.get($tokens, 'icon-color'),
'active-indicator-shape': 0,
'active-pressed-icon-color': map.get($tokens, 'icon-color'),
'active-pressed-label-text-color':
map.get($tokens, 'active-label-text-color'),
'active-pressed-state-layer-color':
map.get($tokens, 'pressed-state-layer-color'),
'active-pressed-state-layer-opacity':
map.get($tokens, 'pressed-state-layer-opacity'),
)
);
@return $tokens;
}

View File

@ -1,272 +0,0 @@
//
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
//
// go/keep-sorted start
@use 'sass:map';
// go/keep-sorted end
// go/keep-sorted start
@use './md-sys-color';
@use './md-sys-elevation';
@use './md-sys-shape';
@use './md-sys-state';
@use './md-sys-typescale';
@use './v0_172/md-comp-primary-navigation-tab';
@use './v0_172/md-comp-secondary-navigation-tab';
@use './values';
// go/keep-sorted end
$_default: (
'md-sys-color': md-sys-color.values-light(),
'md-sys-elevation': md-sys-elevation.values(),
'md-sys-shape': md-sys-shape.values(),
'md-sys-state': md-sys-state.values(),
'md-sys-typescale': md-sys-typescale.values(),
);
$supported-tokens: (
// go/keep-sorted start
'primary-tab-active-focus-icon-color',
'primary-tab-active-focus-label-text-color',
'primary-tab-active-hover-icon-color',
'primary-tab-active-hover-label-text-color',
'primary-tab-active-hover-state-layer-color',
'primary-tab-active-hover-state-layer-opacity',
'primary-tab-active-icon-color',
'primary-tab-active-indicator-color',
'primary-tab-active-indicator-height',
'primary-tab-active-indicator-shape',
'primary-tab-active-label-text-color',
'primary-tab-active-pressed-icon-color',
'primary-tab-active-pressed-label-text-color',
'primary-tab-active-pressed-state-layer-color',
'primary-tab-active-pressed-state-layer-opacity',
'primary-tab-container-color',
'primary-tab-container-elevation',
'primary-tab-container-height',
'primary-tab-container-shape',
'primary-tab-focus-icon-color',
'primary-tab-focus-label-text-color',
'primary-tab-hover-icon-color',
'primary-tab-hover-label-text-color',
'primary-tab-hover-state-layer-color',
'primary-tab-hover-state-layer-opacity',
'primary-tab-icon-color',
'primary-tab-icon-size',
'primary-tab-label-text-color',
'primary-tab-label-text-type',
'primary-tab-pressed-icon-color',
'primary-tab-pressed-label-text-color',
'primary-tab-pressed-state-layer-color',
'primary-tab-pressed-state-layer-opacity',
'secondary-tab-active-focus-icon-color',
'secondary-tab-active-focus-label-text-color',
'secondary-tab-active-hover-icon-color',
'secondary-tab-active-hover-label-text-color',
'secondary-tab-active-hover-state-layer-color',
'secondary-tab-active-hover-state-layer-opacity',
'secondary-tab-active-icon-color',
'secondary-tab-active-indicator-color',
'secondary-tab-active-indicator-height',
'secondary-tab-active-indicator-shape',
'secondary-tab-active-label-text-color',
'secondary-tab-active-pressed-icon-color',
'secondary-tab-active-pressed-label-text-color',
'secondary-tab-active-pressed-state-layer-color',
'secondary-tab-active-pressed-state-layer-opacity',
'secondary-tab-container-color',
'secondary-tab-container-elevation',
'secondary-tab-container-height',
'secondary-tab-container-shape',
'secondary-tab-focus-icon-color',
'secondary-tab-focus-label-text-color',
'secondary-tab-hover-icon-color',
'secondary-tab-hover-label-text-color',
'secondary-tab-hover-state-layer-color',
'secondary-tab-hover-state-layer-opacity',
'secondary-tab-icon-color',
'secondary-tab-icon-size',
'secondary-tab-label-text-color',
'secondary-tab-label-text-type',
'secondary-tab-pressed-icon-color',
'secondary-tab-pressed-label-text-color',
'secondary-tab-pressed-state-layer-color',
'secondary-tab-pressed-state-layer-opacity',
// go/keep-sorted end
);
$unsupported-tokens: (
// include an icon and the size will adjust;
// height is 48 and it's 64 with icon
'primary-tab-with-icon-and-label-text-container-height',
'primary-tab-with-label-text-label-text-font',
'primary-tab-with-label-text-label-text-line-height',
'primary-tab-with-label-text-label-text-size',
'primary-tab-with-label-text-label-text-tracking',
'primary-tab-with-label-text-label-text-weight',
'secondary-tab-container-shadow-color',
'secondary-tab-label-text-font',
'secondary-tab-label-text-line-height',
'secondary-tab-label-text-size',
'secondary-tab-label-text-tracking',
'secondary-tab-label-text-weight',
'primary-tab-active-focus-state-layer-color',
'primary-tab-active-focus-state-layer-opacity',
'primary-tab-focus-state-layer-color',
'primary-tab-focus-state-layer-opacity',
'secondary-tab-focus-state-layer-color',
'secondary-tab-focus-state-layer-opacity'
);
// Note, this combines the raw primary and secondary tab variant tokens
// into a single set prefixed with `primary-tab` or `secondary-tab`.
// Tokens are normalized between the variants, added or removed and renamed
// as needed.
@function values($deps: $_default, $exclude-hardcoded-values: false) {
// prepare token values by normalizing and combinding primary/secondary
// generated tokens *before* fixing up names and limiting to supported tokens.
// 1. for primary
// a. prefix with `primary-tab`
// 2. for secondary
// a. add missing secondary tokens to match primary
// b. prefix with `secondary-tab`
$primary-tokens: md-comp-primary-navigation-tab.values(
$deps,
$exclude-hardcoded-values
);
$primary-tokens: _add-missing-tokens($primary-tokens, $deps);
$primary-tokens: _prefix-tokens($primary-tokens, 'primary-tab');
$secondary-tokens: md-comp-secondary-navigation-tab.values(
$deps,
$exclude-hardcoded-values
);
$secondary-tokens: _add-missing-tokens($secondary-tokens, $deps);
$secondary-tokens: _add-missing-secondary-tokens($secondary-tokens);
$secondary-tokens: _prefix-tokens($secondary-tokens, 'secondary-tab');
$base-tokens: map.merge($primary-tokens, $secondary-tokens);
// now refine the normalized generated tokens to only renamed/supported tokens.
$tokens: values.validate(
$base-tokens,
$supported-tokens: $supported-tokens,
$unsupported-tokens: $unsupported-tokens,
$renamed-tokens: (
// rename primary inactive-
'primary-tab-inactive-focus-state-layer-color':
'primary-tab-focus-state-layer-color',
'primary-tab-inactive-focus-state-layer-opacity':
'primary-tab-focus-state-layer-opacity',
'primary-tab-inactive-hover-state-layer-color':
'primary-tab-hover-state-layer-color',
'primary-tab-inactive-hover-state-layer-opacity':
'primary-tab-hover-state-layer-opacity',
'primary-tab-inactive-pressed-state-layer-color':
'primary-tab-pressed-state-layer-color',
'primary-tab-inactive-pressed-state-layer-opacity':
'primary-tab-pressed-state-layer-opacity',
// rename primary with-icon- and inactive-
'primary-tab-with-icon-active-focus-icon-color':
'primary-tab-active-focus-icon-color',
'primary-tab-with-icon-active-hover-icon-color':
'primary-tab-active-hover-icon-color',
'primary-tab-with-icon-active-icon-color': 'primary-tab-active-icon-color',
'primary-tab-with-icon-active-pressed-icon-color':
'primary-tab-active-pressed-icon-color',
'primary-tab-with-icon-icon-size': 'primary-tab-icon-size',
'primary-tab-with-icon-inactive-focus-icon-color':
'primary-tab-focus-icon-color',
'primary-tab-with-icon-inactive-hover-icon-color':
'primary-tab-hover-icon-color',
'primary-tab-with-icon-inactive-icon-color': 'primary-tab-icon-color',
'primary-tab-with-icon-inactive-pressed-icon-color':
'primary-tab-pressed-icon-color',
// rename primary with-label-text- and inactive-
'primary-tab-with-label-text-active-focus-label-text-color':
'primary-tab-active-focus-label-text-color',
'primary-tab-with-label-text-active-hover-label-text-color':
'primary-tab-active-hover-label-text-color',
'primary-tab-with-label-text-active-label-text-color':
'primary-tab-active-label-text-color',
'primary-tab-with-label-text-active-pressed-label-text-color':
'primary-tab-active-pressed-label-text-color',
'primary-tab-with-label-text-inactive-focus-label-text-color':
'primary-tab-focus-label-text-color',
'primary-tab-with-label-text-inactive-hover-label-text-color':
'primary-tab-hover-label-text-color',
'primary-tab-with-label-text-inactive-label-text-color':
'primary-tab-label-text-color',
'primary-tab-with-label-text-inactive-pressed-label-text-color':
'primary-tab-pressed-label-text-color',
'primary-tab-with-label-text-label-text-type':
'primary-tab-label-text-type',
// rename secondary with-icon- and inactive-
'secondary-tab-inactive-label-text-color':
'secondary-tab-label-text-color',
'secondary-tab-with-icon-active-icon-color':
'secondary-tab-active-icon-color',
'secondary-tab-with-icon-focus-icon-color':
'secondary-tab-focus-icon-color',
'secondary-tab-with-icon-hover-icon-color':
'secondary-tab-hover-icon-color',
'secondary-tab-with-icon-icon-size': 'secondary-tab-icon-size',
'secondary-tab-with-icon-inactive-icon-color': 'secondary-tab-icon-color',
'secondary-tab-with-icon-pressed-icon-color':
'secondary-tab-pressed-icon-color'
)
);
@return $tokens;
}
@function _prefix-tokens($tokens, $prefix: '') {
@each $key, $value in $tokens {
$tokens: map.remove($tokens, $key);
$key: '#{$prefix}-#{$key}';
$tokens: map.set($tokens, $key, $value);
}
@return $tokens;
}
// add tokens for label-text
@function _add-missing-tokens($tokens, $deps) {
// TODO(b/271876162): remove when tokens compiler emits typescale tokens
$tokens: map.merge(
$tokens,
(
'label-text-type': map.get($deps, 'md-sys-typescale', 'title-small'),
)
);
@return $tokens;
}
// add missing secondary tokens to match primary variant.
@function _add-missing-secondary-tokens($tokens) {
$tokens: map.merge(
$tokens,
(
'active-focus-icon-color': map.get($tokens, 'icon-color'),
'active-focus-label-text-color':
map.get($tokens, 'active-label-text-color'),
'active-hover-icon-color': map.get($tokens, 'icon-color'),
'active-hover-label-text-color':
map.get($tokens, 'active-label-text-color'),
'active-hover-state-layer-color':
map.get($tokens, 'hover-state-layer-color'),
'active-hover-state-layer-opacity':
map.get($tokens, 'hover-state-layer-opacity'),
'active-icon-color': map.get($tokens, 'icon-color'),
'active-indicator-shape': 0,
'active-pressed-icon-color': map.get($tokens, 'icon-color'),
'active-pressed-label-text-color':
map.get($tokens, 'active-label-text-color'),
'active-pressed-state-layer-color':
map.get($tokens, 'pressed-state-layer-color'),
'active-pressed-state-layer-opacity':
map.get($tokens, 'pressed-state-layer-opacity'),
)
);
@return $tokens;
}