├── index.js
├── .gitignore
├── .babelrc
├── .npmignore
├── .idea
├── watcherTasks.xml
├── misc.xml
├── vcs.xml
├── modules.xml
├── inspectionProfiles
│ └── Project_Default.xml
└── react-input-moment.iml
├── src
├── less
│ ├── variables.less
│ ├── base.less
│ ├── big-input-moment.less
│ ├── time-slider.less
│ ├── slider.less
│ ├── input-moment.less
│ ├── time-picker.less
│ └── date-picker.less
├── index.js
└── components
│ ├── BigInputMoment.js
│ ├── date
│ ├── shared
│ │ ├── DateMonths.js
│ │ ├── DateCalendar.js
│ │ └── DateCalendarRange.js
│ ├── DatePicker.js
│ └── DatePickerRange.js
│ ├── time
│ ├── TimeSlider.js
│ └── TimePicker.js
│ └── InputMoment.js
├── .editorconfig
├── index.html
├── LICENSE
├── example
├── app.less
├── colors.scss
└── app.js
├── webpack.config.js
├── package.json
├── README.md
└── css
└── input-moment.min.css
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib');
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | workspace.xml
2 | node_modules/
3 | lib/
4 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0", "stage-2", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src/
2 | example/
3 | node_modules/
4 | index.html
5 | webpack.config.js
6 |
7 |
--------------------------------------------------------------------------------
/.idea/watcherTasks.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/less/variables.less:
--------------------------------------------------------------------------------
1 | @color-blue: #1385e5;
2 | @color-gray: #dfe0e4;
3 | @color-white: #ffffff;
4 | @slider-handle-size: 20px;
5 | @slider-size: 4px;
6 | @slider-fix: 20px;
7 | @options-height: 40px;
8 | @toolbar-height: 40px;
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | trim_trailing_whitespace = true
7 | charset = utf-8
8 | indent_style = space
9 | indent_size = 2
10 |
11 | [*.json]
12 | indent_size = 2
13 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | react-input-moment
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/less/base.less:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 | @import 'input-moment';
3 | @import 'big-input-moment';
4 | @import 'date-picker';
5 | @import 'time-picker';
6 | @import 'time-slider';
7 | @import 'slider';
8 |
9 | .im-btn {
10 | display: inline-block;
11 | border: 0;
12 | outline: 0;
13 | cursor: pointer;
14 | line-height: 1;
15 |
16 | &:before {
17 | margin-right: 6px;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/less/big-input-moment.less:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 |
3 | .im-big-input-moment {
4 | width: 100%;
5 | height: 100%;
6 | box-sizing: border-box;
7 | border: 1px solid @color-gray;
8 | user-select: none;
9 |
10 | .date-wrapper {
11 | width: 100%;
12 | height: 70%;
13 | }
14 | .time-wrapper {
15 | display: flex;
16 | align-items: center;
17 | justify-content: center;
18 | width: 100%;
19 | height: 30%;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import _InputMoment from './components/InputMoment';
2 | import _BigInputMoment from './components/BigInputMoment';
3 | import _DatePicker from './components/date/DatePicker';
4 | import _DatePickerRange from './components/date/DatePickerRange';
5 | import _TimePicker from './components/time/TimePicker';
6 |
7 | export let InputMoment = _InputMoment;
8 | export let BigInputMoment = _BigInputMoment;
9 | export let DatePicker = _DatePicker;
10 | export let DatePickerRange = _DatePickerRange;
11 | export let TimePicker = _TimePicker;
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, Wang Zuo
2 |
3 | Permission to use, copy, modify, and/or distribute this software for any
4 | purpose with or without fee is hereby granted, provided that the above
5 | copyright notice and this permission notice appear in all copies.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 |
15 |
--------------------------------------------------------------------------------
/.idea/react-input-moment.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/components/BigInputMoment.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DatePicker from './date/DatePicker';
3 | import TimeSlider from './time/TimeSlider';
4 |
5 | export default class extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | render() {
11 | let mom = this.props.moment;
12 |
13 | return (
14 |
15 |
16 |
21 |
22 |
23 |
28 |
29 |
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/less/time-slider.less:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 |
3 | .im-time-slider {
4 | width: 100%;
5 | color: @color-white;
6 | padding: 10px 10px 20px 10px;
7 | box-sizing: border-box;
8 |
9 | .showtime {
10 | text-align: center;
11 | }
12 |
13 | .separator {
14 | display: inline-block;
15 | font-size: 32px;
16 | font-weight: bold;
17 | color: @color-blue;
18 | width: 32px;
19 | height: 65px;
20 | line-height: 65px;
21 | text-align: center;
22 | }
23 |
24 | .time-text {
25 | position: relative;
26 | left: -10px;
27 | font-size: 20px;
28 | color: @color-blue;
29 | }
30 |
31 | .sliders {
32 | padding: 0 10px;
33 | }
34 |
35 | .time {
36 | width: 65px;
37 | height: 65px;
38 | display: inline-block;
39 | font-size: 38px;
40 | line-height: 65px;
41 | background-color: @color-blue;
42 | border-radius: 3px;
43 | text-align: center;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/less/slider.less:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 |
3 | .im-slider {
4 | position: relative;
5 | display: inline-block;
6 | background-color: @color-gray;
7 | border-radius: 3px;
8 | height: @slider-size;
9 | width: 100%;
10 | cursor: pointer;
11 |
12 | .value {
13 | position: absolute;
14 | background-color: @color-blue;
15 | border-radius: 3px;
16 | top: 0;
17 | height: 100%;
18 | }
19 |
20 | .handle {
21 | position: absolute;
22 | width: @slider-size;
23 | height: @slider-size;
24 |
25 | &:after {
26 | position: relative;
27 | display: block;
28 | content: '';
29 | top: -@slider-fix/2;
30 | left: -(@slider-size+@slider-fix)/2;
31 | width: @slider-size + @slider-fix;
32 | height: @slider-size + @slider-fix;
33 | background-color: #fff;
34 | border: 3px solid @color-blue;
35 | border-radius: 50%;
36 | cursor: pointer;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/date/shared/DateMonths.js:
--------------------------------------------------------------------------------
1 | import cx from 'classnames';
2 | import React from 'react';
3 | import chunk from 'lodash/chunk';
4 |
5 | export default class extends React.Component {
6 | render() {
7 | let months = this.props.moment.localeData().months();
8 |
9 | return (
10 |
11 |
12 | {chunk(months, 3).map((row, ridx) => {
13 | return (
14 |
15 | {row.map((month, midx) => {
16 | let month_num = (ridx * 3) + midx;
17 |
18 | return (
19 | this.props.onMonthSelect(month)}>
23 | {month}
24 |
25 | );
26 | })}
27 |
28 | );
29 | })}
30 |
31 |
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/less/input-moment.less:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 |
3 | .im-input-moment {
4 | width: 100%;
5 | height: 100%;
6 | box-sizing: border-box;
7 | user-select: none;
8 |
9 | .options {
10 | width: 100%;
11 | height: @options-height;
12 | display: inline-block;
13 |
14 | button {
15 | float: left;
16 | width: 50%;
17 | color: @color-blue;
18 | background: none;
19 | text-align: center;
20 | font-size: 20px;
21 | padding: 10px;
22 | border: 1px solid @color-blue;
23 |
24 | &:first-child {
25 | border-top-right-radius: 0;
26 | border-bottom-right-radius: 0;
27 | }
28 |
29 | &:last-child {
30 | border-top-left-radius: 0;
31 | border-bottom-left-radius: 0;
32 | }
33 |
34 | &.is-active {
35 | color: #ffffff;
36 | background-color: @color-blue;
37 | }
38 | }
39 | }
40 |
41 | .tab-component {
42 | display: none;
43 | height: calc(100% ~"-" @options-height);
44 |
45 | &.is-active {
46 | display: block;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/example/app.less:
--------------------------------------------------------------------------------
1 | @import '../src/less/variables';
2 |
3 | body {
4 | margin: 0;
5 | font: 87.5%/1.5em 'Lato', sans-serif;
6 | }
7 |
8 | .app {
9 | max-width: 800px;
10 | margin: 0 auto;
11 | padding: 0 20px;
12 | text-align: center;
13 | font-size: 16px;
14 | line-height: 20px;
15 |
16 | .header {
17 | text-align: center;
18 | font-size: 30px;
19 | padding: 10px;
20 | margin: 10px;
21 | }
22 |
23 | .options {
24 | text-align: center;
25 | }
26 |
27 | input.header-button {
28 | width: 200px;
29 | height: 40px;
30 | margin: 10px;
31 | font-size: 20px;
32 | background: none;
33 | border: 2px solid @color-blue;
34 | color: @color-blue;
35 | cursor: pointer;
36 | }
37 |
38 | input.output {
39 | padding: 7px 8px;
40 | font-size: 20px;
41 | width: 275px;
42 | margin-bottom: 1em;
43 | }
44 |
45 | .wrapper {
46 | margin: 0 auto;
47 |
48 | &.small {
49 | height: 380px;
50 | width: 320px; /* small phone typical size */
51 | }
52 | &.medium {
53 | height: 400px;
54 | width: 500px;
55 | }
56 | &.large {
57 | height: 600px;
58 | width: 700px;
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/less/time-picker.less:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 |
3 | .im-time-picker {
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | width: 100%;
8 | height: 100%;
9 | color: @color-white;
10 | padding: 10px 10px 20px 10px;
11 | border: 1px solid @color-gray;
12 | box-sizing: border-box;
13 |
14 | .time-picker-wrapper {
15 | width: 100%;
16 |
17 | .showtime {
18 | text-align: center;
19 | }
20 |
21 | .separator {
22 | display: inline-block;
23 | font-size: 32px;
24 | font-weight: bold;
25 | color: @color-blue;
26 | width: 32px;
27 | height: 65px;
28 | line-height: 65px;
29 | text-align: center;
30 | }
31 |
32 | .time-text {
33 | position: relative;
34 | left: -10px;
35 | font-size: 20px;
36 | color: @color-blue;
37 | margin-top: 20px;
38 | margin-bottom: 10px;
39 | }
40 |
41 | .sliders {
42 | padding: 0 10px;
43 | }
44 |
45 | .time {
46 | width: 65px;
47 | height: 65px;
48 | display: inline-block;
49 | font-size: 38px;
50 | line-height: 65px;
51 | background-color: @color-blue;
52 | border-radius: 3px;
53 | text-align: center;
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/example/colors.scss:
--------------------------------------------------------------------------------
1 | //example: how to override default colors and use your own color scheme
2 |
3 | $page-fore: #111111;
4 | $accent-back: #e03612;
5 | $accent-fore: #ffffff;
6 |
7 | .im-input-moment {
8 | .options {
9 | button {
10 | color: $accent-back !important;
11 | border-color: $accent-back !important;
12 | &.is-active {
13 | background-color: $accent-back !important;
14 | color: $accent-fore !important;
15 | }
16 | }
17 | }
18 | }
19 | .im-date-picker {
20 | .toolbar {
21 | color: $accent-back !important;
22 | }
23 | table td {
24 | color: $page-fore !important;
25 | &.current {
26 | background-color: $accent-back !important;
27 | color: $accent-fore !important;
28 | }
29 | }
30 | }
31 | .im-time-picker, .im-time-slider {
32 | .time-text {
33 | color: $page-fore !important;
34 | }
35 | .showtime {
36 | .time {
37 | background-color: $accent-back !important;
38 | color: $accent-fore !important;
39 | }
40 | .separator {
41 | color: $accent-back !important;
42 | }
43 | }
44 | .im-slider {
45 | background-color: $page-fore !important;
46 | .value {
47 | background-color: $accent-back !important;
48 | }
49 | .handle:after {
50 | background-color: $accent-fore !important;
51 | border-color: $accent-back !important;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | entry: {
5 | 'example/build/bundle': './example/app.js'
6 | },
7 | output: {
8 | path: __dirname,
9 | filename: '[name].js',
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.js$/,
15 | use: [{
16 | loader: 'babel-loader',
17 | options: {
18 | presets: ['babel-preset-es2015', 'babel-preset-stage-2', 'babel-preset-react']
19 | }
20 | }]
21 | }, {
22 | test: /\.json$/,
23 | use: [
24 | { loader: "json-loader" }
25 | ]
26 | }, {
27 | test: /\.css$/,
28 | use: [
29 | { loader: "style-loader" },
30 | { loader: "css-loader" }
31 | ]
32 | }, {
33 | test: /\.less$/,
34 | use: [
35 | { loader: "style-loader" },
36 | { loader: "css-loader" },
37 | { loader: "less-loader" }
38 | ]
39 | }
40 | ]
41 | },
42 | devServer: {
43 | contentBase: __dirname, //path on disk to serve static files from
44 | publicPath: '/', //path in browser to server bundles from memory
45 | host: 'localhost',
46 | port: 8888,
47 | disableHostCheck: true, //allow external ip addresses to connect
48 | open: true,
49 | inline: true,
50 | quiet: true,
51 | noInfo: true,
52 | historyApiFallback: true,
53 | clientLogLevel: 'warning',
54 | stats: {
55 | colors: true
56 | },
57 | overlay: {
58 | warnings: true,
59 | errors: true
60 | }
61 | },
62 | devtool: 'source-map'
63 | };
64 |
--------------------------------------------------------------------------------
/src/less/date-picker.less:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 |
3 | .im-date-picker {
4 | width: 100%;
5 | height: 100%;
6 | box-sizing: border-box;
7 |
8 | .toolbar {
9 | height: @toolbar-height;
10 | line-height: 30px;
11 | color: @color-blue;
12 | text-align: center;
13 | white-space: nowrap;
14 | user-select: none;
15 |
16 | .current-date {
17 | display: inline-block;
18 | width: 68%;
19 | min-width: 200px;
20 | cursor: pointer;
21 | font-size: 26px;
22 | line-height: 40px;
23 | vertical-align: middle;
24 | text-decoration: underline;
25 | }
26 |
27 | //arrows
28 | .prev-nav, .next-nav {
29 | display: inline-block;
30 | width: 8%;
31 | cursor: pointer;
32 | font-size: 40px;
33 | vertical-align: middle;
34 | }
35 | }
36 |
37 | //days on calendar not within current month
38 | .prev-month, .next-month, .prev-year, .next-year {
39 | color: rgba(50,50,50,0.2);
40 | }
41 |
42 | table {
43 | width: 100%;
44 | height: calc(100% ~"-" @toolbar-height);
45 | border-collapse: collapse;
46 | border-spacing: 0;
47 | table-layout: fixed;
48 | }
49 |
50 | td {
51 | text-align: center;
52 | cursor: pointer;
53 | color: @color-gray;
54 | border: 1px solid @color-gray;
55 | font-size: 20px;
56 |
57 | &.current {
58 | background-color: @color-blue;
59 | color: @color-white;
60 | font-weight: bold;
61 | }
62 | }
63 |
64 | thead {
65 | td {
66 | color: @color-blue;
67 | font-weight: 700;
68 | text-transform: uppercase;
69 | font-size: 12px;
70 | }
71 | }
72 |
73 | tbody {
74 | td {
75 | color: #666666;
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/components/time/TimeSlider.js:
--------------------------------------------------------------------------------
1 | import cx from 'classnames';
2 | import React from 'react';
3 | import InputSlider from 'react-input-slider';
4 |
5 | export default class extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | render() {
11 | let mom = this.props.moment;
12 | let minutes = this.getMinutes(mom);
13 |
14 | return (
15 |
16 |
17 | {mom.format('HH')}
18 | :
19 | {mom.format('mm')}
20 | {this.props.showSeconds &&
21 |
22 | :
23 | {mom.format('ss')}
24 |
25 | }
26 |
27 |
28 |
29 |
37 |
38 |
39 | );
40 | }
41 |
42 | onChange(pos) {
43 | if (window.event && window.event.stopPropagation) window.event.stopPropagation(); //bug fix for picker within react modal component
44 |
45 | let mom = this.props.moment.clone();
46 | let totalMin = parseInt(pos.x, 10);
47 |
48 | let hours = Math.floor(totalMin / 60);
49 | let minutes = totalMin % 60;
50 |
51 | mom.hours(hours).minutes(minutes);
52 | this.props.onChange(mom);
53 | }
54 |
55 | getMinutes(mom) {
56 | return mom.hours() * 60 + mom.minutes();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-input-moment",
3 | "version": "1.7.13",
4 | "description": "React date and time pickers powered by MomentJS.",
5 | "main": "dist/input-moment.js",
6 | "scripts": {
7 | "start": "webpack-dev-server",
8 | "build-js": "babel src --out-dir lib && webpack -p",
9 | "build-css": "lessc --clean-css src/less/base.less css/input-moment.min.css",
10 | "build": "npm run build-js && npm run build-css",
11 | "install-peer-deps": "npm install react react-dom moment --no-save"
12 | },
13 | "license": "ISC",
14 | "peerDependencies": {
15 | "react": ">=15.0.0",
16 | "react-dom": ">=15.0.0",
17 | "moment": ">=2.10.6"
18 | },
19 | "dependencies": {
20 | "classnames": "^2.2.0",
21 | "lodash": "^4.17.4",
22 | "react-icons": "^2.2.0",
23 | "react-input-slider": "^4.0.1"
24 | },
25 | "devDependencies": {
26 | "babel-core": "^6.24.1",
27 | "babel-loader": "^7.0.0",
28 | "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1",
29 | "babel-preset-es2015": "^6.24.1",
30 | "babel-preset-react": "^6.24.1",
31 | "babel-preset-stage-0": "^6.24.1",
32 | "babel-preset-stage-2": "^6.24.1",
33 | "css-loader": "^0.28.4",
34 | "json-loader": "^0.5.4",
35 | "less": "^2.7.2",
36 | "less-loader": "^4.0.4",
37 | "less-plugin-clean-css": "^1.5.1",
38 | "style-loader": "^0.18.2",
39 | "webpack": "^2.6.1",
40 | "webpack-dev-server": "^2.4.5"
41 | },
42 | "repository": {
43 | "type": "git",
44 | "url": "git+https://github.com/wayofthefuture/react-input-moment.git"
45 | },
46 | "keywords": [
47 | "react-input-moment",
48 | "react input moment",
49 | "react date picker",
50 | "react time picker",
51 | "react calendar",
52 | "react moment",
53 | "date picker",
54 | "time picker"
55 | ]
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/InputMoment.js:
--------------------------------------------------------------------------------
1 | import cx from 'classnames';
2 | import React from 'react';
3 | import DatePickerIcon from 'react-icons/lib/fa/calendar';
4 | import ClockIcon from 'react-icons/lib/fa/clock-o';
5 | import DatePicker from './date/DatePicker';
6 | import TimePicker from './time/TimePicker';
7 |
8 | export default class extends React.Component {
9 | constructor(props) {
10 | super(props);
11 |
12 | this.state = {
13 | tab: 0
14 | };
15 | }
16 |
17 | render() {
18 | let tab = this.state.tab;
19 | let mom = this.props.moment;
20 |
21 | return (
22 |
23 |
24 |
25 |
32 | Date
33 |
34 |
35 |
42 | Time
43 |
44 |
45 |
46 |
47 |
52 |
53 |
54 |
60 |
61 |
62 | );
63 | }
64 |
65 | handleClickTab(tab, e) {
66 | e.preventDefault();
67 | this.setState({tab});
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/components/date/shared/DateCalendar.js:
--------------------------------------------------------------------------------
1 | import cx from 'classnames';
2 | import React from 'react';
3 | import range from 'lodash/range';
4 | import chunk from 'lodash/chunk';
5 |
6 | export default class extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | }
10 |
11 | render() {
12 | let mom = this.props.moment;
13 |
14 | let currentDay = mom.date();
15 | let firstDayOfWeek = mom.localeData().firstDayOfWeek();
16 | let endOfPreviousMonth = mom.clone().subtract(1, 'month').endOf('month').date();
17 | let startDayOfCurrentMonth = mom.clone().date(1).day();
18 | let endOfCurrentMonth = mom.clone().endOf('month').date();
19 |
20 | let days = [].concat(
21 | range(
22 | (endOfPreviousMonth - startDayOfCurrentMonth + firstDayOfWeek + 1),
23 | (endOfPreviousMonth + 1)
24 | ),
25 | range(
26 | 1,
27 | (endOfCurrentMonth + 1)
28 | ),
29 | range(
30 | 1,
31 | (42 - endOfCurrentMonth - startDayOfCurrentMonth + firstDayOfWeek + 1)
32 | )
33 | );
34 |
35 | let weeks = mom.localeData().weekdaysShort();
36 | weeks = weeks.slice(firstDayOfWeek).concat(weeks.slice(0, firstDayOfWeek));
37 |
38 | return (
39 |
40 |
41 |
42 | {weeks.map((week, index) => {week} )}
43 |
44 |
45 |
46 |
47 | {chunk(days, 7).map((row, week) => (
48 |
49 | {row.map(day => (
50 | this.props.onDaySelect(day, week)}/>
51 | ))}
52 |
53 | ))}
54 |
55 |
56 | );
57 | }
58 | }
59 |
60 | class Day extends React.Component {
61 | constructor(props) {
62 | super(props);
63 | }
64 |
65 | render() {
66 | let {day, week, currentDay} = this.props;
67 |
68 | let prevMonth = (week === 0 && day > 7);
69 | let nextMonth = (week >= 4 && day <= 14);
70 |
71 | let cn = cx({
72 | 'prev-month': prevMonth,
73 | 'next-month': nextMonth,
74 | 'current': !prevMonth && !nextMonth && (day === currentDay)
75 | });
76 |
77 | return {day} ;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/components/date/shared/DateCalendarRange.js:
--------------------------------------------------------------------------------
1 | import cx from 'classnames';
2 | import React from 'react';
3 | import range from 'lodash/range';
4 | import chunk from 'lodash/chunk';
5 |
6 | export default class extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | }
10 |
11 | render() {
12 | let displayMoment = this.props.displayMoment.clone();
13 | let startMoment = this.props.startMoment.clone();
14 | let endMoment = this.props.endMoment.clone();
15 |
16 | let firstDayOfWeek = displayMoment.localeData().firstDayOfWeek();
17 | let endOfPreviousMonth = displayMoment.clone().subtract(1, 'month').endOf('month').date();
18 | let startDayOfCurrentMonth = displayMoment.clone().date(1).day();
19 | let endOfCurrentMonth = displayMoment.clone().endOf('month').date();
20 |
21 | let days = [].concat(
22 | range(
23 | (endOfPreviousMonth - startDayOfCurrentMonth + firstDayOfWeek + 1),
24 | (endOfPreviousMonth + 1)
25 | ),
26 | range(
27 | 1,
28 | (endOfCurrentMonth + 1)
29 | ),
30 | range(
31 | 1,
32 | (42 - endOfCurrentMonth - startDayOfCurrentMonth + firstDayOfWeek + 1)
33 | )
34 | );
35 |
36 | let weeks = displayMoment.localeData().weekdaysShort();
37 | weeks = weeks.slice(firstDayOfWeek).concat(weeks.slice(0, firstDayOfWeek));
38 |
39 | return (
40 |
41 |
42 |
43 | {weeks.map((week, index) => {week} )}
44 |
45 |
46 |
47 |
48 | {chunk(days, 7).map((row, week) => (
49 |
50 | {row.map(day => (
51 | this.props.onDaySelect(day, week)}/>
52 | ))}
53 |
54 | ))}
55 |
56 |
57 | );
58 | }
59 | }
60 |
61 | class Day extends React.Component {
62 | constructor(props) {
63 | super(props);
64 | }
65 |
66 | render() {
67 | let {day, week} = this.props;
68 | let displayMoment = this.props.displayMoment.clone();
69 | let startMoment = this.props.startMoment.clone();
70 | let endMoment = this.props.endMoment.clone();
71 |
72 | let prevMonth = (week === 0 && day > 7);
73 | let nextMonth = (week >= 4 && day <= 14);
74 |
75 | let compMoment = displayMoment.clone();
76 | if (prevMonth) compMoment.subtract(1, 'month');
77 | if (nextMonth) compMoment.add(1, 'month');
78 | compMoment.date(day);
79 |
80 | let cn = cx({
81 | 'prev-month': prevMonth,
82 | 'next-month': nextMonth,
83 | 'current': (startMoment.valueOf() <= compMoment.valueOf() && compMoment.valueOf() <= endMoment.valueOf())
84 | });
85 |
86 | return {day} ;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/components/time/TimePicker.js:
--------------------------------------------------------------------------------
1 | import cx from 'classnames';
2 | import React from 'react';
3 | import InputSlider from 'react-input-slider';
4 |
5 | export default class extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | render() {
11 | let mom = this.props.moment;
12 |
13 | return (
14 |
15 |
16 |
17 | {mom.format('HH')}
18 | :
19 | {mom.format('mm')}
20 | {this.props.showSeconds &&
21 |
22 | :
23 | {mom.format('ss')}
24 |
25 | }
26 |
27 |
28 |
29 |
Hours:
30 |
37 |
Minutes:
38 |
45 | {this.props.showSeconds &&
46 |
Seconds:
47 | }
48 | {this.props.showSeconds &&
49 |
56 | }
57 |
58 |
59 |
60 | );
61 | }
62 |
63 | changeHours(pos) {
64 | if (window.event && window.event.stopPropagation) window.event.stopPropagation(); //bug fix for picker within react modal component
65 |
66 | let mom = this.props.moment.clone();
67 | mom.hours(parseInt(pos.x, 10));
68 | this.props.onChange(mom);
69 | }
70 |
71 | changeMinutes(pos) {
72 | if (window.event && window.event.stopPropagation) window.event.stopPropagation(); //bug fix for picker within react modal component
73 |
74 | let mom = this.props.moment.clone();
75 | mom.minutes(parseInt(pos.x, 10));
76 | this.props.onChange(mom);
77 | }
78 |
79 | changeSeconds(pos) {
80 | if (window.event && window.event.stopPropagation) window.event.stopPropagation(); //bug fix for picker within react modal component
81 |
82 | let mom = this.props.moment.clone();
83 | mom.seconds(parseInt(pos.x, 10));
84 | this.props.onChange(mom);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | React date and time pickers powered by [MomentJS](http://momentjs.com).
2 |
3 | This project is created from an older GitHub project by [Prometheus Research](https://github.com/prometheusresearch/react-input-moment).
4 | I wanted to add more functionality, but the other project was not being maintained and not being published to NPM so we created this repo.
5 |
6 | ### Demo
7 | [A demo can be viewed here](https://wayofthefuture.github.io/react-input-moment/)
8 |
9 | ### Requirements
10 | This module has peer dependencies: react, react-dom, and moment.
11 | These dependencies are not included in the build to reduce duplicate dependencies as a result of minor version differences.
12 | This allows for a flat dependency graph and should significantly reduce build size.
13 | [Read More Here](https://docs.npmjs.com/how-npm-works/npm3)
14 |
15 | ### Installation
16 | - npm install react react-dom moment --save
17 | - npm install react-input-moment --save
18 | - Go download [input-moment.min.css](https://github.com/wayofthefuture/react-input-moment/tree/master/css) and drop it as a css style link in your html page.
19 |
20 | ### Sizing
21 | As with many css components, getting them to look the way you want on all devices is not always so easy. These pickers are
22 | designed to stretch to their parent container element. The parent wrapper must have a set width and height.
23 |
24 | ### Colors
25 | If you want to override the default colors and use your own color scheme, see the scss in [this file](https://github.com/wayofthefuture/react-input-moment/blob/master/example/colors.scss).
26 |
27 | ``` javascript
28 | import {InputMoment, BigInputMoment, DatePicker, TimePicker} from 'react-input-moment';
29 |
30 | //all wrapper classes should have a set width and height.
31 | //percentages will work as long as the parent of the wrapper has a set width and height.
32 |
33 |
34 |
40 |
41 |
42 |
43 |
48 |
49 |
50 |
51 |
56 |
57 |
58 | //onChange(startMoment, endMoment)
59 |
60 |
65 |
66 |
67 |
68 |
74 |
75 | ```
76 |
77 | Check [app.js](https://github.com/wayofthefuture/react-input-moment/blob/master/example/app.js)
78 | for a working example.
79 |
80 | ### Development
81 | - git clone https://github.com/wayofthefuture/react-input-moment.git
82 | - cd react-input-moment
83 | - npm install react react-dom moment
84 | - npm install
85 | - npm start
86 | - http://localhost:8888
87 |
88 | ### License
89 | ISC
90 |
--------------------------------------------------------------------------------
/src/components/date/DatePicker.js:
--------------------------------------------------------------------------------
1 | import cx from 'classnames';
2 | import moment from 'moment';
3 | import React from 'react';
4 |
5 | import DateCalendar from './shared/DateCalendar';
6 | import DateMonths from './shared/DateMonths';
7 |
8 | import LeftIcon from 'react-icons/lib/fa/angle-left';
9 | import RightIcon from 'react-icons/lib/fa/angle-right';
10 | import DoubleLeftIcon from 'react-icons/lib/fa/angle-double-left';
11 | import DoubleRightIcon from 'react-icons/lib/fa/angle-double-right';
12 |
13 | export default class extends React.Component {
14 | constructor(props) {
15 | super(props);
16 |
17 | this.state = {
18 | mode: 'calendar',
19 | };
20 | }
21 |
22 | render() {
23 | let {mode} = this.state;
24 | let mom = this.props.moment.clone();
25 |
26 | return (
27 |
28 |
36 | {mode === 'calendar' && }
37 | {mode === 'months' && }
38 |
39 | );
40 | }
41 |
42 | onPrevMonth(e) {
43 | e.preventDefault();
44 | let mom = this.props.moment.clone();
45 | this.props.onChange(mom.subtract(1, 'month'));
46 | }
47 |
48 | onNextMonth(e) {
49 | e.preventDefault();
50 | let mom = this.props.moment.clone();
51 | this.props.onChange(mom.add(1, 'month'));
52 | }
53 |
54 | onPrevYear(e) {
55 | e.preventDefault();
56 | let mom = this.props.moment.clone();
57 | this.props.onChange(mom.subtract(1, 'year'));
58 | }
59 |
60 | onNextYear(e) {
61 | e.preventDefault();
62 | let mom = this.props.moment.clone();
63 | this.props.onChange(mom.add(1, 'year'));
64 | }
65 |
66 | onToggleMode() {
67 | this.setState({
68 | mode: this.state.mode === 'calendar' ? 'months' : 'calendar',
69 | });
70 | }
71 |
72 | onDaySelect(day, week) {
73 | let mom = this.props.moment.clone();
74 | let prevMonth = (week === 0 && day > 7);
75 | let nextMonth = (week >= 4 && day <= 14);
76 |
77 | if (prevMonth) mom.subtract(1, 'month');
78 | if (nextMonth) mom.add(1, 'month');
79 | mom.date(day);
80 |
81 | //true - used to indicate day select if parent doesn't want to have a submit button
82 | this.props.onChange(mom, true);
83 | }
84 |
85 | onMonthSelect(month) {
86 | let mom = this.props.moment.clone();
87 | this.setState({mode: 'calendar'}, () => this.props.onChange(mom.month(month)));
88 | }
89 | }
90 |
91 | class Toolbar extends React.Component {
92 | constructor(props) {
93 | super(props);
94 | }
95 |
96 | render() {
97 | return (
98 |
99 |
103 |
107 |
110 | {this.props.display}
111 |
112 |
116 |
120 |
121 | );
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/css/input-moment.min.css:
--------------------------------------------------------------------------------
1 | .im-input-moment{width:100%;height:100%;box-sizing:border-box;user-select:none}.im-input-moment .options{width:100%;height:40px;display:inline-block}.im-input-moment .options button{float:left;width:50%;color:#1385e5;background:0 0;text-align:center;font-size:20px;padding:10px;border:1px solid #1385e5}.im-input-moment .options button:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.im-input-moment .options button:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.im-input-moment .options button.is-active{color:#fff;background-color:#1385e5}.im-input-moment .tab-component{display:none;height:calc(100% - 40px)}.im-input-moment .tab-component.is-active{display:block}.im-big-input-moment{width:100%;height:100%;box-sizing:border-box;border:1px solid #dfe0e4;user-select:none}.im-big-input-moment .date-wrapper{width:100%;height:70%}.im-big-input-moment .time-wrapper{display:flex;align-items:center;justify-content:center;width:100%;height:30%}.im-date-picker{width:100%;height:100%;box-sizing:border-box}.im-date-picker .toolbar{height:40px;line-height:30px;color:#1385e5;text-align:center;white-space:nowrap;user-select:none}.im-date-picker .toolbar .current-date{display:inline-block;width:68%;min-width:200px;cursor:pointer;font-size:26px;line-height:40px;vertical-align:middle;text-decoration:underline}.im-date-picker .toolbar .next-nav,.im-date-picker .toolbar .prev-nav{display:inline-block;width:8%;cursor:pointer;font-size:40px;vertical-align:middle}.im-date-picker .next-month,.im-date-picker .next-year,.im-date-picker .prev-month,.im-date-picker .prev-year{color:rgba(50,50,50,.2)}.im-date-picker table{width:100%;height:calc(100% - 40px);border-collapse:collapse;border-spacing:0;table-layout:fixed}.im-date-picker td{text-align:center;cursor:pointer;color:#dfe0e4;border:1px solid #dfe0e4;font-size:20px}.im-date-picker td.current{background-color:#1385e5;color:#fff;font-weight:700}.im-date-picker thead td{color:#1385e5;font-weight:700;text-transform:uppercase;font-size:12px}.im-date-picker tbody td{color:#666}.im-time-picker{display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:#fff;padding:10px 10px 20px 10px;border:1px solid #dfe0e4;box-sizing:border-box}.im-time-picker .time-picker-wrapper{width:100%}.im-time-picker .time-picker-wrapper .showtime{text-align:center}.im-time-picker .time-picker-wrapper .separator{display:inline-block;font-size:32px;font-weight:700;color:#1385e5;width:32px;height:65px;line-height:65px;text-align:center}.im-time-picker .time-picker-wrapper .time-text{position:relative;left:-10px;font-size:20px;color:#1385e5;margin-top:20px;margin-bottom:10px}.im-time-picker .time-picker-wrapper .sliders{padding:0 10px}.im-time-picker .time-picker-wrapper .time{width:65px;height:65px;display:inline-block;font-size:38px;line-height:65px;background-color:#1385e5;border-radius:3px;text-align:center}.im-time-slider{width:100%;color:#fff;padding:10px 10px 20px 10px;box-sizing:border-box}.im-time-slider .showtime{text-align:center}.im-time-slider .separator{display:inline-block;font-size:32px;font-weight:700;color:#1385e5;width:32px;height:65px;line-height:65px;text-align:center}.im-time-slider .time-text{position:relative;left:-10px;font-size:20px;color:#1385e5}.im-time-slider .sliders{padding:0 10px}.im-time-slider .time{width:65px;height:65px;display:inline-block;font-size:38px;line-height:65px;background-color:#1385e5;border-radius:3px;text-align:center}.im-slider{position:relative;display:inline-block;background-color:#dfe0e4;border-radius:3px;height:4px;width:100%;cursor:pointer}.im-slider .value{position:absolute;background-color:#1385e5;border-radius:3px;top:0;height:100%}.im-slider .handle{position:absolute;width:4px;height:4px}.im-slider .handle:after{position:relative;display:block;content:'';top:-10px;left:-12px;width:24px;height:24px;background-color:#fff;border:3px solid #1385e5;border-radius:50%;cursor:pointer}.im-btn{display:inline-block;border:0;outline:0;cursor:pointer;line-height:1}.im-btn:before{margin-right:6px}
--------------------------------------------------------------------------------
/src/components/date/DatePickerRange.js:
--------------------------------------------------------------------------------
1 | import cx from 'classnames';
2 | import moment from 'moment';
3 | import React from 'react';
4 |
5 | import DateCalendarRange from './shared/DateCalendarRange';
6 | import DateMonths from './shared/DateMonths';
7 |
8 | import LeftIcon from 'react-icons/lib/fa/angle-left';
9 | import RightIcon from 'react-icons/lib/fa/angle-right';
10 | import DoubleLeftIcon from 'react-icons/lib/fa/angle-double-left';
11 | import DoubleRightIcon from 'react-icons/lib/fa/angle-double-right';
12 |
13 | export default class extends React.Component {
14 | constructor(props) {
15 | super(props);
16 | let {startMoment, endMoment} = this.props;
17 | let displayMoment = startMoment.clone();
18 |
19 | this.state = {
20 | mode: 'calendar',
21 | displayMoment: displayMoment,
22 | startMoment: startMoment,
23 | endMoment: endMoment
24 | };
25 | }
26 |
27 | componentWillReceiveProps(nextProps) {
28 | if (this.state.startMoment !== nextProps.startMoment) {
29 | this.setState({startMoment: nextProps.startMoment});
30 | }
31 | if (this.state.endMoment !== nextProps.endMoment) {
32 | this.setState({endMoment: nextProps.endMoment});
33 | }
34 | }
35 |
36 | render() {
37 | let {mode, displayMoment, startMoment, endMoment} = this.state;
38 |
39 | return (
40 |
41 |
49 | {mode === 'calendar' && }
50 | {mode === 'months' && }
51 |
52 | );
53 | }
54 |
55 | onPrevMonth(e) {
56 | e.preventDefault();
57 | let displayMoment = this.state.displayMoment.clone();
58 | displayMoment.subtract(1, 'month');
59 | this.setState({displayMoment});
60 | }
61 |
62 | onNextMonth(e) {
63 | e.preventDefault();
64 | let displayMoment = this.state.displayMoment.clone();
65 | displayMoment.add(1, 'month');
66 | this.setState({displayMoment});
67 | }
68 |
69 | onPrevYear(e) {
70 | e.preventDefault();
71 | let displayMoment = this.state.displayMoment.clone();
72 | displayMoment.subtract(1, 'year');
73 | this.setState({displayMoment});
74 | }
75 |
76 | onNextYear(e) {
77 | e.preventDefault();
78 | let displayMoment = this.state.displayMoment.clone();
79 | displayMoment.add(1, 'year');
80 | this.setState({displayMoment});
81 | }
82 |
83 | onToggleMode() {
84 | this.setState({
85 | mode: this.state.mode === 'calendar' ? 'months' : 'calendar',
86 | });
87 | }
88 |
89 | onDaySelect(day, week) {
90 | let displayMoment = this.state.displayMoment.clone();
91 | let startMoment = this.state.startMoment.clone();
92 | let endMoment = this.state.endMoment.clone();
93 |
94 | let prevMonth = (week === 0 && day > 7);
95 | let nextMonth = (week >= 4 && day <= 14);
96 |
97 | let compMoment = displayMoment.clone();
98 | if (prevMonth) compMoment.subtract(1, 'month');
99 | if (nextMonth) compMoment.add(1, 'month');
100 | compMoment.date(day);
101 |
102 | //begin new range select
103 | if (startMoment.date() !== endMoment.date()) {
104 | this.fireChangeEvent(compMoment, compMoment);
105 | }
106 | //2nd date in range select
107 | else {
108 | if (compMoment.valueOf() <= startMoment.valueOf()) {
109 | this.fireChangeEvent(compMoment, endMoment);
110 | } else {
111 | this.fireChangeEvent(startMoment, compMoment);
112 | }
113 | }
114 | }
115 |
116 | onMonthSelect(month) {
117 | let displayMoment = this.state.displayMoment.clone();
118 | let newMoment = displayMoment.clone().date(1).month(month);
119 |
120 | this.setState({mode: 'calendar', displayMoment: newMoment});
121 | }
122 |
123 | //make sure change event sends range moment boundaries
124 | fireChangeEvent(startMoment, endMoment) {
125 | //sometimes moments are same instance, so must clone
126 | this.props.onChange(startMoment.clone().startOf('day'), endMoment.clone().endOf('day'));
127 | }
128 | }
129 |
130 | class Toolbar extends React.Component {
131 | constructor(props) {
132 | super(props);
133 | }
134 |
135 | render() {
136 | return (
137 |
138 |
142 |
146 |
149 | {this.props.display}
150 |
151 |
155 |
159 |
160 | );
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/example/app.js:
--------------------------------------------------------------------------------
1 | require('../src/less/base.less');
2 | require('./app.less');
3 |
4 | import React from 'react';
5 | import ReactDOM from 'react-dom';
6 | import moment from 'moment';
7 | import {InputMoment, BigInputMoment, DatePicker, DatePickerRange, TimePicker} from '../src/index';
8 |
9 | class App extends React.Component {
10 | constructor(props) {
11 | super(props);
12 |
13 | this.state = {
14 | inputMoment: moment(),
15 | bigInputMoment: moment(),
16 | datePickerMoment: moment(),
17 | datePickerRangeStartMoment: moment().subtract(3, 'days'),
18 | datePickerRangeEndMoment: moment(),
19 | timePickerMoment: moment(),
20 | showSeconds: true,
21 | locale: 'en',
22 | size: 'medium'
23 | };
24 | }
25 |
26 | render() {
27 | let {inputMoment, bigInputMoment, datePickerMoment, datePickerRangeStartMoment, datePickerRangeEndMoment, timePickerMoment, showSeconds, locale, size} = this.state;
28 | let wrapperClass = 'wrapper ' + size;
29 |
30 | return (
31 |
32 |
33 | React Input Moment
34 |
35 |
36 |
window.location = 'https://github.com/wayofthefuture/react-input-moment'}
41 | />
42 |
window.location = 'https://www.npmjs.com/package/react-input-moment'}
47 | />
48 |
49 |
50 |
51 |
52 |
57 | Show Seconds
58 |
59 |
60 |
61 | Locale:
62 |
63 | English
64 | French
65 | Arabic
66 |
67 |
68 |
69 |
70 | this.setState({size: 'small'})}
75 | />
76 | this.setState({size: 'medium'})}
81 | />
82 | this.setState({size: 'large'})}
87 | />
88 |
89 |
90 |
InputMoment
91 |
97 |
98 | this.setState({inputMoment: mom})}
103 | />
104 |
105 |
106 |
107 |
BigInputMoment
108 |
114 |
115 | this.setState({bigInputMoment: mom})}
120 | />
121 |
122 |
123 |
124 |
DatePicker
125 |
131 |
132 | this.setState({datePickerMoment: mom})}
137 | />
138 |
139 |
140 |
141 |
DatePickerRange
142 |
149 |
150 | this.setState({datePickerRangeStartMoment: startMoment, datePickerRangeEndMoment: endMoment})}
155 | />
156 |
157 |
158 |
159 |
TimePicker
160 |
166 |
167 | this.setState({timePickerMoment: mom})}
172 | />
173 |
174 |
175 | );
176 | }
177 |
178 | handleShowSeconds(e) {
179 | this.setState({showSeconds: e.target.checked});
180 | }
181 |
182 | handleLocale(e) {
183 | this.setState({locale: e.target.value});
184 | }
185 | }
186 |
187 | ReactDOM.render( , document.getElementById('app'));
188 |
189 | //testing
190 | window.moment = moment;
191 |
--------------------------------------------------------------------------------