Skip to content

Commit

Permalink
feat(select): add javascript (#41)
Browse files Browse the repository at this point in the history
* feat(select): add javascripts

* delete comment

* delete file

* cleans up code, adds specs

* tweak animation
  • Loading branch information
adamraider authored and Adam Raider committed Feb 27, 2019
1 parent 546a8af commit e9c1cfe
Show file tree
Hide file tree
Showing 12 changed files with 1,456 additions and 58 deletions.
13 changes: 13 additions & 0 deletions packages/ray/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current'
}
}
]
],
plugins: ['@babel/plugin-proposal-class-properties']
};
9 changes: 9 additions & 0 deletions packages/ray/lib/components/select/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## JavaScript

```javascript
import { Select } from '@wework/ray';

Select.createAll();
// or
Select.create(document.querySelector('.ray-select'));
```
10 changes: 10 additions & 0 deletions packages/ray/lib/components/select/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const CSS_CLASSES = {
ACTIVE: 'ray-select--active',
HAS_VALUE: 'ray-select--has-value',
PLACEHOLDER_MODE: 'ray-select--placeholder-mode',
EL__INPUT: 'ray-select__input'
};

export const STRINGS = {
INIT_SELECTOR: 'ray-select'
};
118 changes: 88 additions & 30 deletions packages/ray/lib/components/select/index.js
Original file line number Diff line number Diff line change
@@ -1,67 +1,125 @@
const SELECT_MODIFIER_ACTIVE = 'ray-select--active';
const SELECT_EL_INPUT = 'ray-select__input';
const SELECT_MODIFIER_HAS_VALUE = 'ray-select--has-value';
const SELECT_MODIFIER_PLACEHOLDER_MODE = 'ray-select--placeholder-mode';
import { CSS_CLASSES, STRINGS } from './constants';
import { validateNodeType, isTargetingItself } from './util';

class Select {
constructor(element, options) {
this.element = element;
this.inputElement = element.querySelector(`.${SELECT_EL_INPUT}`);
this.bindEventListeners();
static instances = new WeakMap();

const option = this.getCurrentValueOptionElement();
static get cssClasses() {
return CSS_CLASSES;
}

static get strings() {
return STRINGS;
}

static create(element, options) {
return this.instances.get(element) || new this(element, options);
}

static createAll(target = document, _options = {}) {
// Finds all instances of select on the document or within a given element and instantiates them.
const options = {
initSelector: this.strings.INIT_SELECTOR,
..._options
};

validateNodeType(target);

if (isTargetingItself(target, options)) {
this.create(target, options);
} else {
const selects = [...target.querySelectorAll(options.initSelector)];
return selects.forEach(select => this.create(select, options));
}
}

constructor(root, options) {
this._root = root;
this._inputElement = this._root.querySelector(
`.${this.constructor.cssClasses.EL__INPUT}`
);

if (!this._inputElement) {
throw new Error(
`Select must have an input element with a class of .${
this.constructor.cssClasses.EL__INPUT
}`
);
}

this._bindEventListeners();

const option = this._getCurrentValueOptionElement();

if (option && option.innerHTML) {
if (option.dataset.rayPlaceholder) {
this.element.classList.add(SELECT_MODIFIER_PLACEHOLDER_MODE);
this._root.classList.add(this.constructor.cssClasses.PLACEHOLDER_MODE);
} else {
this.element.classList.add(SELECT_MODIFIER_HAS_VALUE);
this._root.classList.add(this.constructor.cssClasses.HAS_VALUE);
}
}

this.constructor.instances.set(this._root, this);
}

bindEventListeners() {
this.inputElement.addEventListener('focus', this.onFocus);
this.inputElement.addEventListener('blur', this.onBlur);
this.inputElement.addEventListener('change', this.onChange);
_bindEventListeners() {
this._inputElement.addEventListener('focus', this.onFocus);
this._inputElement.addEventListener('blur', this.onBlur);
this._inputElement.addEventListener('change', this.onChange);
}

value() {
return this.element.value;
// Current value of the Select
return this._inputElement.value;
}

set(value) {
this._inputElement.value = value;
this._inputElement.dispatchEvent(new Event('change'));
}

onFocus = () => {
this.element.classList.add(SELECT_MODIFIER_ACTIVE);
this._root.classList.add(this.constructor.cssClasses.ACTIVE);
};

onBlur = () => {
this.element.classList.remove(SELECT_MODIFIER_ACTIVE);
this._root.classList.remove(this.constructor.cssClasses.ACTIVE);
};

onChange = () => {
const option = this.getCurrentValueOptionElement();
const option = this._getCurrentValueOptionElement();

if (option) {
if (option.dataset.rayPlaceholder) {
this.element.classList.add(SELECT_MODIFIER_PLACEHOLDER_MODE);
this.element.classList.remove(SELECT_MODIFIER_HAS_VALUE);
this._root.classList.add(this.constructor.cssClasses.PLACEHOLDER_MODE);
this._root.classList.remove(this.constructor.cssClasses.HAS_VALUE);
} else {
this.element.classList.add(SELECT_MODIFIER_HAS_VALUE);
this.element.classList.remove(SELECT_MODIFIER_PLACEHOLDER_MODE);
this._root.classList.add(this.constructor.cssClasses.HAS_VALUE);
this._root.classList.remove(
this.constructor.cssClasses.PLACEHOLDER_MODE
);
}
} else {
this.element.classList.remove(
SELECT_MODIFIER_PLACEHOLDER_MODE,
SELECT_MODIFIER_HAS_VALUE
this._root.classList.remove(
this.constructor.cssClasses.PLACEHOLDER_MODE,
this.constructor.cssClasses.HAS_VALUE
);
}
};

getCurrentValueOptionElement = () => {
return this.inputElement.options[this.inputElement.selectedIndex];
_getCurrentValueOptionElement = () => {
return this._inputElement.options[this._inputElement.selectedIndex];
};

teardownEventListeners() {}
destroy() {
// Implement this method to release any resources / deregister any listeners they have
// attached. An example of this might be deregistering a resize event from the window object.
this._inputElement.removeEventListener('focus', this.onFocus);
this._inputElement.removeEventListener('blur', this.onBlur);
this._inputElement.removeEventListener('change', this.onChange);

this.constructor.instances.delete(this._root);
}
}

export { Select };
export default Select;
12 changes: 12 additions & 0 deletions packages/ray/lib/components/select/select.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<div class="ray-select">
<select class="ray-select__input">
<option value="" disabled selected data-ray-placeholder />
<option value="Pikatchu">Pikatchu</option>
<option value="Squirtle">Squirtle</option>
<option value="Squirtle">Charmander</option>
</select>

<label class="ray-select__label">
What's your favorite Pokémon?
</label>
</div>
19 changes: 19 additions & 0 deletions packages/ray/lib/components/select/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
function validateNodeType(target) {
if (
target.nodeType !== Node.ELEMENT_NODE &&
target.nodeType !== Node.DOCUMENT_NODE
) {
throw new TypeError(
'DOM document or DOM element should be given to search for and initialize this widget.'
);
}
}

function isTargetingItself(target, options) {
return (
target.nodeType === Node.ELEMENT_NODE &&
target.matches(options.selectorInit)
);
}

export { validateNodeType, isTargetingItself };
3 changes: 3 additions & 0 deletions packages/ray/lib/global/js/settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
prefix: 'ray-'
};
2 changes: 1 addition & 1 deletion packages/ray/lib/global/mixins/_form-items.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
padding-right: $ray-field-h-spacing;
padding-top: $ray-field-v-spacing;
color: $ray-color-text-medium;
transition: padding 0.2s ease;
transition: padding 0.2s ease, top 0.2s ease, left 0.2s ease;
height: 100%;
width: 100%;
max-width: calc(100% - 12px);
Expand Down
5 changes: 4 additions & 1 deletion packages/ray/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"author": "Adam Raider <adam.raider@wework.com>",
"scripts": {
"commit": "git-cz",
"test": "echo \"Error: no test specified\"",
"test": "jest",
"storybook": "start-storybook -s ./stories/static -p 6006",
"build-storybook": "NODE_ENV=production build-storybook -c .storybook -o .out -s ./stories/static",
"build": "yarn prebuild && gulp html:source sass:source sass:compiled",
Expand All @@ -19,6 +19,7 @@
"dependencies": {},
"devDependencies": {
"@babel/core": "7.2.2",
"@babel/plugin-proposal-class-properties": "7.3.0",
"@commitlint/cli": "7.5.2",
"@commitlint/config-conventional": "7.5.0",
"@storybook/addon-backgrounds": "4.1.11",
Expand All @@ -39,9 +40,11 @@
"gulp-uglify": "3.0.1",
"gulp-util": "3.0.8",
"husky": "1.3.1",
"jest": "24.1.0",
"lint-staged": "8.1.3",
"lodash": "4.17.11",
"mini-css-extract-plugin": "0.4.4",
"nanohtml": "1.4.0",
"node-sass": "4.11.0",
"optimize-css-assets-webpack-plugin": "5.0.1",
"postcss-loader": "3.0.0",
Expand Down
7 changes: 4 additions & 3 deletions packages/ray/stories/select.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import React from 'react';
import { storiesOf } from '@storybook/react';

import withPadding from './util/withPadding';
import { Select } from '../lib/components/select';
import Select from '../lib/components/select';

let idNumber = 0;

function init() {
const selects = document.querySelectorAll('.ray-select');
selects.forEach(select => new Select(select));
Select.createAll(document, {
initSelector: '.ray-select'
});
}

storiesOf('Select', module)
Expand Down
Loading

0 comments on commit e9c1cfe

Please sign in to comment.