├── .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 | [](https://travis-ci.org/thomasboyt/react-pikaday) [](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 | value |
72 | A Date object to set the currently-displayed date to. |
73 |
74 |
75 | onChange |
76 |
77 | A callback called when the date is updated by the user. Passes a Date object as the first
78 | argument.
79 | |
80 |
81 |
82 | valueLink |
83 |
84 | Instead of manually hooking up value/onChange to a state variable, you can instead use
85 | LinkedStateMixin
86 | to bind the two.
87 | |
88 |
89 |
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 |
--------------------------------------------------------------------------------