├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── bundled.js ├── example ├── app.js └── index.html ├── karma.conf.js ├── package.json ├── src ├── Pikaday.js └── __tests__ │ └── main.spec.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015", "stage-2"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | 4 | "extends": "eslint:recommended", 5 | 6 | "env": { 7 | "browser": true, 8 | "node": true, 9 | "es6": true, 10 | "mocha": true 11 | }, 12 | 13 | "ecmaFeatures": { 14 | "jsx": true, 15 | "modules": true 16 | }, 17 | 18 | "rules": { 19 | // needed for eslint 20 | "strict": 0, 21 | 22 | // style stuff 23 | "quotes": [2, "single"], 24 | "jsx-quotes": [1, 'prefer-double'], 25 | "no-underscore-dangle": [0], 26 | "comma-dangle": 0, 27 | "camelcase": 0, 28 | "dot-notation": 0, 29 | 30 | // functionality stuff 31 | "new-cap": 0, 32 | "no-shadow": 0, 33 | 34 | // react stuff 35 | "react/jsx-boolean-value": 1, 36 | "react/jsx-no-undef": 2, 37 | "react/jsx-uses-react": 2, 38 | "react/jsx-uses-vars": 1, 39 | "react/no-did-mount-set-state": 1, 40 | "react/no-did-update-set-state": 1, 41 | "react/no-unknown-property": 1, 42 | "react/react-in-jsx-scope": 1, 43 | "react/self-closing-comp": 1, 44 | "react/wrap-multilines": 1 45 | }, 46 | 47 | "plugins": [ 48 | 'react' 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | example/example.bundle.js 2 | node_modules/ 3 | lib/ 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | src 4 | example 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | 5 | notifications: 6 | email: false 7 | 8 | # ensure we're using the container-based infrastructure 9 | # see https://docs.travis-ci.com/user/workers/container-based-infrastructure/#Routing-your-build-to-container-based-infrastructure 10 | sudo: false 11 | 12 | install: 13 | - npm install -g npm@^3 14 | - npm install 15 | 16 | # enable firefox 17 | before_script: 18 | - export DISPLAY=:99.0 19 | - sh -e /etc/init.d/xvfb start 20 | 21 | script: 22 | # --silent surpresses that big ol' NPM script error 23 | - npm run-script lint --silent 24 | - npm run-script test --silent 25 | 26 | cache: 27 | directories: 28 | - node_modules 29 | 30 | # enable native dependencies for node 4.x 31 | # see https://docs.travis-ci.com/user/languages/javascript-with-nodejs#Node.js-v4-(or-io.js-v3)-compiler-requirements 32 | env: 33 | - CXX=g++-4.8 34 | addons: 35 | apt: 36 | sources: 37 | - ubuntu-toolchain-r-test 38 | packages: 39 | - g++-4.8 40 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ### master 4 | 5 | * Allow user to specify arbitrary props that will be passed on to the underlying node (eg props: name, placeholder, data-*, ...) 6 | 7 | ### v0.4.1 (Apr 8 2016) 8 | 9 | * Allow React 15.x as a peer dependency 10 | 11 | ### v0.4.0 (Mar 1, 2016) 12 | 13 | * **[Breaking]** react-pikaday now uses ES6 modules through Babel. If you use ES6 modules, you shouldn't have to change anything. If you use CommonJS, you'll now need to import react-pikaday like so: 14 | 15 | ```js 16 | // before 17 | var Pikaday = require('react-pikaday'); 18 | 19 | //after 20 | var Pikaday = require('react-pikaday').default; 21 | ``` 22 | 23 | * react-pikaday now uses a peer dependency for React, preventing duplicate copies of React from being installed ([#13](https://github.com/thomasboyt/react-pikaday/issues/13)) 24 | * react-pikaday no longer uses the long-deprecated `getDOMNode()` API ([#14](https://github.com/thomasboyt/react-pikaday/issues/14)) 25 | 26 | ### v0.3.1 (Jul 14, 2015) 27 | 28 | * The version of `react-pikaday` on NPM is now prebuilt through Babel and no longer requires a build step. Sorry for the delay on this! 29 | 30 | ### v0.2.0 (Mar 12, 2015) 31 | 32 | * **Potentially breaking change** for some: removed the `/** @jsx React.DOM */` pragma, which has been deprecated since React 0.12 and now fails to compile in Babel (thanks @jaseemabid) 33 | * Clear input manually to avoid relying on Pikaday fork (thanks @jgable) 34 | * Add browserify support (thanks @srlindemann) 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-pikaday 2 | 3 | [![Build Status](https://travis-ci.org/thomasboyt/react-pikaday.svg?branch=master)](https://travis-ci.org/thomasboyt/react-pikaday) [![npm](https://img.shields.io/npm/v/react-pikaday.svg)](https://www.npmjs.com/package/react-pikaday) 4 | 5 | A component wrapper around [Pikaday](https://github.com/dbushell/Pikaday). 6 | 7 | **[View Example](http://thomasboyt.github.io/react-pikaday/)** 8 | 9 | ## Importing 10 | 11 | ### Normal 12 | 13 | To import Pikaday without any CSS: 14 | 15 | ```javascript 16 | import Pikaday from 'react-pikaday'; 17 | ``` 18 | 19 | You'll then need to make sure you include the CSS from `pikaday/css/pikaday.css`. 20 | 21 | ### Bundled 22 | 23 | If you're using [webpack](http://webpack.github.io/) with a configured 24 | [style-loader](https://github.com/webpack/style-loader) for CSS, you can actually require this 25 | component along with the Pikaday CSS in one go: 26 | 27 | ```javascript 28 | import Pikaday from 'react-pikaday/bundled'; 29 | ``` 30 | 31 | See the example app in this repo and the `webpack.config.js` for an example of how this can be configured. 32 | 33 | ## Usage 34 | 35 | ```javascript 36 | import React from 'react'; 37 | import Pikaday from 'react-pikaday'; 38 | 39 | var MyComponent = React.createClass({ 40 | getInitialState: function() { 41 | return { 42 | date: null 43 | }; 44 | }, 45 | 46 | handleChange: function(date) { 47 | this.setState({ 48 | date: date 49 | }); 50 | }, 51 | 52 | render: function() { 53 | var date = this.state.date; 54 | 55 | return ( 56 |
57 |

58 | The date is {date.toDateString()} 59 |

60 | 61 |
62 | ); 63 | } 64 | }); 65 | ``` 66 | 67 | ## Properties 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 80 | 81 | 82 | 83 | 88 | 89 |
valueA Date object to set the currently-displayed date to.
onChange 77 | A callback called when the date is updated by the user. Passes a Date object as the first 78 | argument. 79 |
valueLink 84 | Instead of manually hooking up value/onChange to a state variable, you can instead use 85 | LinkedStateMixin 86 | to bind the two. 87 |
90 | -------------------------------------------------------------------------------- /bundled.js: -------------------------------------------------------------------------------- 1 | // XXX: this doesn't currently get compiled, so keep it es5! 2 | 3 | // this only works in webpack, but it's pretty rad: 4 | require('pikaday/css/pikaday.css'); 5 | 6 | var ReactPikaday = require('./src/Pikaday'); 7 | 8 | module.exports = ReactPikaday; 9 | -------------------------------------------------------------------------------- /example/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import LinkedStateMixin from 'react-addons-linked-state-mixin'; 4 | import Pikaday from '../bundled'; 5 | 6 | var ManualExample = React.createClass({ 7 | getInitialState: function() { 8 | return { 9 | date: null 10 | }; 11 | }, 12 | 13 | handleChange: function(date) { 14 | this.setState({ 15 | date: date 16 | }); 17 | }, 18 | 19 | render: function() { 20 | var date = this.state.date; 21 | var formattedDate = date ? date.toDateString() : 'not set'; 22 | 23 | return ( 24 |
25 |

26 | The date is {formattedDate} 27 |

28 |

29 | 30 |

31 | 34 |
35 | ); 36 | } 37 | }); 38 | 39 | 40 | var LinkedStateExample = React.createClass({ 41 | mixins: [LinkedStateMixin], 42 | 43 | getInitialState: function() { 44 | return { 45 | date: null 46 | }; 47 | }, 48 | 49 | render: function() { 50 | var date = this.state.date; 51 | var formattedDate = date ? date.toDateString() : 'not set'; 52 | 53 | return ( 54 |
55 |

56 | The date is {formattedDate} 57 |

58 |

59 | 60 |

61 | 64 |
65 | ); 66 | } 67 | }); 68 | 69 | 70 | var Examples = React.createClass({ 71 | render: function() { 72 | return ( 73 |
74 |

Manual Example

75 | 76 |

LinkedState Example

77 | 78 |
79 | ); 80 | } 81 | }); 82 | 83 | ReactDOM.render(, document.getElementById('container')); 84 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-pikaday example 6 | 7 | 8 | 9 |

react-pikaday example

10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | var webpackConfig = { 4 | devtool: 'inline-source-map', 5 | 6 | module: { 7 | loaders: [ 8 | { 9 | test: /\.js$/, 10 | exclude: /(node_modules\/)/, 11 | loader: 'babel-loader' 12 | }, 13 | ], 14 | 15 | plugins: [ 16 | new webpack.DefinePlugin({ 17 | 'process.env': { 18 | NODE_ENV: '"test"' 19 | } 20 | }) 21 | ] 22 | } 23 | }; 24 | 25 | module.exports = function(config) { 26 | config.set({ 27 | 28 | browsers: ['Chrome'], 29 | frameworks: ['mocha'], 30 | reporters: ['mocha'], 31 | 32 | files: [ 33 | 'src/__tests__/main.spec.js', 34 | ], 35 | 36 | preprocessors: { 37 | 'src/__tests__/main.spec.js': ['webpack', 'sourcemap'] 38 | }, 39 | 40 | webpack: webpackConfig, 41 | 42 | webpackServer: { 43 | noInfo: true, 44 | }, 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-pikaday", 3 | "version": "0.5.0", 4 | "description": "A React component wrapper around Pikaday.", 5 | "main": "lib/Pikaday.js", 6 | "scripts": { 7 | "test": "karma start --single-run --browsers Firefox", 8 | "build-example": "webpack", 9 | "build": "rimraf lib && babel src --out-dir lib --ignore *.spec.js", 10 | "lint": "eslint src/", 11 | "prepublish": "npm run build" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "http://github.com/thomasboyt/react-pikaday.git" 16 | }, 17 | "dependencies": { 18 | "pikaday": "^1.2.0" 19 | }, 20 | "peerDependencies": { 21 | "react": "^0.14.0 || ^15.0.0-rc.1" 22 | }, 23 | "devDependencies": { 24 | "babel-cli": "^6.6.0", 25 | "babel-core": "^6.11.4", 26 | "babel-eslint": "^6.1.2", 27 | "babel-loader": "^6.2.4", 28 | "babel-preset-es2015": "^6.6.0", 29 | "babel-preset-react": "^6.5.0", 30 | "babel-preset-stage-2": "^6.11.0", 31 | "chai": "^1.9.1", 32 | "css-loader": "^0.23.1", 33 | "eslint": "^3.2.2", 34 | "eslint-plugin-react": "^6.0.0", 35 | "expect": "^1.14.0", 36 | "karma": "^0.13.21", 37 | "karma-chrome-launcher": "^0.2.2", 38 | "karma-firefox-launcher": "^0.1.7", 39 | "karma-mocha": "^0.2.2", 40 | "karma-mocha-reporter": "^1.2.3", 41 | "karma-sourcemap-loader": "^0.3.7", 42 | "karma-webpack": "^1.7.0", 43 | "mocha": "^2.4.5", 44 | "react": "^15.0.0", 45 | "react-addons-linked-state-mixin": "^15.0.0", 46 | "react-addons-test-utils": "^15.0.0", 47 | "react-dom": "^15.0.0", 48 | "rimraf": "^2.5.2", 49 | "style-loader": "^0.13.0", 50 | "webpack": "^1.12.14", 51 | "webpack-dev-server": "^1.14.1" 52 | }, 53 | "author": "Thomas Boyt ", 54 | "license": "MIT" 55 | } 56 | -------------------------------------------------------------------------------- /src/Pikaday.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Pikaday from 'pikaday'; 3 | 4 | var ReactPikaday = React.createClass({ 5 | 6 | propTypes: { 7 | value: React.PropTypes.instanceOf(Date), 8 | onChange: React.PropTypes.func, 9 | initialOptions: React.PropTypes.object, 10 | 11 | valueLink: React.PropTypes.shape({ 12 | value: React.PropTypes.instanceOf(Date), 13 | requestChange: React.PropTypes.func.isRequired 14 | }) 15 | }, 16 | 17 | getDefaultProps: function() { 18 | return { 19 | initialOptions: {} 20 | }; 21 | }, 22 | 23 | getValueLink: function(props) { 24 | return props.valueLink || { 25 | value: props.value, 26 | requestChange: props.onChange 27 | }; 28 | }, 29 | 30 | setDateIfChanged: function(newDate, prevDate) { 31 | var newTime = newDate ? newDate.getTime() : null; 32 | var prevTime = prevDate ? prevDate.getTime() : null; 33 | 34 | if ( newTime !== prevTime ) { 35 | if ( newDate === null ) { 36 | // Workaround for pikaday not clearing value when date set to falsey 37 | this.refs.pikaday.value = ''; 38 | } 39 | this._picker.setDate(newDate, true); // 2nd param = don't call onSelect 40 | } 41 | }, 42 | 43 | // user props to pass down to the underlying DOM node 44 | getDomProps: function() { 45 | var restProps = {}; 46 | for (var propKey in this.props) { 47 | if (this.props.hasOwnProperty(propKey) && !ReactPikaday.propTypes[propKey]) { 48 | restProps[propKey] = this.props[propKey]; 49 | } 50 | } 51 | return restProps 52 | }, 53 | 54 | componentDidMount: function() { 55 | var el = this.refs.pikaday; 56 | 57 | this._picker = new Pikaday({ 58 | field: el, 59 | onSelect: this.getValueLink(this.props).requestChange, 60 | ...this.props.initialOptions 61 | }); 62 | 63 | this.setDateIfChanged(this.getValueLink(this.props).value); 64 | }, 65 | 66 | componentWillReceiveProps: function(nextProps) { 67 | var newDate = this.getValueLink(nextProps).value; 68 | var lastDate = this.getValueLink(this.props).value; 69 | 70 | this.setDateIfChanged(newDate, lastDate); 71 | }, 72 | 73 | render: function() { 74 | return ( 75 | 76 | ); 77 | } 78 | }); 79 | 80 | export default ReactPikaday; 81 | -------------------------------------------------------------------------------- /src/__tests__/main.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import LinkedStateMixin from 'react-addons-linked-state-mixin'; 4 | import {expect} from 'chai'; 5 | import TU from 'react-addons-test-utils'; 6 | 7 | import Pikaday from '../Pikaday'; 8 | 9 | const render = (reactEl) => ReactDOM.render(reactEl, document.createElement('div')); 10 | 11 | describe('Pikaday', () => { 12 | it('renders', () => { 13 | var component = render(); 14 | expect(component).to.be.ok; 15 | }); 16 | 17 | describe('updating the date in Pikaday calls handleChange', () => { 18 | 19 | it('works manually', function() { 20 | var Form = React.createClass({ 21 | getInitialState: function() { 22 | return { date: null }; 23 | }, 24 | 25 | handleChange: function(date) { 26 | this.setState({ date: date }); 27 | }, 28 | 29 | render: function() { 30 | return ( 31 | 32 | ); 33 | } 34 | }); 35 | 36 | var component = render(
); 37 | var pikaday = component.refs.pikaday._picker; 38 | pikaday.setDate(new Date(2014, 0, 1)); 39 | 40 | expect(component.state.date).to.be.eql(new Date(2014, 0, 1)); 41 | }); 42 | 43 | it('works with LinkedStateMixin', function() { 44 | var Form = React.createClass({ 45 | mixins: [ LinkedStateMixin ], 46 | 47 | getInitialState: function() { 48 | return { date: null }; 49 | }, 50 | 51 | render: function() { 52 | return ( 53 | 54 | ); 55 | } 56 | }); 57 | 58 | var component = render(); 59 | var pikaday = component.refs.pikaday._picker; 60 | pikaday.setDate(new Date(2014, 0, 1)); 61 | 62 | expect(component.state.date).to.be.eql(new Date(2014, 0, 1)); 63 | }); 64 | 65 | }); 66 | 67 | describe('setting the passed-in value sets the rendered date', () => { 68 | 69 | it('works manually', () => { 70 | var Form = React.createClass({ 71 | getInitialState: function() { 72 | return { date: new Date(2014, 0, 1) }; 73 | }, 74 | 75 | handleChange: function(date) { 76 | this.setState({ date: date }); 77 | }, 78 | 79 | render: function() { 80 | return ( 81 | 82 | ); 83 | } 84 | }); 85 | 86 | var component = render(); 87 | 88 | var input = TU.findRenderedDOMComponentWithTag(component, 'input'); 89 | expect(input.value).to.be.eql('2014-01-01'); 90 | }); 91 | 92 | it('works with LinkedStateMixin', function() { 93 | var Form = React.createClass({ 94 | mixins: [ LinkedStateMixin ], 95 | 96 | getInitialState: function() { 97 | return { date: new Date(2014, 0, 1) }; 98 | }, 99 | 100 | render: function() { 101 | return ( 102 | 103 | ); 104 | } 105 | }); 106 | 107 | var component = render(); 108 | 109 | var input = TU.findRenderedDOMComponentWithTag(component, 'input'); 110 | expect(input.value).to.be.eql('2014-01-01'); 111 | }); 112 | 113 | }); 114 | 115 | describe('clearing the value', () => { 116 | it('works with LinkedStateMixin', function () { 117 | var Form = React.createClass({ 118 | mixins: [ LinkedStateMixin ], 119 | 120 | getInitialState: function() { 121 | return { date: new Date(2014, 0, 1) }; 122 | }, 123 | 124 | render: function() { 125 | return ( 126 |
127 | 128 | 132 |
133 | ); 134 | } 135 | }); 136 | 137 | var component = render(); 138 | 139 | var input = TU.findRenderedDOMComponentWithTag(component, 'input'); 140 | expect(input.value).to.be.eql('2014-01-01'); 141 | 142 | var clearBtn = component.refs.clearBtn; 143 | TU.Simulate.click(clearBtn); 144 | expect(input.value).to.be.eql(''); 145 | }); 146 | }); 147 | 148 | describe('pikaday options', () => { 149 | it('passes options to pikaday plugin', function() { 150 | var minDate = new Date(2014, 0, 1); 151 | let result; 152 | var Form = React.createClass({ 153 | render: function() { 154 | return ( 155 | result = _picker._o.minDate } initialOptions={{ minDate }}/> 156 | ); 157 | } 158 | }); 159 | 160 | TU.renderIntoDocument(); 161 | 162 | expect(result).to.eql(minDate); 163 | }); 164 | }); 165 | 166 | it('passes arbitrary, unexpected props to the input node', () => { 167 | var component = render(); 168 | var input = TU.findRenderedDOMComponentWithTag(component, 'input'); 169 | 170 | expect(input.initialOptions).to.be.undefined 171 | expect(input.name).to.eql('foo') 172 | }); 173 | }); 174 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: { 3 | example: "./example/app.js", 4 | }, 5 | 6 | output: { 7 | // path: 'example/', 8 | filename: "example/example.bundle.js" 9 | }, 10 | 11 | resolve: { 12 | modulesDirectories: ['node_modules'], 13 | }, 14 | 15 | module: { 16 | loaders: [ 17 | { 18 | test: /\.js$/, 19 | exclude: /node_modules/, 20 | loader: 'babel-loader' 21 | }, 22 | { 23 | test: /\.css$/, 24 | loader: 'style-loader!css-loader' 25 | }, 26 | ] 27 | } 28 | }; 29 | --------------------------------------------------------------------------------