Skip to content

Commit

Permalink
feat(chips): add filter chips
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 524428594
  • Loading branch information
asyncLiz authored and copybara-github committed Apr 15, 2023
1 parent 2eb914e commit ae91366
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 19 deletions.
6 changes: 6 additions & 0 deletions chips/_filter-chip.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
//

@forward './lib/filter-chip' show theme;
28 changes: 28 additions & 0 deletions chips/filter-chip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {customElement} from 'lit/decorators.js';

import {FilterChip} from './lib/filter-chip.js';
import {styles} from './lib/filter-styles.css.js';
import {styles as sharedStyles} from './lib/shared-styles.css.js';

declare global {
interface HTMLElementTagNameMap {
'md-filter-chip': MdFilterChip;
}
}

/**
* TODO(b/243982145): add docs
*
* @final
* @suppress {visibility}
*/
@customElement('md-filter-chip')
export class MdFilterChip extends FilterChip {
static override styles = [sharedStyles, styles];
}
17 changes: 17 additions & 0 deletions chips/filter-chip_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

// import 'jasmine'; (google3-only)

import {createTokenTests} from '../testing/tokens.js';

import {MdFilterChip} from './filter-chip.js';

describe('<md-filter-chip>', () => {
describe('.styles', () => {
createTokenTests(MdFilterChip.styles);
});
});
141 changes: 141 additions & 0 deletions chips/lib/_filter-chip.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
//

// go/keep-sorted start
@use '../../ripple/ripple';
@use '../../sass/theme';
@use '../../tokens';
// go/keep-sorted end

@mixin theme($tokens) {
$tokens: theme.validate-theme(tokens.md-comp-filter-chip-values(), $tokens);

@each $token, $value in $tokens {
--md-filter-chip-#{$token}: #{$value};
}
}

@mixin styles() {
$tokens: tokens.md-comp-filter-chip-values();

:host {
@each $token, $value in $tokens {
--_#{$token}: #{$value};
}
}

.selected {
@include ripple.theme(
(
focus-color: var(--_selected-focus-state-layer-color),
focus-opacity: var(--_selected-focus-state-layer-opacity),
hover-color: var(--_selected-hover-state-layer-color),
hover-opacity: var(--_selected-hover-state-layer-opacity),
pressed-color: var(--_selected-pressed-state-layer-color),
pressed-opacity: var(--_selected-pressed-state-layer-opacity),
)
);
}

.selected .icon.leading {
width: var(--_icon-size);
}

.checkmark {
inset: 0;
opacity: 0;
position: absolute;
}

.selected .checkmark {
opacity: 1;
}

.selected::before {
background: var(--_selected-container-color);
}

.selected .outline {
border-width: var(--_selected-outline-width);
}

.selected.elevated::before {
background: var(--_elevated-selected-container-color);
}

.selected.disabled::before {
background: var(--_disabled-selected-container-color);
opacity: var(--_disabled-selected-container-opacity);
}

.selected .label {
color: var(--_selected-label-text-color);
}

.selected:hover .label {
color: var(--_selected-hover-label-text-color);
}

.selected:focus .label {
color: var(--_selected-focus-label-text-color);
}

.selected:active .label {
color: var(--_selected-pressed-label-text-color);
}

.selected .icon.leading {
color: var(--_selected-leading-icon-color);
}

.selected:hover .icon.leading {
color: var(--_selected-hover-leading-icon-color);
}

.selected:focus .icon.leading {
color: var(--_selected-focus-leading-icon-color);
}

.selected:active .icon.leading {
color: var(--_selected-pressed-leading-icon-color);
}

.icon.trailing {
color: var(--_trailing-icon-color);
}

:hover .icon.trailing {
color: var(--_hover-trailing-icon-color);
}

:focus .icon.trailing {
color: var(--_focus-trailing-icon-color);
}

:active .icon.trailing {
color: var(--_pressed-trailing-icon-color);
}

.disabled .icon.trailing {
color: var(--_disabled-trailing-icon-color);
opacity: var(--_disabled-trailing-icon-opacity);
}

.selected .icon.trailing {
color: var(--_selected-trailing-icon-color);
}

.selected:hover .icon.trailing {
color: var(--_selected-hover-trailing-icon-color);
}

.selected:focus .icon.trailing {
color: var(--_selected-focus-trailing-icon-color);
}

.selected:active .icon.trailing {
color: var(--_selected-pressed-trailing-icon-color);
}
}
42 changes: 23 additions & 19 deletions chips/lib/chip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import '../../ripple/ripple.js';
import {html, LitElement, nothing, TemplateResult} from 'lit';
import {property, queryAsync, state} from 'lit/decorators.js';
import {classMap} from 'lit/directives/class-map.js';
import {when} from 'lit/directives/when.js';
import {html as staticHtml, literal} from 'lit/static-html.js';

import {pointerPress, shouldShowStrongFocus} from '../../focus/strong-focus.js';
Expand All @@ -33,50 +32,55 @@ export class Chip extends LitElement {
@queryAsync('md-ripple') private readonly ripple!: Promise<MdRipple|null>;

override render() {
const classes = {
disabled: this.disabled,
elevated: this.elevated,
flat: !this.elevated,
};

const button = this.href ? literal`a` : literal`button`;
return staticHtml`
<${button} class="container ${classMap(classes)}"
<${button} class="container ${classMap(this.getContainerClasses())}"
?disabled=${this.disabled}
href=${this.href || nothing}
target=${this.href ? this.target : nothing}
@blur=${this.handleBlur}
@focus=${this.handleFocus}
@pointerdown=${this.handlePointerDown}
${ripple(this.getRipple)}>
${!this.elevated ? html`<span class="outline"></span>` : nothing}
${this.elevated ? html`<md-elevation></md-elevation>` : nothing}
${when(this.showRipple, this.renderRipple)}
${this.showRipple ? this.renderRipple() : nothing}
<md-focus-ring .visible=${this.showFocusRing}></md-focus-ring>
<span class="icon leading">
<slot name="leading-icon"></slot>
${this.renderLeadingIcon()}
</span>
<span class="label">${this.label}</span>
<span class="icon trailing">
${this.renderTrailingIcon?.() || nothing}
${this.renderTrailingIcon()}
</span>
</${button}>
`;
}

// Not all chip variants have a trailing icon. We still render a wrapper
// <span class="icon trailing"> to compute the correct padding + gap of the
// button.
protected renderTrailingIcon?: () => TemplateResult;
protected getContainerClasses() {
return {
disabled: this.disabled,
elevated: this.elevated,
};
}

protected renderLeadingIcon(): TemplateResult {
return html`<slot name="leading-icon"></slot>`;
}

protected renderTrailingIcon(): TemplateResult|typeof nothing {
return nothing;
}

private renderRipple() {
return html`<md-ripple ?disabled=${this.disabled}></md-ripple>`;
}

private readonly getRipple = () => { // bind to this
this.showRipple = true;
return this.ripple;
};

private readonly renderRipple = () => { // bind to this
return html`<md-ripple ?disabled=${this.disabled}></md-ripple>`;
};

private handleBlur() {
this.showFocusRing = false;
}
Expand Down
50 changes: 50 additions & 0 deletions chips/lib/filter-chip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {PropertyValues, svg} from 'lit';
import {property} from 'lit/decorators.js';

import {Chip} from './chip.js';

/**
* A filter chip component.
*/
export class FilterChip extends Chip {
@property({type: Boolean}) selected = false;

constructor() {
super();
this.addEventListener('click', () => {
this.selected = !this.selected;
});
}

protected override updated(changedProperties: PropertyValues<FilterChip>) {
if (changedProperties.has('selected')) {
this.dispatchEvent(new Event('change', {bubbles: true}));
}
}

protected override getContainerClasses() {
const classes = super.getContainerClasses();
return {
...classes,
selected: this.selected,
};
}

protected override renderLeadingIcon() {
if (!this.selected) {
return super.renderLeadingIcon();
}

return svg`
<svg class="checkmark" viewBox="0 0 18 18">
<path d="M6.75012 12.1274L3.62262 8.99988L2.55762 10.0574L6.75012 14.2499L15.7501 5.24988L14.6926 4.19238L6.75012 12.1274Z" />
</svg>
`;
}
}
10 changes: 10 additions & 0 deletions chips/lib/filter-styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
//

// go/keep-sorted start
@use './filter-chip';
// go/keep-sorted end

@include filter-chip.styles;

0 comments on commit ae91366

Please sign in to comment.