Skip to content

Instantly share code, notes, and snippets.

@MeoMix
Created January 28, 2016 22:33
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MeoMix/4241564ebe32eeb725ab to your computer and use it in GitHub Desktop.
Save MeoMix/4241564ebe32eeb725ab to your computer and use it in GitHub Desktop.
<h1 class='{{styles.title}}'>
Contact
</h1>
<h4 class='{{styles.description}}'>
Got somethin' to say? Drop me a line!
</h4>
<div class='{{styles.card}}'>
All fields are required.
<form>
<div class='{{styles.inputs}}'>
<streamus-input maxlength='{{nameMaxLength}}' placeholder='Name' required></streamus-input>
<streamus-input maxlength='{{emailMaxLength}}' placeholder='Email' required></streamus-input>
<streamus-input maxlength='{{descriptionMaxLength}}' placeholder='Message' required multiline></streamus-input>
</div>
<div class='{{styles.buttons}}'>
<div class='{{styles.button}}'>
Send Message
</div>
</div>
</form>
</div>
.input {
}
.textInput {
width: 100%;
/*
This needs to be 7px because of 1px border-bottom, otherwise I get into odd-pixel math.
I need 1px border-bottom due to a bug in Chrome: http://stackoverflow.com/questions/26951053/jumpy-transition-when-only-modifying-box-shadow-looks-fine-with-box-shadow-bo
*/
padding: 8px 0 7px 0;
font: inherit;
color: inherit;
background-color: transparent;
border-top: none;
border-right: none;
border-left: none;
border-bottom-width: 1px;
outline: none;
transition: box-shadow .1s cubic-bezier(.39,.575,.565,1), border .1s cubic-bezier(.39,.575,.565,1);
margin-bottom: 8px;
border-bottom-color: rgba(0, 0, 0, .26);
font-size: 16px;
line-height: 16px;
height: 36px;
}
.textInput:not(.isInvalid):focus {
border-bottom-color: rgb(66,133,244);
box-shadow: inset 0 -1px rgb(66,133,244);
}
.isInvalid:focus {
border-bottom-color: rgb(219, 68, 55);
box-shadow: inset 0 -1px rgb(219, 68, 55);
}
.isInvalid:focus + .characterCountHint {
color: rgb(219,68,55);
}
.inputWrapper.hasValue .placeholder {
transform: translate(-12.5%, 0) scale(0.75);
}
.placeholder {
font-size: 16px;
/* 12px because when scaled down by 0.75 it's 12px tall. */
line-height: 12px;
color: rgba(0, 0, 0, .54);
display: inline-block;
transform: translate(0, 25px) scale(1);
transition: transform .2s cubic-bezier(.39, .575, .565, 1);
pointer-events: none;
}
.inputWrapper {
display: flex;
flex-direction: column;
padding-top: 16px;
}
.characterCount {
height: 14px;
font-size: 12px;
composes: darkSecondary from 'color';
cursor: default;
text-align: right;
}
<div class='{{styles.inputWrapper}}' data-ui='inputWrapper'>
<label class='{{styles.placeholder}}'>{{placeholder}}{{ternary isRequired '*' ''}}</label>
<!-- TODO: I wonder if textarea is really the correct decision here. -->
{{#if isMultiline}}
<textarea data-ui='input' class='{{styles.textInput}} {{styles.isInvalid}}' maxlength='{{maxLength}}' rows='1'>{{value}}</textarea>
{{else}}
<input data-ui='input' class='{{styles.textInput}} {{styles.isInvalid}}' type='text' maxlength='{{maxLength}}' value='{{value}}'>
{{/if}}
</div>
<div class='{{styles.characterCount}}'>
<span data-ui='characterCount'>0</span> / {{maxLength}}
</div>
import { Model } from 'backbone';
import _ from 'lodash';
export default Model.extend({
defaults: {
placeholder: '',
value: '',
maxLength: -1,
isValid: false,
isRequired: false,
isMultiline: false
},
initialize() {
this.on('change:value', this._onChangeValue);
},
setInitialValues(initialValues) {
_.forOwn(initialValues, (value, key) => {
if (!_.isUndefined(value) && !_.isNaN(value)) {
this.set(key, value);
}
});
},
_onChangeValue(model, value) {
let isValid = value.length > 0;
const maxLength = this.get('maxLength');
if (isValid && maxLength >= 0) {
isValid = value.length <= maxLength;
}
this.set('isValid', isValid);
}
});
import { LayoutView } from 'marionette';
import template from './input.hbs!';
import styles from './input.css!';
import Input from './input';
import _ from 'lodash';
const InputView = LayoutView.extend({
tagName: 'streamus-input',
className: styles.input,
template,
templateHelpers: {
styles: styles
},
ui: {
input: 'input',
inputWrapper: 'inputWrapper',
characterCount: 'characterCount'
},
events: {
'input': '_onInput'
},
modelEvents: {
'change:value': '_onChangeValue',
'change:isValid': '_onChangeIsValid'
},
initialize() {
this.model.setInitialValues({
placeholder: this.$el.attr('placeholder'),
isRequired: this.el.hasAttribute('required'),
isMultiline: this.el.hasAttribute('multiline'),
value: _.parseInt(this.$el.attr('value')),
maxLength: _.parseInt(this.$el.attr('maxlength'))
});
},
_onInput() {
this.model.set('value', this.ui.input.val());
},
_onChangeValue(model, value) {
this.ui.characterCount.text(value.length);
this.ui.inputWrapper.toggleClass(styles.hasValue, value.length > 0);
if (model.get('isMultiline')) {
// If height is not set to 'auto' then input will not shrink on text deletion.
if (model.previous('value').length > value.length) {
this.ui.value.height('auto');
}
this.ui.value.innerHeight(this.ui.value[0].scrollHeight);
}
},
_onChangeIsValid(model, isValid) {
this.ui.input.toggleClass(styles.isInvalid, !isValid);
}
});
document.registerElement('streamus-input', {
prototype: _.extend(Object.create(HTMLElement.prototype), {
createdCallback() {
const inputView = new InputView({
el: this,
model: new Input()
});
inputView.render();
this._view = inputView;
},
attachedCallback() {
this._view.triggerMethod('attach');
},
detachedCallback() {
this._view.destroy();
delete this._view;
}
})
});
export default InputView;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment