Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(text-field): Add ripple to outlined text field #1807

Merged
merged 19 commits into from
Dec 21, 2017
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/mdc-ripple/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,4 @@ RippleCapableSurface.prototype.unbounded;
*/
RippleCapableSurface.prototype.disabled;

export {MDCRipple, MDCRippleFoundation, util};
export {MDCRipple, MDCRippleFoundation, RippleCapableSurface, util};
2 changes: 1 addition & 1 deletion packages/mdc-textfield/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ Method Signature | Description

##### `MDCTextField.ripple`

The `MDCRipple` instance for the root element that `MDCTextField` initializes when given an `mdc-text-field--box` root element. Otherwise, the field is set to `null`.
`MDCRipple` instance. When given an `mdc-text-field--box` root element, this is set to the `MDCRipple` instance on the root element. When given an `mdc-text-field--outlined` root element, this is set to the `MDCRipple` instance on the `mdc-text-field__outline` element. Otherwise, the field is set to `null`.

### `MDCTextFieldAdapter`

Expand Down
1 change: 1 addition & 0 deletions packages/mdc-textfield/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const cssClasses = {
FOCUSED: 'mdc-text-field--focused',
INVALID: 'mdc-text-field--invalid',
BOX: 'mdc-text-field--box',
OUTLINED: 'mdc-text-field--outlined',
};

export {cssClasses, strings};
32 changes: 20 additions & 12 deletions packages/mdc-textfield/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
*/

import MDCComponent from '@material/base/component';
import {MDCRipple, MDCRippleFoundation} from '@material/ripple';
/* eslint-disable no-unused-vars */
import {MDCRipple, MDCRippleFoundation, RippleCapableSurface} from '@material/ripple';
/* eslint-enable no-unused-vars */
import {getMatchesProperty} from '@material/ripple/util';


Expand Down Expand Up @@ -91,17 +93,6 @@ class MDCTextField extends MDCComponent {
if (labelElement) {
this.label_ = labelFactory(labelElement);
}
this.ripple = null;
if (this.root_.classList.contains(cssClasses.BOX)) {
const MATCHES = getMatchesProperty(HTMLElement.prototype);
const adapter = Object.assign(MDCRipple.createAdapter(this), {
isSurfaceActive: () => this.input_[MATCHES](':active'),
registerInteractionHandler: (type, handler) => this.input_.addEventListener(type, handler),
deregisterInteractionHandler: (type, handler) => this.input_.removeEventListener(type, handler),
});
const foundation = new MDCRippleFoundation(adapter);
this.ripple = rippleFactory(this.root_, foundation);
}
const bottomLineElement = this.root_.querySelector(strings.BOTTOM_LINE_SELECTOR);
if (bottomLineElement) {
this.bottomLine_ = bottomLineFactory(bottomLineElement);
Expand All @@ -120,6 +111,23 @@ class MDCTextField extends MDCComponent {
if (iconElement) {
this.icon_ = iconFactory(iconElement);
}

this.ripple = null;
if (this.root_.classList.contains(cssClasses.BOX) || this.root_.classList.contains(cssClasses.OUTLINED)) {
// For outlined text fields, the ripple is instantiated on the outline element instead of the root element
// to clip the ripple at the outline while still allowing the label to be visible beyond the outline.
const rippleCapableSurface = outlineElement ? this.outline_ : this;
const rippleRoot = outlineElement ? outlineElement : this.root_;
const MATCHES = getMatchesProperty(HTMLElement.prototype);
const adapter =
Object.assign(MDCRipple.createAdapter(/** @type {!RippleCapableSurface} */ (rippleCapableSurface)), {
isSurfaceActive: () => this.input_[MATCHES](':active'),
registerInteractionHandler: (type, handler) => this.input_.addEventListener(type, handler),
deregisterInteractionHandler: (type, handler) => this.input_.removeEventListener(type, handler),
});
const foundation = new MDCRippleFoundation(adapter);
this.ripple = rippleFactory(rippleRoot, foundation);
}
}

destroy() {
Expand Down
6 changes: 4 additions & 2 deletions packages/mdc-textfield/mdc-text-field.scss
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,10 @@
opacity: 1;
}

&:not(.mdc-text-field--focused) .mdc-text-field__outline-path {
stroke-width: 1px;
&.mdc-text-field--focused .mdc-text-field__outline-path {
@include mdc-theme-prop(stroke, primary);

stroke-width: 2px;
}

&.mdc-text-field--disabled {
Expand Down
56 changes: 45 additions & 11 deletions packages/mdc-textfield/outline/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,56 @@ The outline is a border around all sides of the text field. This is used for the

## Usage

#### MDCTextFieldOutline API
### HTML Structure

##### MDCTextFieldOutline.foundation
```html
<div class="mdc-text-field__outline">
<svg>
<path class="mdc-text-field__outline-path"/>
</svg>
</div>
<div class="mdc-text-field__idle-outline"></div>
```

MDCTextFieldOutlineFoundation. This allows the parent MDCTextField component to access the public methods on the MDCTextFieldOutlineFoundation class.
### Usage within `mdc-text-field`

### Using the foundation class
```html
<div class="mdc-text-field mdc-text-field--outlined">
<input class="mdc-text-field__input" id="my-text-field-id" type="text">
<label class="mdc-text-field__label" for="my-text-field-id">Label</label>
<div class="mdc-text-field__outline">
<svg>
<path class="mdc-text-field__outline-path"/>
</svg>
</div>
<div class="mdc-text-field__idle-outline"></div>
</div>
```

Method Signature | Description
### CSS Classes

CSS Class | Description
--- | ---
getWidth() => number | Returns the width of the outline element
getHeight() => number | Returns the height of the outline element
setOutlinePathAttr(value: string) => void | Sets the "d" attribute of the outline element's SVG path
`mdc-text-field__outline` | Mandatory. Container for the SVG in the outline when the label is floating above the input.
`mdc-text-field__outline-path` | Mandatory. The SVG path in the outline when the label is floating above the input.
`mdc-text-field__idle-outline` | Mandatory. The outline when the label is resting in the input position.

#### `MDCTextFieldOutline`

##### `MDCTextFieldOutline.foundation`

This allows the parent `MDCTextField` component to access the public methods on the `MDCTextFieldOutlineFoundation` class.

#### The full foundation API
### `MDCTextFieldOutlineAdapter`

##### MDCTextFieldOutlineFoundation.updateSvgPath(width: number, height: number, labelWidth: number, radius: number, isRtl: boolean)
Method Signature | Description
--- | ---
`getWidth() => number` | Returns the width of the outline element
`getHeight() => number` | Returns the height of the outline element
`setOutlinePathAttr(value: string) => void` | Sets the "d" attribute of the outline element's SVG path

### `MDCTextFieldOutlineFoundation`

Updates the SVG path of the focus outline element based on the given width and height of the text field element, the width of the label element, the corner radius, and the RTL context.
Method Signature | Description
--- | ---
`updateSvgPath(labelWidth: number, radius: number, isRtl: boolean) => void` | Updates the SVG path of the focus outline element based on the given the width of the label element, the corner radius, and the RTL context.
13 changes: 9 additions & 4 deletions packages/mdc-textfield/outline/mdc-text-field-outline.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
}

.mdc-text-field__outline {
@include mdc-ripple-surface;
@include mdc-ripple-radius;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Move this above mdc-states, below mdc-ripple-surface

@include mdc-states-base-color(text-primary-on-light);
@include mdc-states-press-opacity(0.12);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should reference map-get($mdc-ripple-dark-ink-opacities, "press") (which is .16, not .12).

Also, we should probably include styles for dark mode passing text-primary-on-dark and map-get($mdc-ripple-light-ink-opacities, "press").

(There's no dark mode toggle under the outlined text field example at the moment either; should we add one?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call.

I plan on adding dark mode along with dense mode to the PR for adding leading/trailing icons.

@include mdc-theme-prop(color, primary);
@include mdc-text-field-outlined-corner-radius($mdc-text-field-border-radius);

Expand All @@ -44,17 +48,18 @@
transition: mdc-text-field-transition(opacity);
opacity: 0;
z-index: 2;
overflow: hidden;

svg {
position: absolute;
width: 100%;
height: 100%;

.mdc-text-field__outline-path {
@include mdc-theme-prop(stroke, primary);

stroke-width: 2px;
transition: mdc-text-field-transition(stroke-width), mdc-text-field-transition(opacity);
stroke: $mdc-text-field-outlined-idle-border;
stroke-width: 1px;
transition: mdc-text-field-transition(stroke), mdc-text-field-transition(stroke-width),
mdc-text-field-transition(opacity);
fill: transparent;
}
}
Expand Down
33 changes: 32 additions & 1 deletion test/unit/mdc-textfield/mdc-text-field.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class FakeLabel {

class FakeOutline {
constructor() {
this.createRipple = td.function('.createRipple');
this.destroy = td.func('.destroy');
}
}
Expand All @@ -87,7 +88,23 @@ test('#constructor when given a `mdc-text-field--box` element instantiates a rip
assert.equal(component.ripple.root, root);
});

test('#constructor sets the ripple property to `null` when given a non `mdc-text-field--box` element', () => {
test('#constructor when given a `mdc-text-field--outlined` element instantiates a ripple on the ' +
'outline element', () => {
const root = bel`
<div class="mdc-text-field mdc-text-field--outlined">
<input type="text" class="mdc-text-field__input" id="my-text-field">
<label class="mdc-text-field__label" for="my-text-field">My Label</label>
<div class="mdc-text-field__outline"></div>
<div class="mdc-text-field__idle-outline"></div>
</div>
`;
const outline = root.querySelector('.mdc-text-field__outline');
const component = new MDCTextField(root, undefined, (el) => new FakeRipple(el));
assert.equal(component.ripple.root, outline);
});

test('#constructor sets the ripple property to `null` when not given a `mdc-text-field--box` nor ' +
'a `mdc-text-field--outlined` subelement', () => {
const component = new MDCTextField(getFixture());
assert.isNull(component.ripple);
});
Expand All @@ -100,6 +117,20 @@ test('#constructor when given a `mdc-text-field--box` element, initializes a def
assert.instanceOf(component.ripple, MDCRipple);
});

test('#constructor when given a `mdc-text-field--outlined` element, initializes a default ripple when no ' +
'ripple factory given', () => {
const root = bel`
<div class="mdc-text-field mdc-text-field--outlined">
<input type="text" class="mdc-text-field__input" id="my-text-field">
<label class="mdc-text-field__label" for="my-text-field">My Label</label>
<div class="mdc-text-field__outline"></div>
<div class="mdc-text-field__idle-outline"></div>
</div>
`;
const component = new MDCTextField(root);
assert.instanceOf(component.ripple, MDCRipple);
});

test('#constructor instantiates a bottom line on the `.mdc-text-field__bottom-line` element if present', () => {
const root = getFixture();
const component = new MDCTextField(root);
Expand Down