Skip to content
This repository was archived by the owner on Jan 13, 2025. It is now read-only.

Commit 0b73002

Browse files
authored
feat(chips): Replace leading icon with checkmark in selected filter chips (#2320)
BREAKING CHANGE: renamed (de)registerInteractionHandler to (de)registerEventHandler and added multiple new methods to MDCChipAdapter. Also changed HTML structure of filter chips to include checkmark.
1 parent 567deec commit 0b73002

File tree

12 files changed

+401
-38
lines changed

12 files changed

+401
-38
lines changed

demos/chips.html

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,24 +92,85 @@ <h2>Choice Chips</h2>
9292

9393
<section class="example">
9494
<h2>Filter Chips</h2>
95+
<h4>No leading icon</h4>
9596
<div class="mdc-chip-set mdc-chip-set--filter">
9697
<div class="demo-chip mdc-chip" tabindex="0">
98+
<div class="mdc-chip__checkmark" >
99+
<svg class="mdc-chip__checkmark-svg" viewBox="-2 -3 30 30">
100+
<path class="mdc-chip__checkmark-path" fill="none" stroke="black" d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
101+
</svg>
102+
</div>
97103
<div class="mdc-chip__text">Tops</div>
98104
<i class="material-icons mdc-chip__icon mdc-chip__icon--trailing" tabindex="0" role="button">cancel</i>
99105
</div>
100106
<div class="demo-chip mdc-chip" tabindex="0">
107+
<div class="mdc-chip__checkmark" >
108+
<svg class="mdc-chip__checkmark-svg" viewBox="-2 -3 30 30">
109+
<path class="mdc-chip__checkmark-path" fill="none" stroke="black" d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
110+
</svg>
111+
</div>
101112
<div class="mdc-chip__text">Bottoms</div>
102113
<i class="material-icons mdc-chip__icon mdc-chip__icon--trailing" tabindex="0" role="button">cancel</i>
103114
</div>
104115
<div class="demo-chip mdc-chip" tabindex="0">
116+
<div class="mdc-chip__checkmark" >
117+
<svg class="mdc-chip__checkmark-svg" viewBox="-2 -3 30 30">
118+
<path class="mdc-chip__checkmark-path" fill="none" stroke="black" d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
119+
</svg>
120+
</div>
105121
<div class="mdc-chip__text">Shoes</div>
106122
<i class="material-icons mdc-chip__icon mdc-chip__icon--trailing" tabindex="0" role="button">cancel</i>
107123
</div>
108124
<div class="demo-chip mdc-chip" tabindex="0">
125+
<div class="mdc-chip__checkmark" >
126+
<svg class="mdc-chip__checkmark-svg" viewBox="-2 -3 30 30">
127+
<path class="mdc-chip__checkmark-path" fill="none" stroke="black" d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
128+
</svg>
129+
</div>
109130
<div class="mdc-chip__text">Accessories</div>
110131
<i class="material-icons mdc-chip__icon mdc-chip__icon--trailing" tabindex="0" role="button">cancel</i>
111132
</div>
112133
</div>
134+
<br>
135+
<h4>With leading icon</h4>
136+
<div class="mdc-chip-set mdc-chip-set--filter">
137+
<div class="demo-chip mdc-chip" tabindex="0">
138+
<i class="material-icons mdc-chip__icon mdc-chip__icon--leading">face</i>
139+
<div class="mdc-chip__checkmark" >
140+
<svg class="mdc-chip__checkmark-svg" viewBox="-2 -3 30 30">
141+
<path class="mdc-chip__checkmark-path" fill="none" stroke="black" d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
142+
</svg>
143+
</div>
144+
<div class="mdc-chip__text">Alice</div>
145+
</div>
146+
<div class="demo-chip mdc-chip" tabindex="0">
147+
<i class="material-icons mdc-chip__icon mdc-chip__icon--leading">face</i>
148+
<div class="mdc-chip__checkmark" >
149+
<svg class="mdc-chip__checkmark-svg" viewBox="-2 -3 30 30">
150+
<path class="mdc-chip__checkmark-path" fill="none" stroke="black" d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
151+
</svg>
152+
</div>
153+
<div class="mdc-chip__text">Bob</div>
154+
</div>
155+
<div class="demo-chip mdc-chip" tabindex="0">
156+
<i class="material-icons mdc-chip__icon mdc-chip__icon--leading">face</i>
157+
<div class="mdc-chip__checkmark" >
158+
<svg class="mdc-chip__checkmark-svg" viewBox="-2 -3 30 30">
159+
<path class="mdc-chip__checkmark-path" fill="none" stroke="black" d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
160+
</svg>
161+
</div>
162+
<div class="mdc-chip__text">Charlie</div>
163+
</div>
164+
<div class="demo-chip mdc-chip" tabindex="0">
165+
<i class="material-icons mdc-chip__icon mdc-chip__icon--leading">face</i>
166+
<div class="mdc-chip__checkmark" >
167+
<svg class="mdc-chip__checkmark-svg" viewBox="-2 -3 30 30">
168+
<path class="mdc-chip__checkmark-path" fill="none" stroke="black" d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
169+
</svg>
170+
</div>
171+
<div class="mdc-chip__text">David</div>
172+
</div>
173+
</div>
113174
</section>
114175

115176
<section class="example">

packages/mdc-chips/README.md

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,18 +74,52 @@ You can optionally add a leading icon (i.e. thumbnail) and/or a trailing icon to
7474
</div>
7575
```
7676

77+
#### Filter Chips
78+
79+
Filter chips are a variant of chips which allow multiple selection from a set of options. When a filter chip is selected, a checkmark appears as the leading icon. If the chip already has a leading icon, the checkmark replaces it. This requires the HTML structure of a filter chip to differ from other chips:
80+
81+
```html
82+
<div class="mdc-chip">
83+
<div class="mdc-chip__checkmark" >
84+
<svg class="mdc-chip__checkmark-svg" viewBox="-2 -3 30 30">
85+
<path class="mdc-chip__checkmark-path" fill="none" stroke="black"
86+
d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
87+
</svg>
88+
</div>
89+
<div class="mdc-chip__text">Filterable content</div>
90+
</div>
91+
```
92+
93+
> _NOTE_: To use a leading icon in a filter chip, put the `mdc-chip__icon--leading` element _before_ the `mdc-chip__checkmark` element:
94+
95+
```html
96+
<div class="mdc-chip">
97+
<i class="material-icons mdc-chip__icon mdc-chip__icon--leading">face</i>
98+
<div class="mdc-chip__checkmark" >
99+
<svg class="mdc-chip__checkmark-svg" viewBox="-2 -3 30 30">
100+
<path class="mdc-chip__checkmark-path" fill="none" stroke="black"
101+
d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
102+
</svg>
103+
</div>
104+
<div class="mdc-chip__text">Filterable content</div>
105+
</div>
106+
```
107+
77108
### CSS Classes
78109

79110
CSS Class | Description
80111
--- | ---
81-
`mdc-chip-set` | Mandatory. Indicates the set that the chip belongs to
112+
`mdc-chip-set` | Mandatory. Indicates the set that the chip belongs to.
82113
`mdc-chip-set--choice` | Optional. Indicates that the chips in the set are choice chips, which allow a single selection from a set of options.
83114
`mdc-chip-set--filter` | Optional. Indicates that the chips in the set are filter chips, which allow multiple selection from a set of options.
84115
`mdc-chip` | Mandatory.
85-
`mdc-chip__text` | Mandatory. Indicates the text content of the chip
86-
`mdc-chip__icon` | Optional. Indicates an icon in the chip
87-
`mdc-chip__icon--leading` | Optional. Indicates a leading icon in the chip
88-
`mdc-chip__icon--trailing` | Optional. Indicates a trailing icon in the chip
116+
`mdc-chip__text` | Mandatory. Indicates the text content of the chip.
117+
`mdc-chip__icon` | Optional. Indicates an icon in the chip.
118+
`mdc-chip__icon--leading` | Optional. Indicates a leading icon in the chip.
119+
`mdc-chip__icon--trailing` | Optional. Indicates a trailing icon in the chip.
120+
`mdc-chip__checkmark` | Optional. Indicates the checkmark in a filter chip.
121+
`mdc-chip__checkmark-svg` | Mandatory with the use of `mdc-chip__checkmark`. Indicates the checkmark SVG element in a filter chip.
122+
`mdc-chip__checkmark-path` | Mandatory with the use of `mdc-chip__checkmark`. Indicates the checkmark SVG path in a filter chip.
89123

90124
> _NOTE_: Every element that has an `mdc-chip__icon` class must also have either the `mdc-chip__icon--leading` or `mdc-chip__icon--trailing` class.
91125
@@ -141,8 +175,11 @@ Method Signature | Description
141175
`addClass(className: string) => void` | Adds a class to the root element
142176
`removeClass(className: string) => void` | Removes a class from the root element
143177
`hasClass(className: string) => boolean` | Returns true if the root element contains the given class
144-
`registerInteractionHandler(evtType: string, handler: EventListener) => void` | Registers an event listener on the root element
145-
`deregisterInteractionHandler(evtType: string, handler: EventListener) => void` | Deregisters an event listener on the root element
178+
`addClassToLeadingIcon(className: string) => void` | Adds a class to the leading icon element
179+
`removeClassFromLeadingIcon(className: string) => void` | Removes a class from the leading icon element
180+
`eventTargetHasClass(target: EventTarget, className: string) => boolean` | Returns true if target has className, false otherwise
181+
`registerEventHandler(evtType: string, handler: EventListener) => void` | Registers an event listener on the root element
182+
`deregisterEventHandler(evtType: string, handler: EventListener) => void` | Deregisters an event listener on the root element
146183
`registerTrailingIconInteractionHandler(evtType: string, handler: EventListener) => void` | Registers an event listener on the trailing icon element
147184
`deregisterTrailingIconInteractionHandler(evtType: string, handler: EventListener) => void` | Deregisters an event listener on the trailing icon element
148185
`notifyInteraction() => void` | Emits a custom event `MDCChip:interaction` denoting the chip has been interacted with, which bubbles to the parent `mdc-chip-set` element

packages/mdc-chips/_mixins.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@
6161
@include mdc-theme-prop(color, $color);
6262
}
6363
}
64+
65+
.mdc-chip__checkmark-path {
66+
@include mdc-theme-prop(stroke, $color);
67+
}
6468
}
6569

6670
@mixin mdc-chip-stroke($width: 1, $style: solid, $color: black) {

packages/mdc-chips/_variables.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,8 @@ $mdc-chip-icon-color: rgba(black, .54);
2424
$mdc-chip-trailing-icon-size: 18px;
2525
$mdc-chip-trailing-icon-hover-color: rgba(black, .62);
2626
$mdc-chip-trailing-icon-focus-color: rgba(black, .87);
27+
28+
$mdc-chip-checkmark-animation-delay: 50ms;
29+
$mdc-chip-checkmark-animation-duration: 150ms;
30+
$mdc-chip-width-animation-duration: 150ms;
31+
$mdc-chip-opacity-animation-duration: 75ms;

packages/mdc-chips/chip/adapter.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,39 @@ class MDCChipAdapter {
4747
*/
4848
hasClass(className) {}
4949

50+
/**
51+
* Adds a class to the leading icon element.
52+
* @param {string} className
53+
*/
54+
addClassToLeadingIcon(className) {}
55+
56+
/**
57+
* Removes a class from the leading icon element.
58+
* @param {string} className
59+
*/
60+
removeClassFromLeadingIcon(className) {}
61+
62+
/**
63+
* Returns true if target has className, false otherwise.
64+
* @param {!EventTarget} target
65+
* @param {string} className
66+
* @return {boolean}
67+
*/
68+
eventTargetHasClass(target, className) {}
69+
5070
/**
5171
* Registers an event listener on the root element for a given event.
5272
* @param {string} evtType
5373
* @param {function(!Event): undefined} handler
5474
*/
55-
registerInteractionHandler(evtType, handler) {}
75+
registerEventHandler(evtType, handler) {}
5676

5777
/**
5878
* Deregisters an event listener on the root element for a given event.
5979
* @param {string} evtType
6080
* @param {function(!Event): undefined} handler
6181
*/
62-
deregisterInteractionHandler(evtType, handler) {}
82+
deregisterEventHandler(evtType, handler) {}
6383

6484
/**
6585
* Registers an event listener on the trailing icon element for a given event.

packages/mdc-chips/chip/constants.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@
1818
/** @enum {string} */
1919
const strings = {
2020
INTERACTION_EVENT: 'MDCChip:interaction',
21+
LEADING_ICON_SELECTOR: '.mdc-chip__icon--leading',
2122
TRAILING_ICON_INTERACTION_EVENT: 'MDCChip:trailingIconInteraction',
2223
TRAILING_ICON_SELECTOR: '.mdc-chip__icon--trailing',
2324
};
2425

2526
/** @enum {string} */
2627
const cssClasses = {
28+
CHECKMARK: 'mdc-chip__checkmark',
29+
HIDDEN_LEADING_ICON: 'mdc-chip__icon--hidden-leading',
30+
LEADING_ICON: 'mdc-chip__icon--leading',
2731
SELECTED: 'mdc-chip--selected',
2832
};
2933

packages/mdc-chips/chip/foundation.js

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,11 @@ class MDCChipFoundation extends MDCFoundation {
4545
addClass: () => {},
4646
removeClass: () => {},
4747
hasClass: () => {},
48-
registerInteractionHandler: () => {},
49-
deregisterInteractionHandler: () => {},
48+
addClassToLeadingIcon: () => {},
49+
removeClassFromLeadingIcon: () => {},
50+
eventTargetHasClass: () => {},
51+
registerEventHandler: () => {},
52+
deregisterEventHandler: () => {},
5053
registerTrailingIconInteractionHandler: () => {},
5154
deregisterTrailingIconInteractionHandler: () => {},
5255
notifyInteraction: () => {},
@@ -63,25 +66,27 @@ class MDCChipFoundation extends MDCFoundation {
6366
/** @private {function(!Event): undefined} */
6467
this.interactionHandler_ = (evt) => this.handleInteraction_(evt);
6568
/** @private {function(!Event): undefined} */
69+
this.transitionEndHandler_ = (evt) => this.handleTransitionEnd_(evt);
70+
/** @private {function(!Event): undefined} */
6671
this.trailingIconInteractionHandler_ = (evt) => this.handleTrailingIconInteraction_(evt);
6772
}
6873

6974
init() {
7075
['click', 'keydown'].forEach((evtType) => {
71-
this.adapter_.registerInteractionHandler(evtType, this.interactionHandler_);
72-
this.adapter_.registerTrailingIconInteractionHandler(evtType, this.trailingIconInteractionHandler_);
76+
this.adapter_.registerEventHandler(evtType, this.interactionHandler_);
7377
});
74-
['touchstart', 'pointerdown', 'mousedown'].forEach((evtType) => {
78+
this.adapter_.registerEventHandler('transitionend', this.transitionEndHandler_);
79+
['click', 'keydown', 'touchstart', 'pointerdown', 'mousedown'].forEach((evtType) => {
7580
this.adapter_.registerTrailingIconInteractionHandler(evtType, this.trailingIconInteractionHandler_);
7681
});
7782
}
7883

7984
destroy() {
8085
['click', 'keydown'].forEach((evtType) => {
81-
this.adapter_.deregisterInteractionHandler(evtType, this.interactionHandler_);
82-
this.adapter_.deregisterTrailingIconInteractionHandler(evtType, this.trailingIconInteractionHandler_);
86+
this.adapter_.deregisterEventHandler(evtType, this.interactionHandler_);
8387
});
84-
['touchstart', 'pointerdown', 'mousedown'].forEach((evtType) => {
88+
this.adapter_.deregisterEventHandler('transitionend', this.transitionEndHandler_);
89+
['click', 'keydown', 'touchstart', 'pointerdown', 'mousedown'].forEach((evtType) => {
8590
this.adapter_.deregisterTrailingIconInteractionHandler(evtType, this.trailingIconInteractionHandler_);
8691
});
8792
}
@@ -107,6 +112,25 @@ class MDCChipFoundation extends MDCFoundation {
107112
}
108113
}
109114

115+
/**
116+
* Handles a transition end event on the root element.
117+
* This is a proxy for handling a transition end event on the leading icon or checkmark,
118+
* since the transition end event bubbles.
119+
* @param {!Event} evt
120+
*/
121+
handleTransitionEnd_(evt) {
122+
if (evt.propertyName !== 'opacity') {
123+
return;
124+
}
125+
if (this.adapter_.eventTargetHasClass(/** @type {!EventTarget} */ (evt.target), cssClasses.LEADING_ICON) &&
126+
this.adapter_.hasClass(cssClasses.SELECTED)) {
127+
this.adapter_.addClassToLeadingIcon(cssClasses.HIDDEN_LEADING_ICON);
128+
} else if (this.adapter_.eventTargetHasClass(/** @type {!EventTarget} */ (evt.target), cssClasses.CHECKMARK) &&
129+
!this.adapter_.hasClass(cssClasses.SELECTED)) {
130+
this.adapter_.removeClassFromLeadingIcon(cssClasses.HIDDEN_LEADING_ICON);
131+
}
132+
}
133+
110134
/**
111135
* Handles an interaction event on the trailing icon element. This is used to
112136
* prevent the ripple from activating on interaction with the trailing icon.

packages/mdc-chips/chip/index.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class MDCChip extends MDCComponent {
3333
constructor(...args) {
3434
super(...args);
3535

36+
/** @private {?Element} */
37+
this.leadingIcon_ = this.root_.querySelector(strings.LEADING_ICON_SELECTOR);
3638
/** @private {!MDCRipple} */
3739
this.ripple_ = new MDCRipple(this.root_);
3840
}
@@ -65,8 +67,19 @@ class MDCChip extends MDCComponent {
6567
addClass: (className) => this.root_.classList.add(className),
6668
removeClass: (className) => this.root_.classList.remove(className),
6769
hasClass: (className) => this.root_.classList.contains(className),
68-
registerInteractionHandler: (evtType, handler) => this.root_.addEventListener(evtType, handler),
69-
deregisterInteractionHandler: (evtType, handler) => this.root_.removeEventListener(evtType, handler),
70+
addClassToLeadingIcon: (className) => {
71+
if (this.leadingIcon_) {
72+
this.leadingIcon_.classList.add(className);
73+
}
74+
},
75+
removeClassFromLeadingIcon: (className) => {
76+
if (this.leadingIcon_) {
77+
this.leadingIcon_.classList.remove(className);
78+
}
79+
},
80+
eventTargetHasClass: (target, className) => target.classList.contains(className),
81+
registerEventHandler: (evtType, handler) => this.root_.addEventListener(evtType, handler),
82+
deregisterEventHandler: (evtType, handler) => this.root_.removeEventListener(evtType, handler),
7083
registerTrailingIconInteractionHandler: (evtType, handler) => {
7184
const trailingIconEl = this.root_.querySelector(strings.TRAILING_ICON_SELECTOR);
7285
if (trailingIconEl) {

0 commit comments

Comments
 (0)