Skip to content

Commit

Permalink
feat(text-field): add icons
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 464165729
  • Loading branch information
asyncLiz authored and copybara-github committed Jul 29, 2022
1 parent 1ddcbba commit 424596e
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 25 deletions.
15 changes: 12 additions & 3 deletions textfield/filled-text-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,26 @@ export class MdFilledTextField extends FilledTextField {

/** @soyTemplate */
protected override renderField(): TemplateResult {
// TODO(b/239690585): move start/end slots to renderFieldContent
return html`
<md-filled-field
class="md3-text-field__field"
id=${this.fieldId}
?disabled=${this.disabled}
.error=${this.error}
?error=${this.error}
?hasEnd=${this.hasTrailingIcon}
?hasStart=${this.hasLeadingIcon}
.label=${this.label}
.populated=${!!this.value}
.required=${this.required}
?populated=${!!this.value}
?required=${this.required}
>
<span slot="start">
${this.renderLeadingIcon()}
</span>
${this.renderFieldContent()}
<span slot="end">
${this.renderTrailingIcon()}
</span>
</md-filled-field>
`;
}
Expand Down
2 changes: 2 additions & 0 deletions textfield/lib/_filled-text-field-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
@use '@material/web/sass/typography';
@use '@material/web/sass/var';
@use '@material/web/tokens';
@use './icon-theme';
@use './input-theme';

$light-theme: tokens.md-comp-filled-text-field-values();
Expand Down Expand Up @@ -95,6 +96,7 @@ $_reference-theme: map.merge(
$theme: theme.create-theme-vars($theme, filled-text-field);

@include input-theme.theme-styles($theme);
@include icon-theme.theme-styles($theme);

$field-theme: (
active-indicator-color: map.get($theme, active-indicator-color),
Expand Down
100 changes: 100 additions & 0 deletions textfield/lib/_icon-theme.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//
// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0
//

// stylelint-disable selector-class-pattern --
// Selector '.md3-*' should only be used in this project.

@use 'sass:map';
@use '@material/web/iconbutton/standard-icon-button';

@mixin theme-styles($theme) {
@include _leading-icon-color(
(
default: map.get($theme, leading-icon-color),
hover: map.get($theme, hover-leading-icon-color),
focus: map.get($theme, focus-leading-icon-color),
disabled: map.get($theme, disabled-leading-icon-color),
)
);
@include _trailing-icon-color(
(
default: map.get($theme, trailing-icon-color),
hover: map.get($theme, hover-trailing-icon-color),
focus: map.get($theme, focus-trailing-icon-color),
disabled: map.get($theme, disabled-trailing-icon-color),
)
);

&.md3-text-field--error:not(.md3-text-field--disabled) {
@include _leading-icon-color(
(
default: map.get($theme, error-leading-icon-color),
hover: map.get($theme, error-hover-leading-icon-color),
focus: map.get($theme, error-focus-leading-icon-color),
)
);
@include _trailing-icon-color(
(
default: map.get($theme, error-trailing-icon-color),
hover: map.get($theme, error-hover-trailing-icon-color),
focus: map.get($theme, error-focus-trailing-icon-color),
)
);
}

&.md3-text-field--disabled {
.md3-text-field__icon--leading {
opacity: map.get($theme, disabled-leading-icon-opacity);
}

.md3-text-field__icon--trailing {
opacity: map.get($theme, disabled-trailing-icon-opacity);
}
}
}

@mixin _leading-icon-color($colors) {
@include _set-leading-icon-color(map.get($colors, default));

&:hover {
@include _set-leading-icon-color(map.get($colors, hover));
}

&:focus-within {
@include _set-leading-icon-color(map.get($colors, focus));
}

&.md3-text-field--disabled {
@include _set-leading-icon-color(map.get($colors, disabled));
}
}

@mixin _set-leading-icon-color($color) {
.md3-text-field__icon--leading {
color: $color;
}
}

@mixin _trailing-icon-color($colors) {
@include _set-trailing-icon-color(map.get($colors, default));

&:hover {
@include _set-trailing-icon-color(map.get($colors, hover));
}

&:focus-within {
@include _set-trailing-icon-color(map.get($colors, focus));
}

&.md3-text-field--disabled {
@include _set-trailing-icon-color(map.get($colors, disabled));
}
}

@mixin _set-trailing-icon-color($color) {
.md3-text-field__icon--trailing {
color: $color;
}
}
18 changes: 18 additions & 0 deletions textfield/lib/_icon.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0
//

// stylelint-disable selector-class-pattern --
// Selector '.md3-*' should only be used in this project.

@mixin static-styles() {
.md3-text-field__icon {
display: flex;

::slotted(*) {
// Remove excess whitespace below inline elementsxs
display: flex;
}
}
}
2 changes: 2 additions & 0 deletions textfield/lib/_outlined-text-field-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
@use '@material/web/sass/theme';
@use '@material/web/sass/typography';
@use '@material/web/tokens';
@use './icon-theme';
@use './input-theme';

$light-theme: tokens.md-comp-outlined-text-field-values();
Expand Down Expand Up @@ -95,6 +96,7 @@ $_reference-theme: map.merge(
$theme: theme.create-theme-vars($theme, outlined-text-field);

@include input-theme.theme-styles($theme);
@include icon-theme.theme-styles($theme);

$field-theme: (
container-height: map.get($theme, container-height),
Expand Down
2 changes: 2 additions & 0 deletions textfield/lib/shared-styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
// SPDX-License-Identifier: Apache-2.0
//

@use './icon';
@use './input';
@use './text-field';

@include text-field.static-styles;
@include input.static-styles;
@include icon.static-styles;
80 changes: 61 additions & 19 deletions textfield/lib/text-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {FormController, getFormValue} from '@material/web/controller/form-contro
import {stringConverter} from '@material/web/controller/string-converter';
import {ariaProperty} from '@material/web/decorators/aria-property';
import {html, LitElement, PropertyValues, TemplateResult} from 'lit';
import {property, query, state} from 'lit/decorators';
import {property, query, queryAssignedElements, state} from 'lit/decorators';
import {ClassInfo, classMap} from 'lit/directives/class-map';
import {ifDefined} from 'lit/directives/if-defined';
import {live} from 'lit/directives/live';
Expand Down Expand Up @@ -69,6 +69,14 @@ export class TextField extends LitElement {
* The ID on the field element, used for SSR.
*/
@property({type: String}) fieldId = 'field';
/**
* Whether or not the text field has a leading icon. Used for SSR.
*/
@property({type: Boolean}) hasLeadingIcon = false;
/**
* Whether or not the text field has a trailing icon. Used for SSR.
*/
@property({type: Boolean}) hasTrailingIcon = false;

// ARIA
// TODO(b/210730484): replace with @soyParam annotation
Expand Down Expand Up @@ -108,6 +116,11 @@ export class TextField extends LitElement {
@query('.md3-text-field__input')
protected readonly input?: HTMLInputElement|null;

@queryAssignedElements({slot: 'leadingicon'})
private readonly leadingIcons!: Element[];
@queryAssignedElements({slot: 'trailingicon'})
private readonly trailingIcons!: Element[];

constructor() {
super();
this.addController(new FormController(this));
Expand Down Expand Up @@ -159,30 +172,54 @@ export class TextField extends LitElement {

/** @soyTemplate */
protected renderFieldContent(): TemplateResult {
const prefix = this.renderPrefix();
const suffix = this.renderSuffix();
const input = this.renderInput();

return html`${prefix}${input}${suffix}`;
}

/** @soyTemplate */
protected renderLeadingIcon(): TemplateResult {
return html`
<span class="md3-text-field__icon md3-text-field__icon--leading">
<slot name="leadingicon" @slotchange=${this.handleIconChange}></slot>
</span>
`;
}

/** @soyTemplate */
protected renderTrailingIcon(): TemplateResult {
return html`
<span class="md3-text-field__icon md3-text-field__icon--trailing">
<slot name="trailingicon" @slotchange=${this.handleIconChange}></slot>
</span>
`;
}

/** @soyTemplate */
protected renderInput(): TemplateResult {
// TODO(b/237281840): replace ternary operators with double pipes
// TODO(b/237283903): remove when custom isTruthy directive is supported
const placeholderValue = this.placeholder ? this.placeholder : undefined;
const ariaLabelledByValue =
this.ariaLabelledBy ? this.ariaLabelledBy : this.fieldId;

const prefix = this.renderPrefix();
const suffix = this.renderSuffix();

return html`${prefix}<input
class="md3-text-field__input"
aria-invalid=${this.error}
aria-label=${ifDefined(this.ariaLabel)}
aria-labelledby=${ariaLabelledByValue}
?disabled=${this.disabled}
placeholder=${ifDefined(placeholderValue)}
?readonly=${this.readonly}
?required=${this.required}
type=${this.type}
.value=${live(this.value)}
@change=${this.redispatchEvent}
@input=${this.handleInput}
@select=${this.redispatchEvent}
>${suffix}`;
return html`<input
class="md3-text-field__input"
aria-invalid=${this.error}
aria-label=${ifDefined(this.ariaLabel)}
aria-labelledby=${ariaLabelledByValue}
?disabled=${this.disabled}
placeholder=${ifDefined(placeholderValue)}
?readonly=${this.readonly}
?required=${this.required}
type=${this.type}
.value=${live(this.value)}
@change=${this.redispatchEvent}
@input=${this.handleInput}
@select=${this.redispatchEvent}
>`;
}

/** @soyTemplate */
Expand Down Expand Up @@ -224,4 +261,9 @@ export class TextField extends LitElement {
protected redispatchEvent(event: Event) {
redispatchEvent(this, event);
}

private handleIconChange() {
this.hasLeadingIcon = this.leadingIcons.length > 0;
this.hasTrailingIcon = this.trailingIcons.length > 0;
}
}
15 changes: 12 additions & 3 deletions textfield/outlined-text-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,26 @@ export class MdOutlinedTextField extends OutlinedTextField {

/** @soyTemplate */
protected override renderField(): TemplateResult {
// TODO(b/239690585): move start/end slots to renderFieldContent
return html`
<md-outlined-field
class="md3-text-field__field"
id=${this.fieldId}
?disabled=${this.disabled}
.error=${this.error}
?error=${this.error}
?hasEnd=${this.hasTrailingIcon}
?hasStart=${this.hasLeadingIcon}
.label=${this.label}
.populated=${!!this.value}
.required=${this.required}
?populated=${!!this.value}
?required=${this.required}
>
<span slot="start">
${this.renderLeadingIcon()}
</span>
${this.renderFieldContent()}
<span slot="end">
${this.renderTrailingIcon()}
</span>
</md-outlined-field>
`;
}
Expand Down

0 comments on commit 424596e

Please sign in to comment.