diff --git a/.gitignore b/.gitignore
index 123ae94..4787bc4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,6 @@ build/Release
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
+
+# Ignore compiled files
+lib
diff --git a/.zuul.yml b/.zuul.yml
index 3098ff9..71c8396 100644
--- a/.zuul.yml
+++ b/.zuul.yml
@@ -3,4 +3,4 @@ browsers:
- name: chrome
version: latest
browserify:
- - transform: reactify
\ No newline at end of file
+ - transform: babelify
diff --git a/components/Dropdown.js b/components/Dropdown.js
deleted file mode 100644
index 4412882..0000000
--- a/components/Dropdown.js
+++ /dev/null
@@ -1,82 +0,0 @@
-var React = require('react');
-var cx = require('classnames');
-
-var DropdownTrigger = require('./DropdownTrigger.js');
-var DropdownContent = require('./DropdownContent.js');
-
-var Dropdown = React.createClass({
- getInitialState: function(){
- return {
- active: false
- };
- },
- componentDidMount: function () {
- window.addEventListener( 'click', this._onWindowClick );
- },
- componentWillUnmount: function () {
- window.removeEventListener( 'click', this._onWindowClick );
- },
- render: function () {
- // create component classes
- var active = this.isActive();
- var dropdown_classes = cx({
- dropdown: true,
- 'dropdown--active': active
- });
- if( this.props.className ){
- dropdown_classes += ' ' + this.props.className;
- }
- // stick callback on trigger element
- var children = React.Children.map( this.props.children, function( child ){
- if( child.type === DropdownTrigger ){
- child = React.cloneElement( child, {
- ref: 'trigger',
- onClick: this._onToggleClick
- });
- }
- return child;
- }, this);
- return React.createElement('div', {
- className: dropdown_classes
- }, children);
- },
- isActive: function(){
- return ( typeof this.props.active === 'boolean' ) ?
- this.props.active :
- this.state.active;
- },
- hide: function(){
- this.setState({
- active: false
- });
- if( this.props.onHide ){
- this.props.onHide();
- }
- },
- show: function(){
- this.setState({
- active: true
- });
- if( this.props.onShow ){
- this.props.onShow();
- }
- },
- _onWindowClick: function( event ){
- var dropdown_element = this.getDOMNode();
- if( event.target !== dropdown_element && !dropdown_element.contains( event.target ) && this.isActive() ){
- this.hide();
- }
- },
- _onToggleClick: function( event ){
- event.preventDefault();
- if( this.isActive() ){
- this.hide();
- } else {
- this.show();
- }
- }
-});
-
-module.exports = Dropdown;
-module.exports.DropdownTrigger = DropdownTrigger;
-module.exports.DropdownContent = DropdownContent;
\ No newline at end of file
diff --git a/components/Dropdown.jsx b/components/Dropdown.jsx
new file mode 100644
index 0000000..5062007
--- /dev/null
+++ b/components/Dropdown.jsx
@@ -0,0 +1,84 @@
+import React, { cloneElement, createClass } from 'react';
+import { findDOMNode } from 'react-dom';
+import cx from 'classnames';
+
+import DropdownTrigger from './DropdownTrigger.js';
+import DropdownContent from './DropdownContent.js';
+
+var Dropdown = createClass({
+ getInitialState: function(){
+ return {
+ active: false
+ };
+ },
+ getDefaultProps: function(){
+ return {
+ className: ''
+ }
+ },
+ componentDidMount: function () {
+ window.addEventListener( 'click', this._onWindowClick );
+ },
+ componentWillUnmount: function () {
+ window.removeEventListener( 'click', this._onWindowClick );
+ },
+ render: function () {
+ const { children, className } = this.props;
+ // create component classes
+ const active = this.isActive();
+ var dropdown_classes = cx({
+ dropdown: true,
+ 'dropdown--active': active
+ });
+ dropdown_classes += ' ' + className;
+ // stick callback on trigger element
+ const bound_children = React.Children.map( children, child => {
+ if( child.type === DropdownTrigger ){
+ child = cloneElement( child, {
+ ref: 'trigger',
+ onClick: this._onToggleClick
+ });
+ }
+ return child;
+ });
+ return
{bound_children}
;
+ },
+ isActive: function(){
+ return ( typeof this.props.active === 'boolean' ) ?
+ this.props.active :
+ this.state.active;
+ },
+ hide: function(){
+ this.setState({
+ active: false
+ });
+ if( this.props.onHide ){
+ this.props.onHide();
+ }
+ },
+ show: function(){
+ this.setState({
+ active: true
+ });
+ if( this.props.onShow ){
+ this.props.onShow();
+ }
+ },
+ _onWindowClick: function( event ){
+ const dropdown_element = findDOMNode( this );
+ if( event.target !== dropdown_element && !dropdown_element.contains( event.target ) && this.isActive() ){
+ this.hide();
+ }
+ },
+ _onToggleClick: function( event ){
+ event.preventDefault();
+ if( this.isActive() ){
+ this.hide();
+ } else {
+ this.show();
+ }
+ }
+});
+
+export { DropdownTrigger, DropdownContent };
+export default Dropdown;
diff --git a/components/DropdownContent.js b/components/DropdownContent.js
deleted file mode 100644
index c680ffa..0000000
--- a/components/DropdownContent.js
+++ /dev/null
@@ -1,15 +0,0 @@
-var React = require('react');
-
-var DropdownContent = React.createClass({
- render: function(){
- var dropdown_content_classes = 'dropdown__content';
- if( this.props.className ){
- dropdown_content_classes += ' ' + this.props.className;
- }
- return React.createElement( 'div', {
- className: dropdown_content_classes
- }, this.props.children );
- }
-});
-
-module.exports = DropdownContent;
\ No newline at end of file
diff --git a/components/DropdownContent.jsx b/components/DropdownContent.jsx
new file mode 100644
index 0000000..af53401
--- /dev/null
+++ b/components/DropdownContent.jsx
@@ -0,0 +1,20 @@
+import React, { createClass, PropTypes } from 'react';
+
+const DropdownContent = createClass({
+ propTypes: {
+ children: PropTypes.any,
+ className: PropTypes.string
+ },
+ getDefaultProps: function(){
+ return {
+ className: ''
+ }
+ },
+ render: function(){
+ const { children, className } = this.props;
+ const classes = 'dropdown__content ' + className;
+ return {children}
;
+ }
+});
+
+export default DropdownContent;
diff --git a/components/DropdownTrigger.js b/components/DropdownTrigger.js
deleted file mode 100644
index 321a408..0000000
--- a/components/DropdownTrigger.js
+++ /dev/null
@@ -1,17 +0,0 @@
-var React = require('react');
-
-var DropdownTrigger = React.createClass({
- render: function(){
- var dropdown_trigger_classes = 'dropdown__trigger';
- if( this.props.className ){
- dropdown_trigger_classes += ' ' + this.props.className;
- }
- return React.createElement( 'a', {
- className: dropdown_trigger_classes,
- href: '#dropdown-trigger',
- onClick: this.props.onClick
- }, this.props.children );
- }
-});
-
-module.exports = DropdownTrigger;
\ No newline at end of file
diff --git a/components/DropdownTrigger.jsx b/components/DropdownTrigger.jsx
new file mode 100644
index 0000000..a6ad80e
--- /dev/null
+++ b/components/DropdownTrigger.jsx
@@ -0,0 +1,25 @@
+import React, { createClass, PropTypes } from 'react';
+
+const DropdownTrigger = createClass({
+ propTypes: {
+ children: PropTypes.any,
+ className: PropTypes.string,
+ onClick: PropTypes.func
+ },
+ getDefaultProps: function(){
+ return {
+ className: ''
+ };
+ },
+ render: function(){
+ const { children, className, onClick } = this.props;
+ const classes = 'dropdown__trigger ' + className;
+ return (
+
+ {children}
+
+ );
+ }
+});
+
+export default DropdownTrigger;
diff --git a/package.json b/package.json
index 6c137ac..53d74b7 100644
--- a/package.json
+++ b/package.json
@@ -2,10 +2,16 @@
"name": "react-simple-dropdown",
"version": "0.1.1",
"description": "Non-prescriptive React.js dropdown toolkit",
- "main": "components/Dropdown.js",
+ "main": "lib/components/Dropdown.js",
"scripts": {
- "test": "zuul -- test/**/*.{js,jsx}",
- "test:browser": "zuul --local 55555 -- test/**/*.{js,jsx}"
+ "prepublish": "npm run build",
+ "postpublish": "npm run clean",
+ "test": "npm run build && zuul -- test/**/*.{js,jsx}",
+ "test:browser": "zuul --local 55555 -- test/**/*.{js,jsx}",
+ "build": "babel components/**/* --out-dir lib",
+ "watch": "npm run build -- -w",
+ "dev": "npm-run-all --parallel watch test:browser",
+ "clean": "trash lib"
},
"repository": {
"type": "git",
@@ -31,15 +37,31 @@
"classnames": "^2.1.2"
},
"devDependencies": {
- "browserify": "^10.2.4",
+ "babel": "^6.3.13",
+ "babel-cli": "^6.3.15",
+ "babel-preset-es2015": "^6.3.13",
+ "babel-preset-react": "^6.3.13",
+ "babelify": "^7.2.0",
"dom-classes": "0.0.1",
- "reactify": "^1.1.1",
+ "mkdirp": "^0.5.1",
+ "npm-run-all": "^1.4.0",
+ "react": "0.14.x",
+ "react-addons-test-utils": "^0.14.3",
+ "react-dom": "0.14.x",
"simple-mock": "^0.3.1",
"tape": "^4.0.0",
+ "trash-cli": "^1.2.0",
"watchify": "^3.2.3",
"zuul": "^3.1.0"
},
"peerDependencies": {
- "react": "0.14.x"
+ "react": "0.14.x",
+ "react-dom": "0.14.x"
+ },
+ "babel": {
+ "presets": [
+ "es2015",
+ "react"
+ ]
}
}
diff --git a/test/components/Dropdown.jsx b/test/components/Dropdown.jsx
index 0fc111a..6c79085 100644
--- a/test/components/Dropdown.jsx
+++ b/test/components/Dropdown.jsx
@@ -1,96 +1,94 @@
-var test = require('tape');
-var React = require('react/addons');
-var TestUtils = React.addons.TestUtils;
-var smock = require('simple-mock');
-var domClasses = require('dom-classes');
-var hasClass = domClasses.has;
+import test from 'tape';
+import React, { createClass } from 'react';
+import { findDOMNode } from 'react-dom';
+import { findRenderedComponentWithType, findRenderedDOMComponentWithClass, renderIntoDocument, Simulate } from 'react-addons-test-utils';
+import smock from 'simple-mock';
+import domClasses, { has as hasClass } from 'dom-classes';
-var Dropdown = require('../../components/Dropdown.js');
-var DropdownTrigger = Dropdown.DropdownTrigger;
-var DropdownContent = Dropdown.DropdownContent;
+import Dropdown, { DropdownTrigger, DropdownContent } from '../../lib/components/Dropdown.js';
-var TestApp = React.createClass({
- getInitialState: function(){
- return {};
- },
- render: function(){
- return (
-
-
-
-
- );
- }
+var TestApp = createClass({
+ getInitialState: function(){
+ return {};
+ },
+ render: function(){
+ return (
+
+
+
+
+ );
+ }
});
-var test_app = TestUtils.renderIntoDocument( );
-var dropdown = TestUtils.findRenderedComponentWithType( test_app, Dropdown );
-var dropdown_element = TestUtils.findRenderedDOMComponentWithClass( dropdown, 'dropdown' );
-var dropdown_element_dom_node = React.findDOMNode( dropdown_element );
-var trigger = TestUtils.findRenderedComponentWithType( test_app, DropdownTrigger );
-var trigger_element = TestUtils.findRenderedDOMComponentWithClass( trigger, 'dropdown__trigger' );
-var content = TestUtils.findRenderedComponentWithType( test_app, DropdownContent );
-var content_element = TestUtils.findRenderedDOMComponentWithClass( content, 'dropdown__content' );
+var test_app = renderIntoDocument( );
+var dropdown = findRenderedComponentWithType( test_app, Dropdown );
+var dropdown_element = findRenderedDOMComponentWithClass( dropdown, 'dropdown' );
+var dropdown_element_dom_node = findDOMNode( dropdown_element );
+var trigger = findRenderedComponentWithType( test_app, DropdownTrigger );
+var trigger_element = findRenderedDOMComponentWithClass( trigger, 'dropdown__trigger' );
+var content = findRenderedComponentWithType( test_app, DropdownContent );
+var content_element = findRenderedDOMComponentWithClass( content, 'dropdown__content' );
test( 'Merges classes from props with default element class', function( t ){
- t.plan( 3 );
- t.equal( domClasses( dropdown_element_dom_node ).length, 1, 'has one class when `className` is empty' );
- test_app.setState({
- className: 'test'
- });
- t.ok( hasClass( dropdown_element_dom_node, 'dropdown' ), 'has class `dropdown`' );
- t.ok( hasClass( dropdown_element_dom_node, 'test' ), 'has class `test`' );
- test_app.setState({
- className: null
- });
+ t.plan( 3 );
+ t.equal( domClasses( dropdown_element_dom_node ).length, 1, 'has one class when `className` is empty' );
+ test_app.setState({
+ className: 'test'
+ });
+ t.ok( hasClass( dropdown_element_dom_node, 'dropdown' ), 'has class `dropdown`' );
+ t.ok( hasClass( dropdown_element_dom_node, 'test' ), 'has class `test`' );
+ test_app.setState({
+ className: null
+ });
});
test( 'Dropdown is toggled when DropdownTrigger is clicked', function( t ){
- t.plan( 4 );
- var onShowCallback = smock.stub();
- var onHideCallback = smock.stub();
- test_app.setState({
- onShow: onShowCallback,
- onHide: onHideCallback
- });
- TestUtils.Simulate.click( trigger_element );
- t.ok( hasClass( dropdown_element_dom_node, 'dropdown--active' ), 'has class `dropdown--active` after trigger is clicked' );
- t.equal( onShowCallback.callCount, 1, '`onShow` function was called' );
- TestUtils.Simulate.click( trigger_element );
- t.notOk( hasClass( dropdown_element_dom_node, 'dropdown--active' ), 'does not have class `dropdown--active` after trigger is clicked again' );
- t.equal( onHideCallback.callCount, 1, '`onHide` function was called' );
- test_app.setState({
- onShow: null,
- onHide: null
- });
+ t.plan( 4 );
+ var onShowCallback = smock.stub();
+ var onHideCallback = smock.stub();
+ test_app.setState({
+ onShow: onShowCallback,
+ onHide: onHideCallback
+ });
+ Simulate.click( trigger_element );
+ t.ok( hasClass( dropdown_element_dom_node, 'dropdown--active' ), 'has class `dropdown--active` after trigger is clicked' );
+ t.equal( onShowCallback.callCount, 1, '`onShow` function was called' );
+ Simulate.click( trigger_element );
+ t.notOk( hasClass( dropdown_element_dom_node, 'dropdown--active' ), 'does not have class `dropdown--active` after trigger is clicked again' );
+ t.equal( onHideCallback.callCount, 1, '`onHide` function was called' );
+ test_app.setState({
+ onShow: null,
+ onHide: null
+ });
});
test( 'Dropdown state can be manually set with props', function( t ){
- t.plan( 2 );
- test_app.setState({
- active: true
- });
- t.ok( hasClass( dropdown_element_dom_node, 'dropdown--active' ), 'has class `dropdown--active` when `active` is set to `true`' );
- test_app.setState({
- active: false
- });
- t.notOk( hasClass( dropdown_element_dom_node, 'dropdown--active' ), 'does not have class `dropdown--active` when `active` is set to `false`' );
- test_app.setState({
- active: null
- });
+ t.plan( 2 );
+ test_app.setState({
+ active: true
+ });
+ t.ok( hasClass( dropdown_element_dom_node, 'dropdown--active' ), 'has class `dropdown--active` when `active` is set to `true`' );
+ test_app.setState({
+ active: false
+ });
+ t.notOk( hasClass( dropdown_element_dom_node, 'dropdown--active' ), 'does not have class `dropdown--active` when `active` is set to `false`' );
+ test_app.setState({
+ active: null
+ });
});
test( 'Dropdown hides itself when area outside dropdown is clicked', function( t ){
- t.plan( 2 );
- dropdown.setState({
- active: true
- });
- TestUtils.Simulate.click( content_element );
- t.ok( hasClass( dropdown_element_dom_node, 'dropdown--active' ), 'has class `dropdown--active` after content element is clicked' );
- document.body.click();
- t.notOk( hasClass( dropdown_element_dom_node, 'dropdown--active' ), 'does not have class `dropdown--active` after document body is clicked' );
-});
\ No newline at end of file
+ t.plan( 2 );
+ dropdown.setState({
+ active: true
+ });
+ Simulate.click( content_element );
+ t.ok( hasClass( dropdown_element_dom_node, 'dropdown--active' ), 'has class `dropdown--active` after content element is clicked' );
+ document.body.click();
+ t.notOk( hasClass( dropdown_element_dom_node, 'dropdown--active' ), 'does not have class `dropdown--active` after document body is clicked' );
+});
diff --git a/test/components/DropdownContent.jsx b/test/components/DropdownContent.jsx
index aab2545..2253e85 100644
--- a/test/components/DropdownContent.jsx
+++ b/test/components/DropdownContent.jsx
@@ -1,33 +1,33 @@
-var test = require('tape');
-var React = require('react/addons');
-var TestUtils = React.addons.TestUtils;
-var domClasses = require('dom-classes');
-var hasClass = domClasses.has;
+import test from 'tape';
+import React, { createClass } from 'react';
+import { findDOMNode } from 'react-dom';
+import { findRenderedComponentWithType, findRenderedDOMComponentWithClass, renderIntoDocument } from 'react-addons-test-utils';
+import domClasses, { has as hasClass } from 'dom-classes';
-var DropdownContent = require('../../components/DropdownContent.js');
+import DropdownContent from '../../lib/components/DropdownContent.js';
-var TestApp = React.createClass({
- getInitialState: function(){
- return {};
- },
- render: function(){
- return ;
- }
+var TestApp = createClass({
+ getInitialState: function(){
+ return {};
+ },
+ render: function(){
+ return ;
+ }
});
-var test_app = TestUtils.renderIntoDocument( );
-var dropdown_content = TestUtils.findRenderedComponentWithType( test_app, DropdownContent );
-var dropdown_content_element = TestUtils.findRenderedDOMComponentWithClass( dropdown_content, 'dropdown__content' );
-var dropdown_content_dom_node = React.findDOMNode( dropdown_content );
+var test_app = renderIntoDocument( );
+var dropdown_content = findRenderedComponentWithType( test_app, DropdownContent );
+var dropdown_content_element = findRenderedDOMComponentWithClass( dropdown_content, 'dropdown__content' );
+var dropdown_content_dom_node = findDOMNode( dropdown_content );
test( 'Merges classes from props with default element class', function( t ){
- t.plan( 3 );
- t.equal( domClasses( dropdown_content_dom_node ).length, 1, 'has one class when `className` is empty' );
- test_app.setState({
- className: 'test'
- });
- t.ok( hasClass( dropdown_content_dom_node, 'dropdown__content' ), 'has class `dropdown__content`' );
- t.ok( hasClass( dropdown_content_dom_node, 'test' ), 'has class `test`' );
- test_app.setState({
- className: null
- });
-});
\ No newline at end of file
+ t.plan( 3 );
+ t.equal( domClasses( dropdown_content_dom_node ).length, 1, 'has one class when `className` is empty' );
+ test_app.setState({
+ className: 'test'
+ });
+ t.ok( hasClass( dropdown_content_dom_node, 'dropdown__content' ), 'has class `dropdown__content`' );
+ t.ok( hasClass( dropdown_content_dom_node, 'test' ), 'has class `test`' );
+ test_app.setState({
+ className: null
+ });
+});
diff --git a/test/components/DropdownTrigger.jsx b/test/components/DropdownTrigger.jsx
index 95acb5e..4e77f59 100644
--- a/test/components/DropdownTrigger.jsx
+++ b/test/components/DropdownTrigger.jsx
@@ -1,50 +1,38 @@
-var test = require('tape');
-var React = require('react/addons');
-var TestUtils = React.addons.TestUtils;
-var domClasses = require('dom-classes');
-var hasClass = domClasses.has;
+import test from 'tape';
+import React, { createClass } from 'react';
+import { findDOMNode } from 'react-dom';
+import { findRenderedComponentWithType, findRenderedDOMComponentWithClass, renderIntoDocument } from 'react-addons-test-utils';
+import domClasses, { has as hasClass } from 'dom-classes';
-var DropdownTrigger = require('../../components/DropdownTrigger.js');
+import DropdownTrigger from '../../lib/components/DropdownTrigger.js';
-var TestApp = React.createClass({
- getInitialState: function(){
- return {};
- },
- render: function(){
- return (
-
- );
- }
+var TestApp = createClass({
+ getInitialState: function(){
+ return {};
+ },
+ render: function(){
+ return (
+
+ );
+ }
});
-var test_app = TestUtils.renderIntoDocument( );
-var dropdown_trigger = TestUtils.findRenderedComponentWithType( test_app, DropdownTrigger );
-var dropdown_trigger_element = TestUtils.findRenderedDOMComponentWithClass( dropdown_trigger, 'dropdown__trigger' );
-var dropdown_trigger_dom_node = React.findDOMNode( dropdown_trigger );
+var test_app = renderIntoDocument( );
+var dropdown_trigger = findRenderedComponentWithType( test_app, DropdownTrigger );
+var dropdown_trigger_element = findRenderedDOMComponentWithClass( dropdown_trigger, 'dropdown__trigger' );
+var dropdown_trigger_dom_node = findDOMNode( dropdown_trigger );
test( 'Merges classes from props with default element class', function( t ){
- t.plan( 3 );
- t.equal( domClasses( dropdown_trigger_dom_node ).length, 1, 'has one class when `className` is empty' );
- test_app.setState({
- className: 'test'
- });
- t.ok( hasClass( dropdown_trigger_dom_node, 'dropdown__trigger' ), 'has class `dropdown__trigger`' );
- t.ok( hasClass( dropdown_trigger_dom_node, 'test' ), 'has class `test`' );
- test_app.setState({
- className: null
- });
+ t.plan( 3 );
+ t.equal( domClasses( dropdown_trigger_dom_node ).length, 1, 'has one class when `className` is empty' );
+ test_app.setState({
+ className: 'test'
+ });
+ t.ok( hasClass( dropdown_trigger_dom_node, 'dropdown__trigger' ), 'has class `dropdown__trigger`' );
+ t.ok( hasClass( dropdown_trigger_dom_node, 'test' ), 'has class `test`' );
+ test_app.setState({
+ className: null
+ });
});
-
-test( 'Attaches onClick handler to element', function( t ){
- t.plan( 1 );
- var clickHandler = function(){};
- test_app.setState({
- onClick: clickHandler
- });
- t.equal( dropdown_trigger_element.props.onClick, clickHandler, '`clickHandler` passed to DropdownTrigger and attached to anchor element are equal' );
- test_app.setState({
- onClick: null
- });
-});
\ No newline at end of file