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

feat(text-field): Add dense mode to outlined text field #1846

Merged
merged 16 commits into from
Jan 3, 2018
Merged
Show file tree
Hide file tree
Changes from 13 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
8 changes: 8 additions & 0 deletions demos/text-field.html
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ <h2>Outlined Text Field</h2>
<input id="outlined-dark-theme" type="checkbox">
<label for="outlined-dark-theme">Dark Theme</label>
</div>
<div>
<input id="outlined-dense" type="checkbox">
<label for="outlined-dense">Dense</label>
</div>
<script>
setTimeout(function() {
var tfEl = document.getElementById('tf-outlined-example');
Expand All @@ -200,6 +204,10 @@ <h2>Outlined Text Field</h2>
document.getElementById('outlined-dark-theme').addEventListener('change', function(evt) {
wrapper.classList[evt.target.checked ? 'add' : 'remove']('mdc-theme--dark');
});
document.getElementById('outlined-dense').addEventListener('change', function(evt) {
tfEl.classList[evt.target.checked ? 'add' : 'remove']('mdc-text-field--dense');
tf.layout();
});
}, 0);
</script>
</section>
Expand Down
1 change: 1 addition & 0 deletions packages/mdc-textfield/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ Method Signature | Description
--- | ---
`addClass(className: string) => void` | Adds a class to the root element
`removeClass(className: string) => void` | Removes a class from the root element
`hasClass(className: string) => boolean` | Returns true if the root element contains the given class name
`registerTextFieldInteractionHandler(evtType: string, handler: EventListener)` => void | Registers an event handler on the root element for a given event
`deregisterTextFieldInteractionHandler(evtType: string, handler: EventListener)` => void | Deregisters an event handler on the root element for a given event
`registerInputInteractionHandler(evtType: string, handler: EventListener)` => void | Registers an event listener on the native input element for a given event
Expand Down
10 changes: 5 additions & 5 deletions packages/mdc-textfield/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,24 @@
}
}

@mixin mdc-text-field-invalid-label-shake-keyframes_($modifier, $positionY) {
@mixin mdc-text-field-invalid-label-shake-keyframes_($modifier, $positionY, $scale: .75) {
@keyframes invalid-shake-float-above-#{$modifier} {
0% {
transform: translateX(0) translateY(-#{$positionY}) scale(.75, .75);
transform: translateX(0) translateY(-#{$positionY}) scale(#{$scale});
}

33% {
animation-timing-function: cubic-bezier(.5, 0, .701732, .495819);
transform: translateX(5px) translateY(-#{$positionY}) scale(.75, .75);
transform: translateX(5px) translateY(-#{$positionY}) scale(#{$scale});
}

66% {
animation-timing-function: cubic-bezier(.302435, .381352, .55, .956352);
transform: translateX(-5px) translateY(-#{$positionY}) scale(.75, .75);
transform: translateX(-5px) translateY(-#{$positionY}) scale(#{$scale});
}

100% {
transform: translateX(0) translateY(-#{$positionY}) scale(.75, .75);
transform: translateX(0) translateY(-#{$positionY}) scale(#{$scale});
}
}
}
6 changes: 6 additions & 0 deletions packages/mdc-textfield/adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ class MDCTextFieldAdapter {
*/
removeClass(className) {}

/**
* Returns true if the root element contains the given class name.
* @param {string} className
*/
hasClass(className) {}

/**
* Registers an event handler on the root element for a given event.
* @param {string} type
Expand Down
9 changes: 8 additions & 1 deletion packages/mdc-textfield/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,17 @@ const cssClasses = {
ROOT: 'mdc-text-field',
UPGRADED: 'mdc-text-field--upgraded',
DISABLED: 'mdc-text-field--disabled',
DENSE: 'mdc-text-field--dense',
FOCUSED: 'mdc-text-field--focused',
INVALID: 'mdc-text-field--invalid',
BOX: 'mdc-text-field--box',
OUTLINED: 'mdc-text-field--outlined',
};

export {cssClasses, strings};
/** @enum {number} */
const numbers = {
LABEL_SCALE: 0.75,
DENSE_LABEL_SCALE: 0.923,
};

export {cssClasses, strings, numbers};
13 changes: 11 additions & 2 deletions packages/mdc-textfield/foundation.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import MDCTextFieldIconFoundation from './icon/foundation';
import MDCTextFieldLabelFoundation from './label/foundation';
import MDCTextFieldOutlineFoundation from './outline/foundation';
/* eslint-enable no-unused-vars */
import {cssClasses, strings} from './constants';
import {cssClasses, strings, numbers} from './constants';


/**
Expand All @@ -42,6 +42,11 @@ class MDCTextFieldFoundation extends MDCFoundation {
return strings;
}

/** @return enum {string} */
static get numbers() {
return numbers;
}

/**
* {@see MDCTextFieldAdapter} for typing information on parameters and return
* types.
Expand All @@ -51,6 +56,7 @@ class MDCTextFieldFoundation extends MDCFoundation {
return /** @type {!MDCTextFieldAdapter} */ ({
addClass: () => {},
removeClass: () => {},
hasClass: () => {},
registerTextFieldInteractionHandler: () => {},
deregisterTextFieldInteractionHandler: () => {},
registerInputInteractionHandler: () => {},
Expand Down Expand Up @@ -159,7 +165,10 @@ class MDCTextFieldFoundation extends MDCFoundation {
if (!this.outline_ || !this.label_) {
return;
}
const labelWidth = this.label_.getFloatingWidth();

const isDense = this.adapter_.hasClass(cssClasses.DENSE);
const labelScale = isDense ? numbers.DENSE_LABEL_SCALE : numbers.LABEL_SCALE;
const labelWidth = this.label_.getWidth() * labelScale;
// Fall back to reading a specific corner's style because Firefox doesn't report the style on border-radius.
const radiusStyleValue = this.adapter_.getIdleOutlineStyleValue('border-radius') ||
this.adapter_.getIdleOutlineStyleValue('border-top-left-radius');
Expand Down
1 change: 1 addition & 0 deletions packages/mdc-textfield/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ class MDCTextField extends MDCComponent {
/** @type {!MDCTextFieldAdapter} */ (Object.assign({
addClass: (className) => this.root_.classList.add(className),
removeClass: (className) => this.root_.classList.remove(className),
hasClass: (className) => this.root_.classList.contains(className),
registerTextFieldInteractionHandler: (evtType, handler) => this.root_.addEventListener(evtType, handler),
deregisterTextFieldInteractionHandler: (evtType, handler) => this.root_.removeEventListener(evtType, handler),
registerBottomLineEventHandler: (evtType, handler) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/mdc-textfield/label/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,4 @@ Method Signature | Description
`floatAbove() => void` | Makes the label float above the text field
`deactivateFocus(shouldRemoveLabelFloat: boolean) => void` | Deactivates the label's focus state. `shouldRemoveLabelFloat` indicates whether to also reset the label's position and size.
`setValidity(isValid: boolean)` | Updates the label's valid state based on the supplied validity
`getFloatingWidth() => number` | Returns the width of the label element when it floats above the text field
`getWidth() => number` | Returns the width of the label element
7 changes: 3 additions & 4 deletions packages/mdc-textfield/label/foundation.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,11 @@ class MDCTextFieldLabelFoundation extends MDCFoundation {
}

/**
* Returns the width of the label element when it floats above the text field.
* Returns the width of the label element.
* @return {number}
*/
getFloatingWidth() {
// The label is scaled 75% when it floats above the text field.
return this.adapter_.getWidth() * 0.75;
getWidth() {
return this.adapter_.getWidth();
}

/** Makes the label float above the text field. */
Expand Down
27 changes: 24 additions & 3 deletions packages/mdc-textfield/mdc-text-field.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
@include mdc-text-field-invalid-label-shake-keyframes_(standard, 100%);
@include mdc-text-field-invalid-label-shake-keyframes_(box, 50%);
@include mdc-text-field-invalid-label-shake_keyframes_(outlined, 130%);
@include mdc-text-field-invalid-label-shake_keyframes_(outlined-dense, 138%, .923);

// postcss-bem-linter: define text-field

Expand Down Expand Up @@ -142,7 +143,6 @@

position: absolute;
bottom: 20px;
left: 16px;
transition: transform 260ms ease;
z-index: 2;

Expand Down Expand Up @@ -198,6 +198,29 @@
}
}

&.mdc-text-field--dense {
height: 48px;

.mdc-text-field__input {
padding: 12px 12px 7px;
}

.mdc-text-field__label {
bottom: 18px;

&--float-above {
transform: translateY(calc(-122% - 2px)) scale(.923, .923);
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove second .923


// stylelint-disable max-nesting-depth
// stylelint-disable-next-line selector-max-specificity
&.mdc-text-field__label--shake {
animation: invalid-shake-float-above-outlined-dense 250ms 1;
}
// stylelint-enable max-nesting-depth
}
}
}

// stylelint-enable plugin/selector-bem-pattern
}

Expand Down Expand Up @@ -295,8 +318,6 @@
}

.mdc-text-field__label {
@include mdc-rtl-reflexive-position(left, 12px);

bottom: 20px;

&--float-above {
Expand Down
26 changes: 15 additions & 11 deletions packages/mdc-textfield/outline/foundation.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,28 +57,32 @@ class MDCTextFieldOutlineFoundation extends MDCFoundation {
* @param {boolean=} isRtl
*/
updateSvgPath(labelWidth, radius, isRtl = false) {
const width = this.adapter_.getWidth() + 2;
const height = this.adapter_.getHeight() + 2;
const width = this.adapter_.getWidth();
const height = this.adapter_.getHeight();
const cornerWidth = radius + 1.2;
const leadingStrokeLength = Math.abs(11 - cornerWidth);
const paddedLabelWidth = labelWidth + 8;

// The right, bottom, and left sides of the outline follow the same SVG path.
const pathMiddle = 'a' + radius + ',' + radius + ' 0 0 1 ' + radius + ',' + radius
+ 'v' + (height - 2 * (radius + 2.1))
+ 'v' + (height - (2 * cornerWidth))
+ 'a' + radius + ',' + radius + ' 0 0 1 ' + -radius + ',' + radius
+ 'h' + (-width + 2 * (radius + 1.7))
+ 'h' + (-width + (2 * cornerWidth))
+ 'a' + radius + ',' + radius + ' 0 0 1 ' + -radius + ',' + -radius
+ 'v' + (-height + 2 * (radius + 2.1))
+ 'v' + (-height + (2 * cornerWidth))
+ 'a' + radius + ',' + radius + ' 0 0 1 ' + radius + ',' + -radius;

let path;
if (!isRtl) {
path = 'M' + (radius + 2.1 + Math.abs(10 - radius) + labelWidth + 8) + ',' + 1
+ 'h' + (width - (2 * (radius + 2.1)) - labelWidth - 8.5 - Math.abs(10 - radius))
path = 'M' + (cornerWidth + leadingStrokeLength + paddedLabelWidth) + ',' + 1
+ 'h' + (width - (2 * cornerWidth) - paddedLabelWidth - leadingStrokeLength)
+ pathMiddle
+ 'h' + Math.abs(10 - radius);
+ 'h' + leadingStrokeLength;
} else {
path = 'M' + (width - radius - 2.1 - Math.abs(10 - radius)) + ',' + 1
+ 'h' + Math.abs(10 - radius)
path = 'M' + (width - cornerWidth - leadingStrokeLength) + ',' + 1
+ 'h' + leadingStrokeLength
+ pathMiddle
+ 'h' + (width - (2 * (radius + 2.1)) - labelWidth - 8.5 - Math.abs(10 - radius));
+ 'h' + (width - (2 * cornerWidth) - paddedLabelWidth - leadingStrokeLength);
}

this.adapter_.setOutlinePathAttr(path);
Expand Down
26 changes: 21 additions & 5 deletions test/unit/mdc-textfield/foundation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {verifyDefaultAdapter} from '../helpers/foundation';
import MDCTextFieldFoundation from '../../../packages/mdc-textfield/foundation';
import MDCTextFieldBottomLineFoundation from '../../../packages/mdc-textfield/bottom-line/foundation';

const {cssClasses} = MDCTextFieldFoundation;
const {cssClasses, numbers} = MDCTextFieldFoundation;

suite('MDCTextFieldFoundation');

Expand All @@ -33,9 +33,13 @@ test('exports cssClasses', () => {
assert.isOk('cssClasses' in MDCTextFieldFoundation);
});

test('exports numbers', () => {
assert.isOk('numbers' in MDCTextFieldFoundation);
});

test('defaultAdapter returns a complete adapter implementation', () => {
verifyDefaultAdapter(MDCTextFieldFoundation, [
'addClass', 'removeClass',
'addClass', 'removeClass', 'hasClass',
'registerTextFieldInteractionHandler', 'deregisterTextFieldInteractionHandler',
'registerInputInteractionHandler', 'deregisterInputInteractionHandler',
'registerBottomLineEventHandler', 'deregisterBottomLineEventHandler',
Expand Down Expand Up @@ -63,7 +67,7 @@ const setupTest = () => {
handleInteraction: () => {},
});
const label = td.object({
getFloatingWidth: () => {},
getWidth: () => {},
floatAbove: () => {},
deactivateFocus: () => {},
setValidity: () => {},
Expand Down Expand Up @@ -231,12 +235,24 @@ test('#setHelperTextContent sets the content of the helper text element', () =>

test('#updateOutline updates the SVG path of the outline element', () => {
const {foundation, mockAdapter, label, outline} = setupTest();
td.when(label.getFloatingWidth()).thenReturn(30);
td.when(label.getWidth()).thenReturn(30);
td.when(mockAdapter.hasClass(cssClasses.DENSE)).thenReturn(false);
td.when(mockAdapter.getIdleOutlineStyleValue('border-radius')).thenReturn('8px');
td.when(mockAdapter.isRtl()).thenReturn(false);

foundation.updateOutline();
td.verify(outline.updateSvgPath(30 * numbers.LABEL_SCALE, 8, false));
});

test('#updateOutline updates the SVG path of the outline element when dense', () => {
const {foundation, mockAdapter, label, outline} = setupTest();
td.when(label.getWidth()).thenReturn(30);
td.when(mockAdapter.hasClass(cssClasses.DENSE)).thenReturn(true);
td.when(mockAdapter.getIdleOutlineStyleValue('border-radius')).thenReturn('8px');
td.when(mockAdapter.isRtl()).thenReturn(false);

foundation.updateOutline();
td.verify(outline.updateSvgPath(30, 8, false));
td.verify(outline.updateSvgPath(30 * numbers.DENSE_LABEL_SCALE, 8, false));
});

test('on input floats label if input event occurs without any other events', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ test('defaultAdapter returns a complete adapter implementation', () => {

const setupTest = () => setupFoundationTest(MDCTextFieldLabelFoundation);

test('#getFloatingWidth returns the width of the label element scaled by 75%', () => {
test('#getWidth returns the width of the label element scaled by 75%', () => {
const {foundation, mockAdapter} = setupTest();
const width = 100;
td.when(mockAdapter.getWidth()).thenReturn(width);
assert.equal(foundation.getFloatingWidth(), width * 0.75);
assert.equal(foundation.getWidth(), width);
});

test('#floatAbove adds mdc-text-field__label--float-above class', () => {
Expand Down