├── 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 | 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 | 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 | 25 | ); 26 | })} 27 | 28 | ); 29 | })} 30 | 31 |
this.props.onMonthSelect(month)}> 23 | {month} 24 |
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 | 34 | 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) => )} 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 |
{week}
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) => )} 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 |
{week}
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 | 59 |
60 | 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 | --------------------------------------------------------------------------------