Skip to content

Commit

Permalink
fix(textfield): add demo a11y and fix outlined label navigation
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 565191993
  • Loading branch information
asyncLiz authored and copybara-github committed Sep 13, 2023
1 parent 4b40e67 commit 7866a93
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 69 deletions.
4 changes: 2 additions & 2 deletions field/internal/field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ export class Field extends LitElement implements SurfacePositionTarget {
<div class="field ${classMap(classes)}">
<div class="container-overflow">
${this.renderBackground?.()}
${this.renderIndicator?.()}
${outline}
<div class="container">
<div class="start">
<slot name="start"></slot>
Expand All @@ -137,8 +139,6 @@ export class Field extends LitElement implements SurfacePositionTarget {
<slot name="end"></slot>
</div>
</div>
${outline}
${this.renderIndicator?.()}
</div>
${this.renderSupportingText()}
</div>
Expand Down
13 changes: 2 additions & 11 deletions textfield/demo/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,19 @@ import './index.js';
import './material-collection.js';

import {KnobTypesToKnobs, MaterialCollection, materialInitsToStoryInits, setUpDemo} from './material-collection.js';
import {boolInput, Knob, numberInput, textInput} from './index.js';
import {boolInput, Knob, textInput} from './index.js';

import {stories, StoryKnobs} from './stories.js';

const collection =
new MaterialCollection<KnobTypesToKnobs<StoryKnobs>>('Textfield', [
new Knob('label', {ui: textInput(), defaultValue: 'Label'}),
new Knob('textarea', {ui: boolInput(), defaultValue: false}),
new Knob('placeholder', {ui: textInput(), defaultValue: ''}),
new Knob('disabled', {ui: boolInput(), defaultValue: false}),
new Knob('required', {ui: boolInput(), defaultValue: false}),
new Knob('prefixText', {ui: textInput(), defaultValue: ''}),
new Knob('suffixText', {ui: textInput(), defaultValue: ''}),
new Knob(
'supportingText', {ui: textInput(), defaultValue: 'Supporting text'}),
new Knob('minLength', {ui: numberInput(), defaultValue: -1}),
new Knob('maxLength', {ui: numberInput(), defaultValue: -1}),
new Knob('min', {ui: textInput(), defaultValue: ''}),
new Knob('max', {ui: textInput(), defaultValue: ''}),
new Knob('step', {ui: textInput(), defaultValue: ''}),
new Knob('pattern', {ui: textInput(), defaultValue: ''}),
new Knob('leading icon', {ui: boolInput(), defaultValue: false}),
new Knob('trailing icon', {ui: boolInput(), defaultValue: false}),
]);

collection.addStories(...materialInitsToStoryInits(stories));
Expand Down
256 changes: 200 additions & 56 deletions textfield/demo/stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

import '@material/web/button/outlined-button.js';
import '@material/web/button/text-button.js';
import '@material/web/icon/icon.js';
import '@material/web/iconbutton/icon-button.js';
import '@material/web/textfield/filled-text-field.js';
Expand All @@ -16,24 +18,27 @@ import {css, html, nothing} from 'lit';
/** Knob types for Textfield stories. */
export interface StoryKnobs {
label: string;
textarea: boolean;
placeholder: string;
disabled: boolean;
required: boolean;
prefixText: string;
suffixText: string;
supportingText: string;
minLength: number;
maxLength: number;
min: string;
max: string;
step: string;
pattern: string;
'leading icon': boolean;
'trailing icon': boolean;
}

// Set min-height for resizable textareas
const styles = css`
.row {
align-items: flex-start;
display: flex;
flex-wrap: wrap;
gap: 16px;
}
md-filled-text-field,
md-outlined-text-field {
width: 200px;
}
[type=textarea] {
min-height: 56px;
}
Expand All @@ -43,68 +48,207 @@ const styles = css`
}
`;

const filled: MaterialStoryInit<StoryKnobs> = {
name: '<md-filled-text-field>',
const textfields: MaterialStoryInit<StoryKnobs> = {
name: 'Text fields',
styles,
render(knobs) {
return html`
<div class="row">
<md-filled-text-field
?disabled=${knobs.disabled}
label=${knobs.label || nothing}
placeholder=${knobs.placeholder || nothing}
prefix-text=${knobs.prefixText || nothing}
suffix-text=${knobs.suffixText || nothing}
supporting-text=${knobs.supportingText || nothing}
></md-filled-text-field>
<md-outlined-text-field
?disabled=${knobs.disabled}
label=${knobs.label || nothing}
placeholder=${knobs.placeholder || nothing}
prefix-text=${knobs.prefixText || nothing}
suffix-text=${knobs.suffixText || nothing}
supporting-text=${knobs.supportingText || nothing}
></md-outlined-text-field>
</div>
`;
}
};

const textareas: MaterialStoryInit<StoryKnobs> = {
name: 'Text areas',
styles,
render(knobs) {
return html`
<div class="row">
<md-filled-text-field
type="textarea"
?disabled=${knobs.disabled}
label=${knobs.label || nothing}
placeholder=${knobs.placeholder || nothing}
supporting-text=${knobs.supportingText || nothing}
></md-filled-text-field>
<md-outlined-text-field
type="textarea"
?disabled=${knobs.disabled}
label=${knobs.label || nothing}
placeholder=${knobs.placeholder || nothing}
supporting-text=${knobs.supportingText || nothing}
></md-outlined-text-field>
</div>
`;
}
};

const icons: MaterialStoryInit<StoryKnobs> = {
name: 'Icons',
styles,
render(knobs) {
return html`
<md-filled-text-field
label=${knobs.label || nothing}
?disabled=${knobs.disabled}
prefix-text=${knobs.prefixText || nothing}
max=${knobs.max || nothing}
maxlength=${knobs.maxLength || nothing}
min=${knobs.min || nothing}
minlength=${knobs.minLength || nothing}
pattern=${knobs.pattern || nothing}
?required=${knobs.required}
step=${knobs.step || nothing}
suffix-text=${knobs.suffixText || nothing}
supporting-text=${knobs.supportingText || nothing}
type=${knobs.textarea ? 'textarea' : 'text'}
@change=${reportValidity}
>
${knobs['leading icon'] ? LEADING_ICON : nothing}
${knobs['trailing icon'] ? TRAILING_ICON : nothing}
</md-filled-text-field>
<div class="row">
<md-filled-text-field
?disabled=${knobs.disabled}
label=${knobs.label || nothing}
placeholder=${knobs.placeholder || nothing}
value="Value"
prefix-text=${knobs.prefixText || nothing}
suffix-text=${knobs.suffixText || nothing}
supporting-text=${knobs.supportingText || nothing}
>
<md-icon slot="leading-icon">search</md-icon>
<md-icon-button aria-label="Clear input" slot="trailing-icon"
@click=${clearInput}>
<md-icon>clear</md-icon>
</md-icon-button>
</md-filled-text-field>
<md-outlined-text-field
?disabled=${knobs.disabled}
label=${knobs.label || nothing}
placeholder=${knobs.placeholder || nothing}
value="Value"
prefix-text=${knobs.prefixText || nothing}
suffix-text=${knobs.suffixText || nothing}
supporting-text=${knobs.supportingText || nothing}
>
<md-icon slot="leading-icon">search</md-icon>
<md-icon-button aria-label="Clear input" slot="trailing-icon"
@click=${clearInput}>
<md-icon>clear</md-icon>
</md-icon-button>
</md-outlined-text-field>
</div>
`;
}
};

const outlined: MaterialStoryInit<StoryKnobs> = {
name: '<md-outlined-text-field>',
const validation: MaterialStoryInit<StoryKnobs> = {
name: 'Validation',
styles,
render(knobs) {
return html`
<md-outlined-text-field
label=${knobs.label || nothing}
?disabled=${knobs.disabled}
prefix-text=${knobs.prefixText || nothing}
max=${knobs.max || nothing}
maxlength=${knobs.maxLength || nothing}
min=${knobs.min || nothing}
minlength=${knobs.minLength || nothing}
pattern=${knobs.pattern || nothing}
?required=${knobs.required}
step=${knobs.step || nothing}
suffix-text=${knobs.suffixText || nothing}
supporting-text=${knobs.supportingText || nothing}
type=${knobs.textarea ? 'textarea' : 'text'}
@change=${reportValidity}
>
${knobs['leading icon'] ? LEADING_ICON : nothing}
${knobs['trailing icon'] ? TRAILING_ICON : nothing}
</md-outlined-text-field>
<div class="row">
<md-outlined-text-field
?disabled=${knobs.disabled}
label="Required"
value="Value"
required
supporting-text="* this field is required"
@change=${reportValidity}
></md-outlined-text-field>
<md-outlined-text-field
?disabled=${knobs.disabled}
type="number"
label="Numeric"
min="1"
max="10"
supporting-text="Enter a number between 1 and 10"
@change=${reportValidity}
></md-outlined-text-field>
<md-outlined-text-field
?disabled=${knobs.disabled}
label="Length"
minlength="3"
maxlength="10"
supporting-text="3 to 10 characters"
@change=${reportValidity}
></md-outlined-text-field>
<md-outlined-text-field
style="text-align: end"
?disabled=${knobs.disabled}
label="Pattern"
pattern="[a-zA-Z]+"
placeholder="username"
suffix-text="@gmail.com"
supporting-text="Characters only"
@change=${reportValidity}
></md-outlined-text-field>
</div>
`;
}
};

const forms: MaterialStoryInit<StoryKnobs> = {
name: 'Forms',
styles: [
styles,
css`
.buttons {
justify-content: flex-end;
padding: 16px;
}
`,
],
render(knobs) {
return html`
<form @submit=${alertValues}>
<div class="row">
<md-filled-text-field
?disabled=${knobs.disabled}
label="First name"
name="first-name"
autocomplete="given-name"
></md-filled-text-field>
<md-filled-text-field
?disabled=${knobs.disabled}
label="Last name"
name="last-name"
autocomplete="family-name"
></md-filled-text-field>
</div>
<div class="row buttons">
<md-text-button type="reset">Reset</md-text-button>
<md-outlined-button type="submit">Submit</md-outlined-button>
</div>
</form>
`;
}
};

const LEADING_ICON = html`<md-icon slot="leading-icon">search</md-icon>`;
const TRAILING_ICON =
html`<md-icon-button slot="trailing-icon"><md-icon>event</md-icon></md-icon-button>`;
function reportValidity(event: Event) {
(event.target as MdFilledTextField).reportValidity();
}

function clearInput(event: Event) {
const iconButton = event.target as HTMLElement;
const textField = iconButton.parentElement as MdFilledTextField;
iconButton.blur();
textField.value = '';
textField.focus();
}

function alertValues(event: SubmitEvent) {
event.preventDefault();
const data = new FormData(event.target as HTMLFormElement);
const first = data.get('first-name') || '<empty>';
const last = data.get('last-name') || '<empty>';
alert(`First name: ${first}, Last name: ${last}`);
}

/** Textfield stories. */
export const stories = [filled, outlined];
export const stories = [textfields, textareas, icons, validation, forms];
1 change: 1 addition & 0 deletions textfield/internal/_input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@

.prefix,
.suffix {
text-wrap: nowrap;
width: min-content;
}

Expand Down

0 comments on commit 7866a93

Please sign in to comment.