Skip to content

Commit

Permalink
feat(text-field): Add ripple to outlined text field (#1807)
Browse files Browse the repository at this point in the history
  • Loading branch information
bonniezhou committed Dec 21, 2017
1 parent 737f712 commit 49fc1c4
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 32 deletions.
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.
14 changes: 10 additions & 4 deletions packages/mdc-textfield/outline/mdc-text-field-outline.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
@import "../mixins";
@import "../variables";
@import "@material/theme/mixins";
@import "@material/ripple/variables";

.mdc-text-field__idle-outline {
@include mdc-text-field-outlined-corner-radius($mdc-text-field-border-radius);
Expand All @@ -33,6 +34,10 @@
}

.mdc-text-field__outline {
@include mdc-ripple-surface;
@include mdc-ripple-radius;
@include mdc-states-base-color(text-primary-on-light);
@include mdc-states-press-opacity(map-get($mdc-ripple-dark-ink-opacities, "press"));
@include mdc-theme-prop(color, primary);
@include mdc-text-field-outlined-corner-radius($mdc-text-field-border-radius);

Expand All @@ -44,17 +49,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

0 comments on commit 49fc1c4

Please sign in to comment.