├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .prettierrc ├── .travis.yml ├── README.md ├── example ├── README.md ├── build │ ├── asset-manifest.json │ ├── favicon.ico │ ├── index.html │ ├── manifest.json │ ├── precache-manifest.1a2423046b400a55b60a4f8f1f242f5e.js │ ├── service-worker.js │ └── static │ │ ├── css │ │ ├── main.ba0e79f2.chunk.css │ │ └── main.ba0e79f2.chunk.css.map │ │ └── js │ │ ├── 2.60e1af46.chunk.js │ │ ├── 2.60e1af46.chunk.js.LICENSE.txt │ │ ├── 2.60e1af46.chunk.js.map │ │ ├── main.6e2d41ae.chunk.js │ │ ├── main.6e2d41ae.chunk.js.map │ │ ├── runtime-main.e68ab877.js │ │ └── runtime-main.e68ab877.js.map ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src │ ├── App.js │ ├── DatePicker │ │ ├── DatePickerDemo.js │ │ ├── PropSelector.js │ │ └── datepicker.hooks.js │ ├── DateRangeCalendarPicker │ │ ├── DateRangeCalendarPickerDemo.js │ │ ├── PropSelector.js │ │ └── daterangecalendarpicker.hooks.js │ ├── DateRangePicker │ │ ├── DateRangePickerDemo.js │ │ ├── PropSelector.js │ │ └── daterangepicker.hooks.js │ ├── DateTimePicker │ │ ├── DateTimePickerDemo.js │ │ ├── PropSelector.js │ │ └── datetimepicker.hooks.js │ ├── DateTimeRangePicker │ │ ├── DateTimeRangePickerDemo.js │ │ ├── PropSelector.js │ │ └── datetimerangepicker.hooks.js │ ├── MonthPicker │ │ ├── MonthPickerDemo.js │ │ └── monthpicker.hooks.js │ ├── TimePicker │ │ ├── PropSelector.js │ │ ├── TimePickerDemo.js │ │ └── timepicker.hooks.js │ ├── components │ │ ├── StyleSelector.js │ │ └── ThemeSelector.js │ ├── index.js │ ├── styles │ │ ├── demo.css │ │ ├── layout.css │ │ └── materialize.css │ └── test │ │ └── AllInputTest.js └── yarn.lock ├── package.json ├── src ├── .eslintrc ├── components │ ├── ClockFace.tsx │ ├── DatePicker.tsx │ ├── DatePickerInput.tsx │ ├── DateRangeCalendarPicker.tsx │ ├── DateRangeCalendarPickerInput.tsx │ ├── DateRangePicker.tsx │ ├── DateRangePickerInput.tsx │ ├── DateTimePicker.tsx │ ├── DateTimePickerInput.tsx │ ├── MonthPicker.tsx │ ├── RangePicker.tsx │ ├── RangePickerInput.tsx │ ├── TimePicker.tsx │ └── TimePickerInput.tsx ├── index.tsx ├── interfaces │ ├── datepicker.interfaces.ts │ ├── datetimepicker.interfaces.ts │ ├── monthpicker.interfaces.ts │ ├── rangepicker.interfaces.ts │ ├── style.interfaces.ts │ └── timepicker.interfaces.ts ├── styles │ ├── clockface.colors.ts │ ├── date_time_picker.css │ ├── datepicker.color.ts │ ├── datepicker.css │ ├── monthpicker.colors.ts │ ├── monthpicker.css │ ├── rangepicker.colors.ts │ ├── rangepicker.css │ ├── root.css │ └── timepicker.css ├── typings.d.ts └── utils │ ├── datepicker.utils.ts │ ├── datetimepicker.utils.ts │ ├── monthpicker.utils.ts │ ├── style.theme.tsx │ ├── style.utils.tsx │ ├── timepicker.utils.ts │ └── useOutsideAlerter.hook.ts ├── tsconfig.json ├── tsconfig.test.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | node_modules/ 4 | .snapshots/ 5 | *.min.js -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "standard", 5 | "standard-react", 6 | "plugin:prettier/recommended", 7 | "prettier/standard", 8 | "prettier/react", 9 | "plugin:@typescript-eslint/eslint-recommended" 10 | ], 11 | "env": { 12 | "node": true 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 2020, 16 | "ecmaFeatures": { 17 | "legacyDecorators": true, 18 | "jsx": true 19 | } 20 | }, 21 | "settings": { 22 | "react": { 23 | "version": "16" 24 | } 25 | }, 26 | "rules": { 27 | "space-before-function-paren": 0, 28 | "react/prop-types": 0, 29 | "react/jsx-handler-names": 0, 30 | "react/jsx-fragments": 0, 31 | "react/no-unused-prop-types": 0, 32 | "import/export": 0 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .cache 3 | .idea 4 | .vscode 5 | dist 6 | coverage 7 | node_modules 8 | npm-debug.log 9 | yarn-error.log -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "jsxSingleQuote": true, 4 | "semi": false, 5 | "tabWidth": 2, 6 | "bracketSpacing": true, 7 | "jsxBracketSameLine": false, 8 | "arrowParens": "always", 9 | "trailingComma": "none" 10 | } 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 12 4 | - 10 5 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | This example was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | It is linked to the react-datetime-range-super-picker package in the parent directory for development purposes. 4 | 5 | You can run `yarn install` and then `yarn start` to test your package. 6 | -------------------------------------------------------------------------------- /example/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/react-datetime-range-super-picker/static/css/main.ba0e79f2.chunk.css", 4 | "main.js": "/react-datetime-range-super-picker/static/js/main.6e2d41ae.chunk.js", 5 | "main.js.map": "/react-datetime-range-super-picker/static/js/main.6e2d41ae.chunk.js.map", 6 | "runtime-main.js": "/react-datetime-range-super-picker/static/js/runtime-main.e68ab877.js", 7 | "runtime-main.js.map": "/react-datetime-range-super-picker/static/js/runtime-main.e68ab877.js.map", 8 | "static/js/2.60e1af46.chunk.js": "/react-datetime-range-super-picker/static/js/2.60e1af46.chunk.js", 9 | "static/js/2.60e1af46.chunk.js.map": "/react-datetime-range-super-picker/static/js/2.60e1af46.chunk.js.map", 10 | "index.html": "/react-datetime-range-super-picker/index.html", 11 | "precache-manifest.1a2423046b400a55b60a4f8f1f242f5e.js": "/react-datetime-range-super-picker/precache-manifest.1a2423046b400a55b60a4f8f1f242f5e.js", 12 | "service-worker.js": "/react-datetime-range-super-picker/service-worker.js", 13 | "static/css/main.ba0e79f2.chunk.css.map": "/react-datetime-range-super-picker/static/css/main.ba0e79f2.chunk.css.map", 14 | "static/js/2.60e1af46.chunk.js.LICENSE.txt": "/react-datetime-range-super-picker/static/js/2.60e1af46.chunk.js.LICENSE.txt" 15 | }, 16 | "entrypoints": [ 17 | "static/js/runtime-main.e68ab877.js", 18 | "static/js/2.60e1af46.chunk.js", 19 | "static/css/main.ba0e79f2.chunk.css", 20 | "static/js/main.6e2d41ae.chunk.js" 21 | ] 22 | } -------------------------------------------------------------------------------- /example/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dishant15/react-datetime-range-super-picker/718f12b5ffc09c7d6606c6ea6e67160472b88896/example/build/favicon.ico -------------------------------------------------------------------------------- /example/build/index.html: -------------------------------------------------------------------------------- 1 | React Datetime Range Super Picker
-------------------------------------------------------------------------------- /example/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "react-datetime-range-super-picker", 3 | "name": "react-datetime-range-super-picker", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /example/build/precache-manifest.1a2423046b400a55b60a4f8f1f242f5e.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = (self.__precacheManifest || []).concat([ 2 | { 3 | "revision": "a88f485f45d112c4a4f209ef0af3da86", 4 | "url": "/react-datetime-range-super-picker/index.html" 5 | }, 6 | { 7 | "revision": "976130d209a86dc4ec2b", 8 | "url": "/react-datetime-range-super-picker/static/css/main.ba0e79f2.chunk.css" 9 | }, 10 | { 11 | "revision": "2568cfadc0227b5b4d1d", 12 | "url": "/react-datetime-range-super-picker/static/js/2.60e1af46.chunk.js" 13 | }, 14 | { 15 | "revision": "90565ba0b43e1b95d2876295dfd7014d", 16 | "url": "/react-datetime-range-super-picker/static/js/2.60e1af46.chunk.js.LICENSE.txt" 17 | }, 18 | { 19 | "revision": "976130d209a86dc4ec2b", 20 | "url": "/react-datetime-range-super-picker/static/js/main.6e2d41ae.chunk.js" 21 | }, 22 | { 23 | "revision": "a8ed8de4a17a35664d97", 24 | "url": "/react-datetime-range-super-picker/static/js/runtime-main.e68ab877.js" 25 | } 26 | ]); -------------------------------------------------------------------------------- /example/build/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"); 15 | 16 | importScripts( 17 | "/react-datetime-range-super-picker/precache-manifest.1a2423046b400a55b60a4f8f1f242f5e.js" 18 | ); 19 | 20 | self.addEventListener('message', (event) => { 21 | if (event.data && event.data.type === 'SKIP_WAITING') { 22 | self.skipWaiting(); 23 | } 24 | }); 25 | 26 | workbox.core.clientsClaim(); 27 | 28 | /** 29 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 30 | * requests for URLs in the manifest. 31 | * See https://goo.gl/S9QRab 32 | */ 33 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 34 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 35 | 36 | workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/react-datetime-range-super-picker/index.html"), { 37 | 38 | blacklist: [/^\/_/,/\/[^/?]+\.[^/]+$/], 39 | }); 40 | -------------------------------------------------------------------------------- /example/build/static/js/2.60e1af46.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /** 8 | * @license 9 | * Lodash 10 | * Copyright OpenJS Foundation and other contributors 11 | * Released under MIT license 12 | * Based on Underscore.js 1.8.3 13 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 14 | */ 15 | 16 | /** @license React v0.19.1 17 | * scheduler.production.min.js 18 | * 19 | * Copyright (c) Facebook, Inc. and its affiliates. 20 | * 21 | * This source code is licensed under the MIT license found in the 22 | * LICENSE file in the root directory of this source tree. 23 | */ 24 | 25 | /** @license React v16.13.1 26 | * react-dom.production.min.js 27 | * 28 | * Copyright (c) Facebook, Inc. and its affiliates. 29 | * 30 | * This source code is licensed under the MIT license found in the 31 | * LICENSE file in the root directory of this source tree. 32 | */ 33 | 34 | /** @license React v16.13.1 35 | * react.production.min.js 36 | * 37 | * Copyright (c) Facebook, Inc. and its affiliates. 38 | * 39 | * This source code is licensed under the MIT license found in the 40 | * LICENSE file in the root directory of this source tree. 41 | */ 42 | -------------------------------------------------------------------------------- /example/build/static/js/runtime-main.e68ab877.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,a,p=r[0],i=r[1],l=r[2],f=0,s=[];f0.2%", 24 | "not dead", 25 | "not ie <= 11", 26 | "not op_mini all" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dishant15/react-datetime-range-super-picker/718f12b5ffc09c7d6606c6ea6e67160472b88896/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 16 | 17 | 18 | 27 | React Datetime Range Super Picker 28 | 29 | 30 | 31 | 34 | 35 |
36 | 37 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "react-datetime-range-super-picker", 3 | "name": "react-datetime-range-super-picker", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /example/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from 'react' 2 | 3 | import TimePickerDemo from './TimePicker/TimePickerDemo' 4 | import DatePickerDemo from './DatePicker/DatePickerDemo' 5 | import MonthPickerDemo from './MonthPicker/MonthPickerDemo' 6 | import DateTimePickerDemo from './DateTimePicker/DateTimePickerDemo' 7 | import DateTimeRangePickerDemo from './DateTimeRangePicker/DateTimeRangePickerDemo' 8 | import DateRangePickerDemo from './DateRangePicker/DateRangePickerDemo' 9 | import DateRangeCalendarPickerDemo from './DateRangeCalendarPicker/DateRangeCalendarPickerDemo' 10 | 11 | import './styles/materialize.css' 12 | import './styles/layout.css' 13 | import './styles/demo.css' 14 | import 'react-datetime-range-super-picker/dist/index.css' 15 | 16 | 17 | const App = () => { 18 | 19 | /** 20 | * Parent : 21 | * index 22 | * 23 | * Renders: 24 | * TimePickerDemo 25 | * DatePickerDemo 26 | * MonthPickerDemo 27 | * DateTimePickerDemo 28 | * DateTimeRangePickerDemo 29 | * DateRangePickerDemo 30 | */ 31 | 32 | const [selectedComponent, setComponent] = useState('timePicker') 33 | 34 | const renderComponent = useCallback(() => { 35 | if (selectedComponent === 'timePicker') { 36 | return 37 | } 38 | else if (selectedComponent === 'datePicker') { 39 | return 40 | } 41 | else if (selectedComponent === 'monthPicker') { 42 | return 43 | } 44 | else if (selectedComponent === 'dateTimePicker') { 45 | return 46 | } 47 | else if (selectedComponent === 'rangePicker') { 48 | return 49 | } 50 | else if (selectedComponent === 'dateRangePicker') { 51 | return 52 | } 53 | else if (selectedComponent === 'dateRangeCalendarPicker') { 54 | return 55 | } 56 | }, [selectedComponent]) 57 | 58 | return ( 59 |
60 |
61 |
setComponent('timePicker')}> 63 | access_time 64 | Time Picker 65 |
66 | 67 |
setComponent('datePicker')}> 69 | today 70 | Date Picker 71 |
72 | 73 |
setComponent('dateTimePicker')}> 75 | 76 | today 77 | access_time 78 | 79 |
Date Time Picker
80 |
81 | 82 |
setComponent('dateRangePicker')}> 84 | date_range 85 | Date Range Picker 86 |
87 | 88 |
setComponent('dateRangeCalendarPicker')}> 90 | date_range 91 | Date Range Calendar Picker 92 |
93 | 94 | 95 |
setComponent('rangePicker')}> 97 | date_range 98 | Date Time Range Picker 99 |
100 | 101 |
setComponent('monthPicker')}> 103 | apps 104 | Month Picker 105 |
106 |
107 | 108 |
109 | 111 | 117 | View on GitHub 118 | 119 | {renderComponent()} 120 |
121 |
122 | ) 123 | } 124 | 125 | export default App 126 | -------------------------------------------------------------------------------- /example/src/DatePicker/DatePickerDemo.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { DatePicker, DatePickerInput } from 'react-datetime-range-super-picker'; 3 | 4 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; 5 | import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; 6 | 7 | import PropSelector from "./PropSelector"; 8 | import StyleSelector from "../components/StyleSelector"; 9 | import ThemeSelector from "../components/ThemeSelector"; 10 | 11 | import { useDatePickerProps } from './datepicker.hooks'; 12 | 13 | export default () => { 14 | 15 | /** 16 | * Parent: 17 | * App 18 | * 19 | * Renders: 20 | * PropSelector 21 | * StyleSelector 22 | * ThemeSelector 23 | * 24 | * DatePicker 25 | * DatePickerInput 26 | */ 27 | 28 | const [pickerProps, pickerHtml, handlePropsUpdate, 29 | isInput, handleToggleInput] = useDatePickerProps() 30 | 31 | const [curr_date, setDate] = useState(new Date()) 32 | const [isCopy, setCopy] = useState(false) 33 | 34 | 35 | const handleDateUpdate = ({date}) => { 36 | setDate(date) 37 | } 38 | 39 | const DProps = {...pickerProps, 40 | date: curr_date, onDateUpdate: handleDateUpdate 41 | } 42 | 43 | const handleInputToggle = (e) => { 44 | if(e) e.preventDefault(); 45 | handleToggleInput(!isInput) 46 | } 47 | 48 | const onCopyClick = () => { 49 | window.Clipboard.copy(pickerHtml); 50 | setCopy(true) 51 | 52 | setTimeout(() => { 53 | setCopy(false) 54 | }, 3000); 55 | } 56 | 57 | return ( 58 |
59 |
Date Picker
60 | 61 |
62 | 63 |
64 |
65 |
66 | 71 |
72 | 73 | {isInput ? 74 |
75 | 76 |
77 | : 78 | 79 | } 80 |
81 | 82 | 83 | 84 |
85 | 86 |
87 | 88 |
89 | {isCopy ? 90 |
91 | content_paste 92 |
93 | : 94 |
95 | content_copy 96 |
97 | } 98 | 99 | {pickerHtml} 100 | 101 |
102 |
103 |
104 | 105 |
106 | 107 |
108 |
109 | 110 |
111 | 112 |
113 | 114 |
115 |
116 |
117 | ) 118 | } -------------------------------------------------------------------------------- /example/src/DatePicker/PropSelector.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | export default ({pickerProps, handlePropsUpdate, isInput}) => { 4 | 5 | const [format, setFormat] = useState(pickerProps.format || undefined) 6 | const [weekStartsOn, setWeekStartsOn] = useState(pickerProps.weekStartsOn || undefined) 7 | const [isDisabled, setIsDisabled] = useState(false) 8 | 9 | const handleSubmit = () => { 10 | handlePropsUpdate({...pickerProps, 11 | format, isDisabled, 12 | weekStartsOn: !!Number(weekStartsOn) ? Number(weekStartsOn) : undefined, 13 | }) 14 | } 15 | 16 | const handleCheckbox = (e) => { 17 | if(e) e.preventDefault(); 18 | setIsDisabled(!isDisabled) 19 | } 20 | 21 | return ( 22 |
23 |
24 |
25 |

Props Selector

26 | 27 |
28 | setFormat(e.target.value)} /> 31 | 32 |
33 | 34 |
35 | setWeekStartsOn(e.target.value)} /> 38 | 39 |
40 | 41 | {isInput && 42 |
43 |
44 | 49 |
50 |
51 | } 52 | 53 |
54 |
55 |
56 | 60 |
61 |
62 | ) 63 | } -------------------------------------------------------------------------------- /example/src/DatePicker/datepicker.hooks.js: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from "react" 2 | import { isEmpty } from "lodash"; 3 | 4 | 5 | const generatePickerHtml = ({format, weekStartsOn, isInput, 6 | theme, colors, isDisabled}) => { 7 | const componentStr = isInput ? 'DatePickerInput' : 'DatePicker' 8 | 9 | let propStr = `date={curr_date} 10 | onDateUpdate={handleDateUpdate}` 11 | 12 | if(!!format) { 13 | propStr += `\n\t\t\t\tformat="${format}"` 14 | } 15 | 16 | if(!!theme) { 17 | propStr += `\n\t\t\t\ttheme="${theme}"` 18 | } 19 | 20 | if(!!weekStartsOn) { 21 | propStr += `\n\t\t\t\tweekStartsOn={${weekStartsOn}}` 22 | } 23 | 24 | if(!isEmpty(colors)) { 25 | propStr += `\n\t\t\t\tcolors={${JSON.stringify(colors)}}` 26 | } 27 | 28 | if(!!isDisabled && isInput) { 29 | propStr += `\n\t\t\t\t\isDisabled={${isDisabled}}` //eslint-disable-line 30 | } 31 | 32 | return ` 33 | import React, { useState } from 'react'; 34 | import { ${componentStr} } from 'react-datetime-range-super-picker'; 35 | 36 | import 'react-datetime-range-super-picker/dist/index.css' 37 | 38 | const DatePickerComponent = () => { 39 | const [curr_date, setDate] = useState(new Date()) 40 | 41 | const handleDateUpdate = ({date}) => { 42 | setDate(date) 43 | } 44 | 45 | return ( 46 | <${componentStr} ${propStr} /> 47 | ) 48 | } 49 | ` 50 | } 51 | 52 | export const useDatePickerProps = () => { 53 | 54 | const [isInput, setInput] = useState(false) 55 | const [pickerProps, setPickerProps] = useState({}) 56 | const [pickerHtml, setPickerHtml] = useState(generatePickerHtml({})) 57 | 58 | const handlePropsUpdate = useCallback((newProps) => { 59 | setPickerProps(newProps) 60 | setPickerHtml(generatePickerHtml({...newProps, isInput})) 61 | }, [isInput]) 62 | 63 | const handleToggleInput = useCallback((isInput) => { 64 | setInput(isInput) 65 | setPickerHtml(generatePickerHtml({...pickerProps, isInput})) 66 | }, [pickerProps]) 67 | 68 | return [pickerProps, pickerHtml, handlePropsUpdate, isInput, handleToggleInput] 69 | } -------------------------------------------------------------------------------- /example/src/DateRangeCalendarPicker/DateRangeCalendarPickerDemo.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { DateRangeCalendarPicker, DateRangeCalendarPickerInput } from 'react-datetime-range-super-picker'; 3 | 4 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; 5 | import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; 6 | 7 | import PropSelector from "./PropSelector"; 8 | import StyleSelector from "../components/StyleSelector"; 9 | import ThemeSelector from "../components/ThemeSelector"; 10 | 11 | import { useRangePickerProps } from './daterangecalendarpicker.hooks'; 12 | 13 | 14 | export default () => { 15 | 16 | const [pickerProps, pickerHtml, handlePropsUpdate, 17 | isInput, handleToggleInput ] = useRangePickerProps() 18 | 19 | const [from_date, setFromDate] = useState(new Date()) 20 | const [to_date, setToDate] = useState(new Date()) 21 | const [isCopy, setCopy] = useState(false) 22 | 23 | 24 | const handleFromDateUpdate = ({date}) => { 25 | setFromDate(date) 26 | } 27 | const handleToDateUpdate = ({date}) => { 28 | setToDate(date) 29 | } 30 | 31 | const TProps = {...pickerProps, 32 | from_date, to_date, 33 | onFromDateUpdate: handleFromDateUpdate, 34 | onToDateUpdate: handleToDateUpdate 35 | } 36 | 37 | const handleInputToggle = (e) => { 38 | if(e) e.preventDefault(); 39 | handleToggleInput(!isInput) 40 | } 41 | 42 | const onCopyClick = () => { 43 | window.Clipboard.copy(pickerHtml); 44 | setCopy(true) 45 | 46 | setTimeout(() => { 47 | setCopy(false) 48 | }, 3000); 49 | } 50 | 51 | return ( 52 |
53 |
Date Range Calendar Picker
54 | 55 |
56 | 57 |
58 |
59 | 60 |
61 | 66 |
67 | {isInput ? 68 |
69 | 70 |
71 | : 72 | 73 | } 74 |
75 | 76 | 77 |
78 | 79 |
80 |
81 | {isCopy ? 82 |
83 | content_paste 84 |
85 | : 86 |
87 | content_copy 88 |
89 | } 90 | 91 | {pickerHtml} 92 | 93 |
94 |
95 |
96 | 97 |
98 | 99 |
100 |
101 | 102 |
103 | 104 |
105 | 106 |
107 |
108 |
109 | ) 110 | } 111 | -------------------------------------------------------------------------------- /example/src/DateRangeCalendarPicker/PropSelector.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | export default ({ pickerProps, handlePropsUpdate, isInput }) => { 4 | 5 | const [format, setFormat] = useState(pickerProps.format || undefined) 6 | const [weekStartsOn, setWeekStartsOn] = useState(pickerProps.weekStartsOn || undefined) 7 | // const [closeButtonText, setCloseBtnText] = useState(pickerProps.closeButtonText || undefined) 8 | const [isDisabled, setIsDisabled] = useState(false) 9 | 10 | const handleSubmit = () => { 11 | handlePropsUpdate({ ...pickerProps, format, isDisabled, 12 | weekStartsOn: !!Number(weekStartsOn) ? Number(weekStartsOn) : undefined 13 | }) 14 | } 15 | 16 | const handleCheckbox = (e) => { 17 | if (e) e.preventDefault(); 18 | setIsDisabled(!isDisabled) 19 | } 20 | 21 | return ( 22 |
23 |
24 |
25 |

Props Selector

26 | 27 |
28 | setFormat(e.target.value)} /> 31 | 32 |
33 | 34 |
35 | setWeekStartsOn(e.target.value)} /> 38 | 39 |
40 | 41 | {isInput && 42 |
43 |
44 | 49 |
50 |
51 | } 52 | 53 |
54 |
55 |
56 | 60 |
61 |
62 | ) 63 | } -------------------------------------------------------------------------------- /example/src/DateRangeCalendarPicker/daterangecalendarpicker.hooks.js: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from "react" 2 | import { isEmpty } from "lodash"; 3 | 4 | 5 | const generatePickerHtml = ({format, weekStartsOn, 6 | theme, colors, closeButtonText, isInput, isDisabled}) => { 7 | 8 | const componentStr = isInput ? 'DateRangeCalendarPickerInput' : 'DateRangeCalendarPicker' 9 | 10 | let propStr = `from_date={from_date} to_date={to_date} 11 | onFromDateUpdate={handleFromDateUpdate} 12 | onToDateUpdate={handleToDateUpdate}` 13 | 14 | if(!!format) { 15 | propStr += `\n\t\t\t\tformat="${format}"` 16 | } 17 | 18 | if(!!weekStartsOn) { 19 | propStr += `\n\t\t\t\tweekStartsOn={${weekStartsOn}}` 20 | } 21 | 22 | if(!!closeButtonText) { 23 | propStr += `\n\t\t\t\t\closeButtonText="${closeButtonText}"` //eslint-disable-line 24 | } 25 | 26 | if(!!theme) { 27 | propStr += `\n\t\t\t\ttheme="${theme}"` 28 | } 29 | 30 | if(!isEmpty(colors)) { 31 | propStr += `\n\t\t\t\tcolors={${JSON.stringify(colors)}}` 32 | } 33 | 34 | if(!!isDisabled) { 35 | propStr += `\n\t\t\t\t\isDisabled={${isDisabled}}` //eslint-disable-line 36 | } 37 | 38 | 39 | return ` 40 | import React, { useState } from 'react'; 41 | import { ${componentStr} } from 'react-datetime-range-super-picker'; 42 | 43 | import 'react-datetime-range-super-picker/dist/index.css' 44 | 45 | const DateRangeCalendarPickerComponent = () => { 46 | const [from_date, setFromDate] = useState(new Date()) 47 | const [to_date, setToDate] = useState(new Date()) 48 | 49 | const handleFromDateUpdate = ({date}) => { 50 | setFromDate(date) 51 | } 52 | 53 | const handleToDateUpdate = ({date}) => { 54 | setToDate(date) 55 | } 56 | 57 | return ( 58 | <${componentStr} ${propStr} /> 59 | ) 60 | } 61 | ` 62 | } 63 | 64 | export const useRangePickerProps = () => { 65 | 66 | const [isInput, setInput] = useState(false) 67 | const [pickerProps, setPickerProps] = useState({}) 68 | const [pickerHtml, setPickerHtml] = useState(generatePickerHtml({})) 69 | 70 | const handlePropsUpdate = useCallback((newProps) => { 71 | setPickerProps(newProps) 72 | setPickerHtml(generatePickerHtml({...newProps, isInput})) 73 | }, [isInput]) 74 | 75 | const handleToggleInput = useCallback((isInput) => { 76 | setInput(isInput) 77 | setPickerHtml(generatePickerHtml({...pickerProps, isInput})) 78 | }, [pickerProps]) 79 | 80 | return [pickerProps, pickerHtml, handlePropsUpdate, isInput, handleToggleInput] 81 | } -------------------------------------------------------------------------------- /example/src/DateRangePicker/DateRangePickerDemo.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { DateRangePickerInput, DateRangePicker } from 'react-datetime-range-super-picker'; 3 | 4 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; 5 | import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; 6 | 7 | import PropSelector from "./PropSelector"; 8 | import StyleSelector from "../components/StyleSelector"; 9 | import ThemeSelector from "../components/ThemeSelector"; 10 | 11 | import { useRangePickerProps } from './daterangepicker.hooks'; 12 | 13 | 14 | export default () => { 15 | 16 | /** 17 | * Parent: 18 | * App 19 | * 20 | * Renders: 21 | * PropSelector 22 | * StyleSelector 23 | * ThemeSelector 24 | * 25 | * DateRangePickerInput 26 | */ 27 | 28 | const [pickerProps, pickerHtml, handlePropsUpdate, 29 | isInput, handleToggleInput ] = useRangePickerProps() 30 | 31 | const [from_date, setFromDate] = useState(new Date()) 32 | const [to_date, setToDate] = useState(new Date()) 33 | const [isCopy, setCopy] = useState(false) 34 | 35 | 36 | const handleFromDateUpdate = ({date}) => { 37 | setFromDate(date) 38 | } 39 | const handleToDateUpdate = ({date}) => { 40 | setToDate(date) 41 | } 42 | 43 | const TProps = {...pickerProps, 44 | from_date, to_date, 45 | onFromDateUpdate: handleFromDateUpdate, 46 | onToDateUpdate: handleToDateUpdate 47 | } 48 | 49 | const handleInputToggle = (e) => { 50 | if(e) e.preventDefault(); 51 | handleToggleInput(!isInput) 52 | } 53 | 54 | const onCopyClick = () => { 55 | window.Clipboard.copy(pickerHtml); 56 | setCopy(true) 57 | 58 | setTimeout(() => { 59 | setCopy(false) 60 | }, 3000); 61 | } 62 | 63 | return ( 64 |
65 |
Date Range Picker (Beta)
66 | 67 |
68 | 69 |
70 |
71 | 72 |
73 | 78 |
79 | {isInput ? 80 |
81 | 82 |
83 | : 84 | 85 | } 86 |
87 | 88 | 89 |
90 | 91 |
92 |
93 | {isCopy ? 94 |
95 | content_paste 96 |
97 | : 98 |
99 | content_copy 100 |
101 | } 102 | 103 | {pickerHtml} 104 | 105 |
106 |
107 |
108 | 109 |
110 | 111 |
112 |
113 | 114 |
115 | 116 |
117 | 118 |
119 |
120 |
121 | ) 122 | } -------------------------------------------------------------------------------- /example/src/DateRangePicker/PropSelector.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | export default ({ pickerProps, handlePropsUpdate, isInput }) => { 4 | 5 | const [format, setFormat] = useState(pickerProps.format || undefined) 6 | const [weekStartsOn, setWeekStartsOn] = useState(pickerProps.weekStartsOn || undefined) 7 | const [isDisabled, setIsDisabled] = useState(false) 8 | 9 | const handleSubmit = () => { 10 | handlePropsUpdate({ ...pickerProps, format, isDisabled, 11 | weekStartsOn: !!Number(weekStartsOn) ? Number(weekStartsOn) : undefined 12 | }) 13 | } 14 | 15 | const handleCheckbox = (e) => { 16 | if (e) e.preventDefault(); 17 | setIsDisabled(!isDisabled) 18 | } 19 | 20 | return ( 21 |
22 |
23 |
24 |

Props Selector

25 | 26 |
27 | setFormat(e.target.value)} /> 30 | 31 |
32 | 33 |
34 | setWeekStartsOn(e.target.value)} /> 37 | 38 |
39 | 40 | {isInput && 41 |
42 |
43 | 48 |
49 |
50 | } 51 | 52 |
53 |
54 |
55 | 59 |
60 |
61 | ) 62 | } -------------------------------------------------------------------------------- /example/src/DateRangePicker/daterangepicker.hooks.js: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from "react" 2 | import { isEmpty } from "lodash"; 3 | 4 | 5 | const generatePickerHtml = ({format, weekStartsOn, 6 | theme, colors, closeButtonText, isInput, isDisabled}) => { 7 | 8 | const componentStr = isInput ? 'DateRangePickerInput' : 'DateRangePicker' 9 | 10 | let propStr = `from_date={from_date} to_date={to_date} 11 | onFromDateUpdate={handleFromDateUpdate} 12 | onToDateUpdate={handleToDateUpdate}` 13 | 14 | if(!!format) { 15 | propStr += `\n\t\t\t\tformat="${format}"` 16 | } 17 | 18 | if(!!weekStartsOn) { 19 | propStr += `\n\t\t\t\tweekStartsOn={${weekStartsOn}}` 20 | } 21 | 22 | if(!!closeButtonText) { 23 | propStr += `\n\t\t\t\t\closeButtonText="${closeButtonText}"` //eslint-disable-line 24 | } 25 | 26 | if(!!theme) { 27 | propStr += `\n\t\t\t\ttheme="${theme}"` 28 | } 29 | 30 | if(!isEmpty(colors)) { 31 | propStr += `\n\t\t\t\tcolors={${JSON.stringify(colors)}}` 32 | } 33 | 34 | if(!!isDisabled) { 35 | propStr += `\n\t\t\t\t\isDisabled={${isDisabled}}` //eslint-disable-line 36 | } 37 | 38 | return ` 39 | import React, { useState } from 'react'; 40 | import { ${componentStr} } from 'react-datetime-range-super-picker'; 41 | 42 | import 'react-datetime-range-super-picker/dist/index.css' 43 | 44 | const DateRangePickerComponent = () => { 45 | const [from_date, setFromDate] = useState(new Date()) 46 | const [to_date, setToDate] = useState(new Date()) 47 | 48 | const handleFromDateUpdate = ({date}) => { 49 | setFromDate(date) 50 | } 51 | 52 | const handleToDateUpdate = ({date}) => { 53 | setToDate(date) 54 | } 55 | 56 | return ( 57 | <${componentStr} ${propStr} /> 58 | ) 59 | } 60 | ` 61 | } 62 | 63 | export const useRangePickerProps = () => { 64 | 65 | const [isInput, setInput] = useState(false) 66 | const [pickerProps, setPickerProps] = useState({}) 67 | const [pickerHtml, setPickerHtml] = useState(generatePickerHtml({})) 68 | 69 | const handlePropsUpdate = useCallback((newProps) => { 70 | setPickerProps(newProps) 71 | setPickerHtml(generatePickerHtml({...newProps, isInput})) 72 | }, [isInput]) 73 | 74 | const handleToggleInput = useCallback((isInput) => { 75 | setInput(isInput) 76 | setPickerHtml(generatePickerHtml({...pickerProps, isInput})) 77 | }, [pickerProps]) 78 | 79 | return [pickerProps, pickerHtml, handlePropsUpdate, isInput, handleToggleInput] 80 | } -------------------------------------------------------------------------------- /example/src/DateTimePicker/DateTimePickerDemo.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { DateTimePicker, DateTimePickerInput } from 'react-datetime-range-super-picker'; 3 | 4 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; 5 | import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; 6 | 7 | import PropSelector from "./PropSelector"; 8 | import StyleSelector from "../components/StyleSelector"; 9 | import ThemeSelector from "../components/ThemeSelector"; 10 | 11 | import { useDateTimePickerProps } from './datetimepicker.hooks'; 12 | 13 | 14 | export default () => { 15 | 16 | /** 17 | * Parent: 18 | * App 19 | * 20 | * Renders: 21 | * PropSelector 22 | * StyleSelector 23 | * ThemeSelector 24 | * 25 | * DateTimePicker 26 | * DateTimePickerInput 27 | */ 28 | 29 | const [pickerProps, pickerHtml, handlePropsUpdate, 30 | isInput, handleToggleInput] = useDateTimePickerProps() 31 | 32 | const [curr_date, setDate] = useState(new Date()) 33 | const [isCopy, setCopy] = useState(false) 34 | 35 | 36 | const handleDateUpdate = ({date}) => { 37 | setDate(date.date) 38 | } 39 | 40 | const TProps = {...pickerProps, 41 | date: curr_date, onDateTimeUpdate: handleDateUpdate 42 | } 43 | 44 | const handleInputToggle = (e) => { 45 | if(e) e.preventDefault(); 46 | handleToggleInput(!isInput) 47 | } 48 | 49 | const onCopyClick = () => { 50 | window.Clipboard.copy(pickerHtml); 51 | setCopy(true) 52 | 53 | setTimeout(() => { 54 | setCopy(false) 55 | }, 3000); 56 | } 57 | 58 | return ( 59 |
60 |
Date Time Picker
61 | 62 |
63 | 64 |
65 |
66 |
67 | 72 |
73 | 74 | {isInput ? 75 |
76 | 77 |
78 | : 79 | } 80 |
81 | 82 | 83 |
84 | 85 |
86 | 87 |
88 | {isCopy ? 89 |
90 | content_paste 91 |
92 | : 93 |
94 | content_copy 95 |
96 | } 97 | 98 | {pickerHtml} 99 | 100 |
101 |
102 |
103 | 104 |
105 | 106 |
107 |
108 | 109 |
110 | 111 |
112 | 113 |
114 |
115 |
116 | ) 117 | } -------------------------------------------------------------------------------- /example/src/DateTimePicker/PropSelector.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | export default ({pickerProps, handlePropsUpdate, isInput}) => { 4 | 5 | const [format, setFormat] = useState(pickerProps.format || undefined) 6 | const [timeFormat, setTimeFormat] = useState(pickerProps.timeFormat || undefined) 7 | const [dateFormat, setDateFormat] = useState(pickerProps.dateFormat || undefined) 8 | const [weekStartsOn, setWeekStartsOn] = useState(pickerProps.weekStartsOn || undefined) 9 | const [closeButtonText, setCloseBtnText] = useState(pickerProps.closeButtonText || undefined) 10 | const [isDisabled, setIsDisabled] = useState(false) 11 | 12 | const handleSubmit = () => { 13 | handlePropsUpdate({...pickerProps, format, 14 | timeFormat, dateFormat, closeButtonText, 15 | weekStartsOn: !!Number(weekStartsOn) ? Number(weekStartsOn) : undefined, 16 | isDisabled 17 | }) 18 | } 19 | 20 | const handleCheckbox = (e) => { 21 | if(e) e.preventDefault(); 22 | setIsDisabled(!isDisabled) 23 | } 24 | 25 | return ( 26 |
27 |
28 |
29 |

Props Selector

30 | 31 |
32 | setFormat(e.target.value)} /> 35 | 36 |
37 | 38 |
39 | setTimeFormat(e.target.value)} /> 42 | 43 |
44 | 45 |
46 | setDateFormat(e.target.value)} /> 49 | 50 |
51 | 52 |
53 | setWeekStartsOn(e.target.value)} /> 56 | 57 |
58 | {isInput && 59 | <> 60 |
61 | setCloseBtnText(e.target.value)} /> 64 | 65 |
66 |
67 |
68 | 73 |
74 |
75 | 76 | } 77 |
78 |
79 |
80 | 84 |
85 |
86 | ) 87 | } -------------------------------------------------------------------------------- /example/src/DateTimePicker/datetimepicker.hooks.js: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from "react" 2 | import { isEmpty } from "lodash"; 3 | 4 | 5 | const generatePickerHtml = ({format, timeFormat, dateFormat, weekStartsOn, 6 | isInput, theme, colors, closeButtonText, isDisabled }) => { 7 | const componentStr = isInput ? 'DateTimePickerInput' : 'DateTimePicker' 8 | 9 | let propStr = `date={curr_date} 10 | onDateTimeUpdate={handleDateUpdate}` 11 | 12 | if(!!format) { 13 | propStr += `\n\t\t\t\tformat="${format}"` 14 | } 15 | 16 | if(!!timeFormat) { 17 | propStr += `\n\t\t\t\ttimeFormat="${timeFormat}"` 18 | } 19 | 20 | if(!!dateFormat) { 21 | propStr += `\n\t\t\t\tdateFormat="${dateFormat}"` 22 | } 23 | 24 | if(!!weekStartsOn) { 25 | propStr += `\n\t\t\t\tweekStartsOn={${weekStartsOn}}` 26 | } 27 | 28 | if(!!closeButtonText && isInput) { 29 | propStr += `\n\t\t\t\t\closeButtonText="${closeButtonText}"` //eslint-disable-line 30 | } 31 | 32 | if(!!isDisabled && isInput) { 33 | propStr += `\n\t\t\t\t\isDisabled={${isDisabled}}` //eslint-disable-line 34 | } 35 | 36 | if(!!theme) { 37 | propStr += `\n\t\t\t\ttheme="${theme}"` 38 | } 39 | 40 | if(!isEmpty(colors)) { 41 | propStr += `\n\t\t\t\tcolors={${JSON.stringify(colors)}}` 42 | } 43 | 44 | return ` 45 | import React, { useState } from 'react'; 46 | import { ${componentStr} } from 'react-datetime-range-super-picker'; 47 | 48 | import 'react-datetime-range-super-picker/dist/index.css' 49 | 50 | const DateTimePickerComponent = () => { 51 | const [curr_date, setDate] = useState(new Date()) 52 | 53 | const handleDateUpdate = ({date}) => { 54 | setDate(date.date) 55 | } 56 | 57 | return ( 58 | <${componentStr} ${propStr} /> 59 | ) 60 | } 61 | ` 62 | } 63 | 64 | export const useDateTimePickerProps = () => { 65 | 66 | const [isInput, setInput] = useState(false) 67 | const [pickerProps, setPickerProps] = useState({}) 68 | const [pickerHtml, setPickerHtml] = useState(generatePickerHtml({})) 69 | 70 | const handlePropsUpdate = useCallback((newProps) => { 71 | setPickerProps(newProps) 72 | setPickerHtml(generatePickerHtml({...newProps, isInput})) 73 | }, [isInput]) 74 | 75 | const handleToggleInput = useCallback((isInput) => { 76 | setInput(isInput) 77 | setPickerHtml(generatePickerHtml({...pickerProps, isInput})) 78 | }, [pickerProps]) 79 | 80 | return [pickerProps, pickerHtml, handlePropsUpdate, isInput, handleToggleInput] 81 | } -------------------------------------------------------------------------------- /example/src/DateTimeRangePicker/DateTimeRangePickerDemo.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { DateTimeRangePicker, DateTimeRangePickerInput } from 'react-datetime-range-super-picker'; 3 | 4 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; 5 | import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; 6 | 7 | import PropSelector from "./PropSelector"; 8 | import StyleSelector from "../components/StyleSelector"; 9 | import ThemeSelector from "../components/ThemeSelector"; 10 | 11 | import { useRangePickerProps } from './datetimerangepicker.hooks'; 12 | 13 | 14 | export default () => { 15 | 16 | /** 17 | * Parent: 18 | * App 19 | * 20 | * Renders: 21 | * PropSelector 22 | * StyleSelector 23 | * ThemeSelector 24 | * 25 | * DateTimeRangePicker 26 | * DateTimeRangePickerInput 27 | */ 28 | 29 | const [pickerProps, pickerHtml, handlePropsUpdate, 30 | isInput, handleToggleInput] = useRangePickerProps() 31 | 32 | const [from_date, setFromDate] = useState(new Date()) 33 | const [to_date, setToDate] = useState(new Date()) 34 | const [isCopy, setCopy] = useState(false) 35 | 36 | 37 | const handleFromDateUpdate = ({date}) => { 38 | setFromDate(date.date) 39 | } 40 | const handleToDateUpdate = ({date}) => { 41 | setToDate(date.date) 42 | } 43 | 44 | const TProps = {...pickerProps, 45 | from_date, to_date, 46 | onFromDateTimeUpdate: handleFromDateUpdate, 47 | onToDateTimeUpdate: handleToDateUpdate 48 | } 49 | 50 | const handleInputToggle = (e) => { 51 | if(e) e.preventDefault(); 52 | handleToggleInput(!isInput) 53 | } 54 | 55 | const onCopyClick = () => { 56 | window.Clipboard.copy(pickerHtml); 57 | setCopy(true) 58 | 59 | setTimeout(() => { 60 | setCopy(false) 61 | }, 3000); 62 | } 63 | 64 | return ( 65 |
66 |
Date Time Range Picker
67 | 68 |
69 | 70 |
71 |
72 |
73 | 78 |
79 | 80 | {isInput ? 81 |
82 | 83 |
84 | : 85 | 86 | } 87 |
88 | 89 | 90 |
91 | 92 |
93 |
94 | {isCopy ? 95 |
96 | content_paste 97 |
98 | : 99 |
100 | content_copy 101 |
102 | } 103 | 104 | {pickerHtml} 105 | 106 |
107 |
108 |
109 | 110 |
111 | 112 |
113 |
114 | 115 |
116 | 117 |
118 | 119 |
120 |
121 |
122 | ) 123 | } -------------------------------------------------------------------------------- /example/src/DateTimeRangePicker/PropSelector.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | export default ({pickerProps, handlePropsUpdate, isInput}) => { 4 | 5 | const [format, setFormat] = useState(pickerProps.format || undefined) 6 | const [timeFormat, setTimeFormat] = useState(pickerProps.timeFormat || undefined) 7 | const [dateFormat, setDateFormat] = useState(pickerProps.dateFormat || undefined) 8 | const [weekStartsOn, setWeekStartsOn] = useState(pickerProps.weekStartsOn || undefined) 9 | const [closeButtonText, setCloseBtnText] = useState(pickerProps.closeButtonText || undefined) 10 | const [isDisabled, setIsDisabled] = useState(false) 11 | 12 | const handleSubmit = () => { 13 | handlePropsUpdate({...pickerProps, isDisabled, 14 | format, timeFormat, dateFormat, closeButtonText, 15 | weekStartsOn: !!Number(weekStartsOn) ? Number(weekStartsOn) : undefined 16 | }) 17 | } 18 | 19 | const handleCheckbox = (e) => { 20 | if(e) e.preventDefault(); 21 | setIsDisabled(!isDisabled) 22 | } 23 | 24 | return ( 25 |
26 |
27 |
28 |

Props Selector

29 | 30 |
31 | setFormat(e.target.value)} /> 34 | 35 |
36 | 37 |
38 | setTimeFormat(e.target.value)} /> 41 | 42 |
43 | 44 |
45 | setDateFormat(e.target.value)} /> 48 | 49 |
50 | 51 |
52 | setWeekStartsOn(e.target.value)} /> 55 | 56 |
57 | 58 |
59 | setCloseBtnText(e.target.value)} /> 62 | 63 |
64 | 65 | {isInput && 66 |
67 |
68 | 73 |
74 |
75 | } 76 | 77 |
78 |
79 |
80 | 84 |
85 |
86 | ) 87 | } -------------------------------------------------------------------------------- /example/src/DateTimeRangePicker/datetimerangepicker.hooks.js: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from "react" 2 | import { isEmpty } from "lodash"; 3 | 4 | 5 | const generatePickerHtml = ({format, timeFormat, 6 | dateFormat, weekStartsOn, isInput, theme, 7 | colors, closeButtonText, isDisabled }) => { 8 | 9 | const componentStr = isInput ? 'DateTimePickerInput' : 'DateTimeRangePicker' 10 | 11 | let propStr = `from_date={from_date} to_date={to_date} 12 | onFromDateTimeUpdate={handleFromDateUpdate} 13 | onToDateTimeUpdate={handleToDateUpdate}` 14 | 15 | if(!!format) { 16 | propStr += `\n\t\t\t\tformat="${format}"` 17 | } 18 | 19 | if(!!timeFormat) { 20 | propStr += `\n\t\t\t\ttimeFormat="${timeFormat}"` 21 | } 22 | 23 | if(!!dateFormat) { 24 | propStr += `\n\t\t\t\tdateFormat="${dateFormat}"` 25 | } 26 | 27 | if(!!weekStartsOn) { 28 | propStr += `\n\t\t\t\tweekStartsOn={${weekStartsOn}}` 29 | } 30 | 31 | if(!!closeButtonText) { 32 | propStr += `\n\t\t\t\t\closeButtonText="${closeButtonText}"` //eslint-disable-line 33 | } 34 | 35 | if(!!theme) { 36 | propStr += `\n\t\t\t\ttheme="${theme}"` 37 | } 38 | 39 | if(!isEmpty(colors)) { 40 | propStr += `\n\t\t\t\tcolors={${JSON.stringify(colors)}}` 41 | } 42 | 43 | if(!!isDisabled && isInput) { 44 | propStr += `\n\t\t\t\t\isDisabled={${isDisabled}}` //eslint-disable-line 45 | } 46 | 47 | 48 | return ` 49 | import React, { useState } from 'react'; 50 | import { ${componentStr} } from 'react-datetime-range-super-picker'; 51 | 52 | import 'react-datetime-range-super-picker/dist/index.css' 53 | 54 | const DateTimeRangePickerComponent = () => { 55 | const [from_date, setFromDate] = useState(new Date()) 56 | const [to_date, setToDate] = useState(new Date()) 57 | 58 | const handleFromDateUpdate = ({date}) => { 59 | setFromDate(date.date) 60 | } 61 | 62 | const handleToDateUpdate = ({date}) => { 63 | setToDate(date.date) 64 | } 65 | 66 | return ( 67 | <${componentStr} ${propStr} /> 68 | ) 69 | } 70 | ` 71 | } 72 | 73 | export const useRangePickerProps = () => { 74 | 75 | const [isInput, setInput] = useState(false) 76 | const [pickerProps, setPickerProps] = useState({}) 77 | const [pickerHtml, setPickerHtml] = useState(generatePickerHtml({})) 78 | 79 | const handlePropsUpdate = useCallback((newProps) => { 80 | setPickerProps(newProps) 81 | setPickerHtml(generatePickerHtml({...newProps, isInput})) 82 | }, [isInput]) 83 | 84 | const handleToggleInput = useCallback((isInput) => { 85 | setInput(isInput) 86 | setPickerHtml(generatePickerHtml({...pickerProps, isInput})) 87 | }, [pickerProps]) 88 | 89 | return [pickerProps, pickerHtml, handlePropsUpdate, isInput, handleToggleInput] 90 | } -------------------------------------------------------------------------------- /example/src/MonthPicker/MonthPickerDemo.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { MonthPicker } from 'react-datetime-range-super-picker'; 3 | 4 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; 5 | import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; 6 | 7 | import StyleSelector from "../components/StyleSelector"; 8 | import ThemeSelector from "../components/ThemeSelector"; 9 | 10 | import { useMonthPickerProps } from './monthpicker.hooks'; 11 | 12 | 13 | export default () => { 14 | 15 | /** 16 | * Parent: 17 | * App 18 | * 19 | * Renders: 20 | * StyleSelector 21 | * ThemeSelector 22 | * 23 | * MonthPicker 24 | */ 25 | 26 | const [pickerProps, pickerHtml, handlePropsUpdate] = useMonthPickerProps() 27 | 28 | const [res_month, setMonth] = useState(4) 29 | const [res_year, setYear] = useState(2020) 30 | const [isCopy, setCopy] = useState(false) 31 | 32 | 33 | const handleDateUpdate = ({ month, year }) => { 34 | setMonth(month) 35 | setYear(year) 36 | } 37 | 38 | const MProps = { 39 | ...pickerProps, 40 | time: { month: res_month, year: res_year }, onDateUpdate: handleDateUpdate 41 | } 42 | 43 | const onCopyClick = () => { 44 | window.Clipboard.copy(pickerHtml); 45 | setCopy(true) 46 | 47 | setTimeout(() => { 48 | setCopy(false) 49 | }, 3000); 50 | } 51 | 52 | return ( 53 |
54 |
Month Picker
55 | 56 |
57 | 58 |
59 |
60 | 61 |
62 | 63 |
64 | 65 |
66 | 67 |
68 | {isCopy ? 69 |
70 | content_paste 71 |
72 | : 73 |
74 | content_copy 75 |
76 | } 77 | 78 | {pickerHtml} 79 | 80 |
81 |
82 |
83 | 84 |
85 | 86 |
87 |
88 | 89 |
90 |
91 |
92 | ) 93 | } -------------------------------------------------------------------------------- /example/src/MonthPicker/monthpicker.hooks.js: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from "react" 2 | import { isEmpty } from "lodash"; 3 | 4 | 5 | const generatePickerHtml = ({theme, colors}) => { 6 | 7 | let propStr = `time={{month: res_month, year:res_year}} 8 | onDateUpdate={handleDateUpdate}` 9 | 10 | if(!!theme) { 11 | propStr += `\n\t\t\t\ttheme="${theme}"` 12 | } 13 | 14 | if(!isEmpty(colors)) { 15 | propStr += `\n\t\t\t\tcolors={${JSON.stringify(colors)}}` 16 | } 17 | 18 | return ` 19 | import React, { useState } from 'react'; 20 | import { MonthPicker } from 'react-datetime-range-super-picker'; 21 | 22 | import 'react-datetime-range-super-picker/dist/index.css' 23 | 24 | const MonthPickerComponent = () => { 25 | const [res_month, setMonth] = useState(4) 26 | const [res_year, setYear] = useState(2020) 27 | 28 | const handleDateUpdate = ({ month, year }) => { 29 | setMonth(month) 30 | setYear(year) 31 | } 32 | 33 | return ( 34 | 35 | ) 36 | } 37 | ` 38 | } 39 | 40 | export const useMonthPickerProps = () => { 41 | 42 | const [pickerProps, setPickerProps] = useState({}) 43 | const [pickerHtml, setPickerHtml] = useState(generatePickerHtml({})) 44 | 45 | const handlePropsUpdate = useCallback((newProps) => { 46 | setPickerProps(newProps) 47 | setPickerHtml(generatePickerHtml({...newProps})) 48 | }, []) 49 | 50 | return [pickerProps, pickerHtml, handlePropsUpdate] 51 | } -------------------------------------------------------------------------------- /example/src/TimePicker/PropSelector.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | export default ({pickerProps, handlePropsUpdate, isInput}) => { 4 | 5 | const [format, setFormat] = useState(pickerProps.format || undefined) 6 | const [isDisabled, setIsDisabled] = useState(false) 7 | 8 | const handleSubmit = () => { 9 | handlePropsUpdate({...pickerProps, format, isDisabled}) 10 | } 11 | 12 | const handleCheckbox = (e) => { 13 | if(e) e.preventDefault(); 14 | setIsDisabled(!isDisabled) 15 | } 16 | 17 | 18 | return ( 19 |
20 |
21 |
22 |

Props Selector

23 | 24 |
25 | setFormat(e.target.value)} /> 28 | 29 |
30 | 31 | {isInput && 32 |
33 |
34 | 39 |
40 |
41 | } 42 | 43 |
44 |
45 |
46 | 50 |
51 |
52 | ) 53 | } -------------------------------------------------------------------------------- /example/src/TimePicker/TimePickerDemo.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { TimePicker, TimePickerInput } from 'react-datetime-range-super-picker'; 3 | 4 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; 5 | import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; 6 | 7 | import PropSelector from "./PropSelector"; 8 | import StyleSelector from "../components/StyleSelector"; 9 | import ThemeSelector from "../components/ThemeSelector"; 10 | 11 | import { useTimePickerProps } from './timepicker.hooks'; 12 | 13 | export default () => { 14 | 15 | /** 16 | * Parent: 17 | * App 18 | * 19 | * Renders: 20 | * PropSelector 21 | * StyleSelector 22 | * ThemeSelector 23 | * 24 | * TimePicker 25 | * TimePickerInput 26 | */ 27 | 28 | const [pickerProps, pickerHtml, handlePropsUpdate, 29 | isInput, handleToggleInput] = useTimePickerProps() 30 | 31 | const [hour, setHour] = useState(22) 32 | const [minute, setMin] = useState(30) 33 | // const [timeStr, setTimeStr] = useState('08:30 pm') 34 | const [isCopy, setCopy] = useState(false) 35 | 36 | 37 | const handleTimeUpdate = ({time, formatted}) => { 38 | setHour(time.hour24) 39 | setMin(time.minute) 40 | // setTimeStr(formatted) 41 | } 42 | 43 | const TProps = {...pickerProps, 44 | // time: timeStr, 45 | time: {hour24 : hour, minute }, 46 | onTimeUpdate: handleTimeUpdate, 47 | } 48 | 49 | const handleInputToggle = (e) => { 50 | if(e) e.preventDefault(); 51 | handleToggleInput(!isInput) 52 | } 53 | 54 | const onCopyClick = () => { 55 | window.Clipboard.copy(pickerHtml); 56 | setCopy(true) 57 | 58 | setTimeout(() => { 59 | setCopy(false) 60 | }, 3000); 61 | } 62 | 63 | return ( 64 |
65 |
Time Picker
66 | 67 |
68 | 69 |
70 |
71 |
72 | 77 |
78 | 79 | {isInput ? 80 |
81 | 82 |
83 | : 84 | 85 | } 86 |
87 | 88 | 89 | 90 |
91 | 92 |
93 | 94 |
95 | {isCopy ? 96 |
97 | content_paste 98 |
99 | : 100 |
101 | content_copy 102 |
103 | } 104 | 105 | {pickerHtml} 106 | 107 |
108 |
109 |
110 | 111 |
112 | 113 |
114 |
115 | 116 |
117 | 118 |
119 | 120 |
121 |
122 |
123 | ) 124 | } -------------------------------------------------------------------------------- /example/src/TimePicker/timepicker.hooks.js: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from "react" 2 | import { isEmpty } from "lodash"; 3 | 4 | 5 | const generatePickerHtml = ({format, isInput, 6 | theme, colors, isDisabled}) => { 7 | const componentStr = isInput ? 'TimePickerInput' : 'TimePicker' 8 | 9 | let propStr = `time={{hour24 : hour, minute }} 10 | onTimeUpdate={handleTimeUpdate}` 11 | 12 | if(!!format) { 13 | propStr += `\n\t\t\t\tformat="${format}"` 14 | } 15 | 16 | if(!!theme) { 17 | propStr += `\n\t\t\t\ttheme="${theme}"` 18 | } 19 | 20 | if(!isEmpty(colors)) { 21 | propStr += `\n\t\t\t\tcolors={${JSON.stringify(colors)}}` 22 | } 23 | 24 | if(!!isDisabled && isInput) { 25 | propStr += `\n\t\t\t\t\isDisabled={${isDisabled}}` //eslint-disable-line 26 | } 27 | 28 | return ` 29 | import React, { useState } from 'react'; 30 | import { ${componentStr} } from 'react-datetime-range-super-picker'; 31 | 32 | import 'react-datetime-range-super-picker/dist/index.css' 33 | 34 | const TimePickerComponent = () => { 35 | const [hour, setHour] = useState(22) 36 | const [minute, setMin] = useState(30) 37 | 38 | const handleTimeUpdate = ({time}) => { 39 | setHour(time.hour24) 40 | setMin(time.minute) 41 | } 42 | 43 | return ( 44 | <${componentStr} ${propStr} /> 45 | ) 46 | } 47 | ` 48 | } 49 | 50 | export const useTimePickerProps = () => { 51 | 52 | const [isInput, setInput] = useState(false) 53 | const [pickerProps, setPickerProps] = useState({}) 54 | const [pickerHtml, setPickerHtml] = useState(generatePickerHtml({})) 55 | 56 | const handlePropsUpdate = useCallback((newProps) => { 57 | setPickerProps(newProps) 58 | setPickerHtml(generatePickerHtml({...newProps, isInput})) 59 | }, [isInput]) 60 | 61 | const handleToggleInput = useCallback((isInput) => { 62 | setInput(isInput) 63 | setPickerHtml(generatePickerHtml({...pickerProps, isInput})) 64 | }, [pickerProps]) 65 | 66 | return [pickerProps, pickerHtml, handlePropsUpdate, isInput, handleToggleInput] 67 | } -------------------------------------------------------------------------------- /example/src/components/StyleSelector.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { get } from "lodash"; 3 | 4 | export default class StyleSelector extends React.Component { 5 | 6 | constructor(props) { 7 | super(props); 8 | 9 | this.state = { 10 | colors : { 11 | primary_color: get(props, 'colors.primary_color', ''), 12 | primary_highlight_color: get(props, 'colors.primary_highlight_color', ''), 13 | primary_font_color: get(props, 'colors.primary_font_color', ''), 14 | light_font_color: get(props, 'colors.light_font_color', ''), 15 | secondary_color: get(props, 'colors.secondary_color', ''), 16 | secondary_highlight_color: get(props, 'colors.secondary_highlight_color', ''), 17 | } 18 | } 19 | } 20 | 21 | handleColorUpdate = (e) => { 22 | const colors = {...this.state.colors} 23 | colors[e.target.name] = e.target.value 24 | this.setState({colors}) 25 | } 26 | 27 | handleSubmit = () => { 28 | // remove color if empty 29 | let colors = {...this.state.colors} 30 | for (const color in colors) { 31 | if (colors.hasOwnProperty(color)) { 32 | if(!colors[color]) delete colors[color]; 33 | } 34 | } 35 | 36 | this.props.handlePropsUpdate({ 37 | ...this.props.pickerProps, 38 | colors, 39 | }) 40 | } 41 | 42 | render = () => { 43 | const {primary_color, secondary_color, 44 | primary_font_color, light_font_color, 45 | secondary_highlight_color, primary_highlight_color} = this.state.colors 46 | 47 | return ( 48 |
49 |
50 |
51 |

Style Selector

52 | 53 |
54 | 59 | 60 |
61 | 62 |
63 | 67 | 68 |
69 | 70 |
71 | 75 | 76 |
77 | 78 |
79 | 83 | 84 |
85 | 86 |
87 | 91 | 92 |
93 | 94 |
95 | 99 | 100 |
101 | 102 |
103 |
104 |
105 | 109 |
110 |
111 | ) 112 | } 113 | } -------------------------------------------------------------------------------- /example/src/components/ThemeSelector.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | export default class ThemeSelector extends Component { 4 | 5 | state = { 6 | theme : 'light', 7 | } 8 | 9 | handleThemeUpdate = (theme) => { 10 | this.setState({theme}) 11 | 12 | this.props.handlePropsUpdate({ 13 | ...this.props.pickerProps, 14 | theme 15 | }) 16 | } 17 | 18 | render = () => { 19 | const {theme} = this.state 20 | 21 | return ( 22 |
23 |
24 |
Select Theme
25 |
26 |
27 |
Light
29 |
Dark
31 |
Colorful
33 |
34 |
35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | // import AllInputTest from './test/AllInputTest' 6 | 7 | ReactDOM.render(, document.getElementById('root')) 8 | 9 | // custom clipboard function 10 | window.Clipboard = (function(window, document, navigator) { 11 | var textArea, 12 | copy; 13 | 14 | function isOS() { 15 | return navigator.userAgent.match(/ipad|iphone/i); 16 | } 17 | 18 | function createTextArea(text) { 19 | textArea = document.createElement('textArea'); 20 | textArea.value = text; 21 | document.body.appendChild(textArea); 22 | } 23 | 24 | function selectText() { 25 | var range, 26 | selection; 27 | 28 | if (isOS()) { 29 | range = document.createRange(); 30 | range.selectNodeContents(textArea); 31 | selection = window.getSelection(); 32 | selection.removeAllRanges(); 33 | selection.addRange(range); 34 | textArea.setSelectionRange(0, 999999); 35 | } else { 36 | textArea.select(); 37 | } 38 | } 39 | 40 | function copyToClipboard() { 41 | document.execCommand('copy'); 42 | document.body.removeChild(textArea); 43 | } 44 | 45 | copy = function(text) { 46 | createTextArea(text); 47 | selectText(); 48 | copyToClipboard(); 49 | }; 50 | 51 | return { 52 | copy: copy 53 | }; 54 | })(window, document, navigator); 55 | -------------------------------------------------------------------------------- /example/src/styles/demo.css: -------------------------------------------------------------------------------- 1 | .heading { 2 | padding-left: 1em; 3 | font-size: 5em; 4 | color : black; 5 | font-weight: bold; 6 | border-bottom: 2px solid black; 7 | } 8 | 9 | .heading.font-small { 10 | font-size: 4em; 11 | } 12 | 13 | .picker-wrapper { 14 | text-align: center; 15 | padding: 30px; 16 | } 17 | 18 | .picker-wrapper .switch { margin-bottom: 20px } 19 | 20 | .picker-wrapper label { font-size: 1em } 21 | 22 | .disabled-wrapper label { font-size: 1em } 23 | 24 | 25 | .form-wrapper { 26 | padding: 30px; 27 | } 28 | 29 | .form-wrapper .submit-btn-wrapper { 30 | text-align: center; 31 | } 32 | 33 | .demo-input-class { 34 | border:none; 35 | outline: none; 36 | font-size: 0.9em; 37 | height: 2.5em; 38 | padding: .2em 1.2em .2em .5em; 39 | border-radius: 0.417em; 40 | background-color: #f7f7f7; 41 | color: #6c6b6b; 42 | } 43 | 44 | .light-btn { 45 | background-color: white; 46 | color : rgba(0, 0, 0, 0.57) 47 | } 48 | .light-btn.active, .light-btn:hover { 49 | background-color: white; 50 | -webkit-transform: scale(1.166); 51 | -ms-transform: scale(1.166); 52 | transform: scale(1.166); 53 | } 54 | 55 | .dark-btn { 56 | background-color : #36465D; 57 | color : #8897b9 58 | } 59 | .dark-btn.active, .dark-btn:hover { 60 | background-color : #36465D; 61 | -webkit-transform: scale(1.166); 62 | -ms-transform: scale(1.166); 63 | transform: scale(1.166); 64 | } 65 | 66 | .colorful-btn { 67 | background-color : #8897b9; 68 | color : #ffd180; 69 | } 70 | .colorful-btn.active, .colorful-btn:hover { 71 | background-color : #8897b9; 72 | -webkit-transform: scale(1.166); 73 | -ms-transform: scale(1.166); 74 | transform: scale(1.166); 75 | } 76 | 77 | .theme-action-wrapper { 78 | padding: 30px; 79 | padding-bottom: 0; 80 | } 81 | 82 | .pad-bottom-16 { 83 | padding-bottom: 16px; 84 | } 85 | 86 | .pad-bottom-8 { 87 | padding-bottom: 8px; 88 | } 89 | 90 | .mr-right { 91 | margin-right: 1.8em; 92 | } 93 | 94 | .code-wrapper { 95 | width: 100%; 96 | position: relative; 97 | } 98 | 99 | .copy-block { 100 | position: absolute; 101 | top: 0.6em; 102 | right: 0.6em; 103 | cursor: pointer; 104 | color: #fff; 105 | } 106 | 107 | /* space below input fields in style and props selector */ 108 | .input-field { 109 | padding-bottom: 8px; 110 | } 111 | 112 | .range-picker-input-wrapper { 113 | padding-bottom: 30em; 114 | max-width: 56em; 115 | margin: 0 auto; 116 | } 117 | 118 | .datetime-pad-bot { 119 | padding-bottom: 24em; 120 | max-width: 48em; 121 | margin: 0 auto; 122 | } 123 | 124 | .date-pad-bot { 125 | padding-bottom: 24em; 126 | max-width: 26em; 127 | margin: 0 auto; 128 | } 129 | 130 | .time-pad-bot { 131 | padding-bottom: 20em; 132 | max-width: 20em; 133 | margin: 0 auto; 134 | } -------------------------------------------------------------------------------- /example/src/styles/layout.css: -------------------------------------------------------------------------------- 1 | .layout-wrapper { 2 | display: flex; 3 | height: 100%; 4 | width: 100%; 5 | } 6 | 7 | #layout-menu { 8 | height: 100vh; 9 | flex: 1; 10 | display: flex; 11 | flex-direction: column; 12 | max-width: 19em; 13 | } 14 | 15 | #layout-menu .layout-pill { 16 | cursor: pointer; 17 | display: flex; 18 | flex-direction: column; 19 | justify-content: center; 20 | flex: 1; 21 | background: white; 22 | color: black; 23 | border-right: black solid 3px; 24 | border-bottom: transparent solid 3px; 25 | transition: all .2s linear; 26 | text-align: center; 27 | } 28 | 29 | .layout-pill.active { 30 | background: black !important; 31 | color: white !important; 32 | } 33 | 34 | .layout-pill:hover { 35 | border-bottom: black solid 3px !important; 36 | } 37 | 38 | #layout-content { 39 | flex: 4; 40 | display: block; 41 | height: 100vh; 42 | overflow-y: scroll; 43 | } 44 | 45 | #layout-content .git-wrapper { 46 | float: right; 47 | padding: 6px 10px; 48 | cursor: pointer; 49 | } 50 | 51 | #layout-content .git-wrapper svg, 52 | #layout-content .git-wrapper span { 53 | display: inline-block; 54 | vertical-align: middle; 55 | } 56 | 57 | #layout-content .git-wrapper span { 58 | padding: 0 4px; 59 | color: black; 60 | } -------------------------------------------------------------------------------- /example/src/test/AllInputTest.js: -------------------------------------------------------------------------------- 1 | import React, { Component, useState } from 'react' 2 | 3 | import { DateTimeRangePicker, 4 | TimePickerInput, DatePickerInput, MonthPicker, 5 | DateRangePickerInput, DateTimePickerInput, 6 | DateRangeCalendarPickerInput, DateTimeRangePickerInput 7 | } from 'react-datetime-range-super-picker'; 8 | 9 | import 'react-datetime-range-super-picker/dist/index.css' 10 | 11 | 12 | const TimePickerComponent = () => { 13 | const [hour, setHour] = useState('PM') 14 | const [minute, setMin] = useState() 15 | 16 | const handleTimeUpdate = (timeObj) => { 17 | setHour(timeObj.time.hour24) 18 | setMin(timeObj.time.minute) 19 | // setHour(timeObj.formatted) 20 | } 21 | 22 | return ( 23 | 25 | ) 26 | } 27 | 28 | const DatePickerComponent = () => { 29 | const [curr_date, setDate] = useState() 30 | 31 | const handleDateUpdate = (dateObj) => { 32 | setDate(dateObj.date) 33 | } 34 | 35 | return ( 36 | <> 37 | } 38 | onDateUpdate={handleDateUpdate} /> 39 | 40 | 41 | ) 42 | } 43 | 44 | const MonthPickerComponent = () => { 45 | const [res_month, setMonth] = useState(4) 46 | const [res_year, setYear] = useState(2020) 47 | 48 | const handleDateUpdate = ({ month, year }) => { 49 | setMonth(month) 50 | setYear(year) 51 | } 52 | 53 | return ( 54 | 56 | ) 57 | } 58 | 59 | const DateTimePickerComponent = () => { 60 | const [curr_date, setDate] = useState() 61 | 62 | const handleDateUpdate = (ip) => { 63 | console.log("🚀 ~ file: AllInputTest.js ~ line 63 ~ handleDateUpdate ~ ip", ip) 64 | setDate(ip.formatted) 65 | } 66 | 67 | return ( 68 | console.log('hehre')} 71 | /> 72 | ) 73 | } 74 | 75 | const DateRangePickerComponent = () => { 76 | const [from_date, setFromDate] = useState() 77 | const [to_date, setToDate] = useState() 78 | 79 | const handleFromDateUpdate = ({ date }) => { 80 | setFromDate(date) 81 | } 82 | 83 | const handleToDateUpdate = ({ date }) => { 84 | setToDate(date) 85 | } 86 | 87 | return ( 88 | 91 | ) 92 | } 93 | 94 | const DateRangeCalendarPickerComponent = () => { 95 | const [from_date, setFromDate] = useState() 96 | const [to_date, setToDate] = useState() 97 | 98 | const handleFromDateUpdate = ({ date }) => { 99 | setFromDate(date) 100 | } 101 | 102 | const handleToDateUpdate = ({ date }) => { 103 | setToDate(date) 104 | } 105 | 106 | return ( 107 | 110 | ) 111 | } 112 | 113 | const DateTimeRangePickerComponent = () => { 114 | const [from_date, setFromDate] = useState() 115 | const [to_date, setToDate] = useState() 116 | 117 | const handleFromDateUpdate = ({ date }) => { 118 | setFromDate(date.date) 119 | } 120 | 121 | const handleToDateUpdate = ({ date }) => { 122 | setToDate(date.date) 123 | } 124 | 125 | return ( 126 | 130 | ) 131 | } 132 | 133 | export default class AllInputTest extends Component { 134 | render = () => { 135 | return ( 136 |
137 | 138 |
139 |
140 |
Time picker
141 | 142 |
143 |
144 | 145 |
146 | ) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-datetime-range-super-picker", 3 | "version": "1.14.1", 4 | "description": "React date, time, date range, calender, clock i.e. all in one picker", 5 | "author": "Dishant15", 6 | "license": "MIT", 7 | "repository": "Dishant15/react-datetime-range-super-picker", 8 | "main": "dist/index.js", 9 | "module": "dist/index.modern.js", 10 | "source": "src/index.tsx", 11 | "engines": { 12 | "node": ">=10" 13 | }, 14 | "scripts": { 15 | "build": "microbundle-crl --no-compress --format modern,cjs", 16 | "start": "microbundle-crl watch --no-compress --format modern,cjs", 17 | "prepublish": "run-s build", 18 | "test": "run-s test:unit test:lint test:build", 19 | "test:build": "run-s build", 20 | "test:lint": "eslint .", 21 | "test:unit": "cross-env CI=1 react-scripts test --env=jsdom", 22 | "test:watch": "react-scripts test --env=jsdom", 23 | "predeploy": "cd example && yarn install && yarn run build", 24 | "deploy": "gh-pages -d example/build" 25 | }, 26 | "peerDependencies": { 27 | "date-fns": "^2.14.0", 28 | "lodash": "^4.17.19", 29 | "react": "^16.0.0" 30 | }, 31 | "devDependencies": { 32 | "@types/jest": "^25.1.4", 33 | "@types/lodash": "^4.14.151", 34 | "@types/react": "^16.9.27", 35 | "@typescript-eslint/eslint-plugin": "^2.26.0", 36 | "@typescript-eslint/parser": "^2.26.0", 37 | "babel-eslint": "^10.0.3", 38 | "cross-env": "^7.0.2", 39 | "date-fns": "^2.14.0", 40 | "eslint": "^6.8.0", 41 | "eslint-config-prettier": "^6.7.0", 42 | "eslint-config-standard": "^14.1.0", 43 | "eslint-config-standard-react": "^9.2.0", 44 | "eslint-plugin-import": "^2.18.2", 45 | "eslint-plugin-node": "^11.0.0", 46 | "eslint-plugin-prettier": "^3.1.1", 47 | "eslint-plugin-promise": "^4.2.1", 48 | "eslint-plugin-react": "^7.17.0", 49 | "eslint-plugin-standard": "^4.0.1", 50 | "gh-pages": "^2.2.0", 51 | "lodash": "4.17.19", 52 | "microbundle-crl": "^0.13.10", 53 | "npm-run-all": "^4.1.5", 54 | "prettier": "^2.0.4", 55 | "react": "^16.13.1", 56 | "react-dom": "^16.13.1", 57 | "react-scripts": "^3.4.1" 58 | }, 59 | "files": [ 60 | "dist" 61 | ], 62 | "dependencies": {} 63 | } 64 | -------------------------------------------------------------------------------- /src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/components/ClockFace.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {range} from 'lodash' 3 | 4 | import { ClockFaceProps } from "../interfaces/timepicker.interfaces"; 5 | 6 | import styles from '../styles/timepicker.css' 7 | import { getTickColors, getMeridiemStyles } from '../styles/clockface.colors'; 8 | 9 | 10 | // making inline css due to problem with css modules on production build 11 | const CLOCK_SIZE = 16 // em 12 | const CLOCK_HAND_HEIGHT = 1.8 // em 13 | 14 | export default ({hour, minute, meridiem, onTimeUpdate, colors} : ClockFaceProps) => { 15 | const clock_tick_list: number[] = range(1, 13) 16 | const hand_position = (CLOCK_SIZE / 2) + (CLOCK_HAND_HEIGHT / 2 ) 17 | 18 | const { meridiem_style, meridiem_active_style } = getMeridiemStyles(colors) 19 | 20 | return ( 21 |
22 |
24 | 25 | 26 | 27 | { // draw clock hands 28 | clock_tick_list.map((tick) => { 29 | const curr_minute = tick === 12 ? 0 : (tick * 5) 30 | let rotation = tick === 12 ? 0 : tick * 30 31 | // adjust hands so 12 O'clock is at the top rather than at 3 O'clock 32 | rotation -= 90 33 | 34 | const { hh_style, mm_style} = getTickColors( 35 | colors, rotation, tick === hour, curr_minute === minute) 36 | 37 | return ( 38 |
43 | 44 |
onTimeUpdate({hour, meridiem, minute:curr_minute})}> 46 | {curr_minute} 47 |
48 | 49 |
onTimeUpdate({minute, meridiem, hour:tick})}> 51 | {tick} 52 |
53 | 54 |
55 | ) 56 | })} 57 | 58 |
59 |
onTimeUpdate({hour, minute, meridiem:"AM"})} >AM
62 | 63 |
onTimeUpdate({hour, minute, meridiem:"PM"})} >PM
66 |
67 | ) 68 | } 69 | 70 | const ClockHands = ({hour, minute, colors}:any) => { 71 | const hasMin = !isNaN(Number(minute)) // isNaN will handle 0 boolean true problem 72 | const hasHour = !isNaN(Number(hour)) 73 | const mm_rotation = hasMin ? ((minute / 5) * 30) - 90 : 0 74 | const hh_rotation = hasHour ? (hour === 12 ? -90 : (hour * 30) - 90) : 0 75 | 76 | const hand_position = (CLOCK_SIZE / 2) + (CLOCK_HAND_HEIGHT / 2 ) 77 | 78 | return ( 79 | 80 | {hasMin && 81 |
88 | } 89 | {hasHour && 90 |
97 | } 98 |
99 | ) 100 | } -------------------------------------------------------------------------------- /src/components/DatePicker.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import MonthPicker from "./MonthPicker"; 4 | 5 | import { formatDate, getWeekList, getDayList, 6 | generateDatePickerOutput, createRangeIndex, parseRangeIndex, 7 | _is_number } from "../utils/datepicker.utils"; 8 | import { DatePickerProps, defaultConfigs, DatePickerState } from "../interfaces/datepicker.interfaces"; 9 | import { OutputShape } from '../interfaces/monthpicker.interfaces' 10 | import { getWrapperStyles, getCalenderCellColors } from '../styles/datepicker.color' 11 | 12 | import styles from '../styles/datepicker.css' 13 | 14 | 15 | export default class DatePicker extends React.Component { 16 | 17 | constructor(props:DatePickerProps) { 18 | super(props) 19 | const date_obj = formatDate(props.date, props.format) 20 | this.state = { 21 | ...date_obj, // day, month, year 22 | dateRangeIndex: createRangeIndex(date_obj.day, date_obj.month, date_obj.year), 23 | // hover states 24 | hoverOn: false, 25 | hoverRangeIndex: 0, 26 | } 27 | } 28 | 29 | static defaultProps = { 30 | weekStartsOn : defaultConfigs.weekStartsOn, 31 | format : defaultConfigs.format, 32 | showRangeTrace: false, 33 | otherDateRangeIndex: 0, 34 | } 35 | 36 | static getDerivedStateFromProps(props:DatePickerProps) { 37 | const date_obj = formatDate(props.date, props.format) 38 | return { 39 | ...date_obj, 40 | dateRangeIndex: createRangeIndex(date_obj.day, date_obj.month, date_obj.year) 41 | } 42 | } 43 | 44 | handleMonthUpdate = (updated_date:OutputShape) => { 45 | const {day} = this.state 46 | const {onDateUpdate, format} = this.props 47 | 48 | if(onDateUpdate) onDateUpdate( 49 | generateDatePickerOutput(day || 1, // if user changed month set day to 1 50 | updated_date.month, updated_date.year, format), false) 51 | // is_date_update is false because this will not update To / from state selection 52 | // mainly used for date range picker 53 | } 54 | 55 | handleDateUpdate = (rangeIndex:number) => { 56 | const {onDateUpdate, onComplete, format} = this.props 57 | 58 | const [new_day, new_month, new_year] = parseRangeIndex(rangeIndex) 59 | 60 | if(onDateUpdate) onDateUpdate( 61 | generateDatePickerOutput(new_day, new_month, new_year, format)) 62 | 63 | if(onComplete) onComplete() 64 | } 65 | 66 | handleMouseEnter = (hoverRangeIndex:number) => () => { 67 | this.setState({ 68 | hoverOn: true, hoverRangeIndex, 69 | }) 70 | } 71 | 72 | handleMouseLeave = () => { 73 | this.setState({ 74 | hoverOn: false, hoverRangeIndex: 0, 75 | }) 76 | } 77 | 78 | render = () => { 79 | const {day, month, year, 80 | hoverOn, hoverRangeIndex, dateRangeIndex} = this.state; 81 | const {weekStartsOn, colors, otherDateRangeIndex, 82 | showRangeTrace, traceStatus} = this.props; 83 | 84 | const week_header_list = getWeekList(weekStartsOn); 85 | const day_obj_list = getDayList(day, month, year, weekStartsOn); 86 | const todayRangeIndex = createRangeIndex( 87 | new Date().getDate(), new Date().getMonth(), new Date().getFullYear() 88 | ); 89 | 90 | let minRangeIndex:number = 0, maxRangeIndex:number = 0; 91 | // create min max selected range end points of our current range 92 | if(showRangeTrace) { 93 | // if user selected both date only show selected Range and disable hover highlights 94 | const compareDate = traceStatus==='A' ? dateRangeIndex : 95 | // if hover than show range on hover state, else show on selected state 96 | hoverOn ? hoverRangeIndex : dateRangeIndex; 97 | if(_is_number(compareDate)) { 98 | minRangeIndex = Math.min(compareDate, otherDateRangeIndex) 99 | maxRangeIndex = Math.max(compareDate, otherDateRangeIndex) 100 | } 101 | } 102 | 103 | return( 104 |
105 | 108 | 109 | 110 | 111 | 112 | 113 | {week_header_list.map((week_str, i) => { 114 | return ( 115 | 118 | ) 119 | })} 120 | 121 | 122 | {day_obj_list.map((week, i) => { 123 | return ( 124 | 125 | 126 | {week.map((curr_day, j) => { 127 | const opacity = curr_day.curr_month ? 1 : 0.5 128 | // style current cell according to hover state, for DateRangePicker 129 | let cell_type = '' 130 | if(curr_day.rangeIndex === dateRangeIndex) { 131 | // only when selecting To date dont show curr day as solid 132 | if(traceStatus !== 'T') cell_type = 'solid' 133 | } 134 | else if(curr_day.rangeIndex === otherDateRangeIndex) { 135 | // other selected day will be simple when selecting from date 136 | if(traceStatus==='T' || traceStatus==='A') cell_type = 'solid'; 137 | } 138 | else if( 139 | curr_day.rangeIndex < maxRangeIndex && 140 | curr_day.rangeIndex > minRangeIndex) { 141 | // range between hovered date and selected From date 142 | if(traceStatus==='T') cell_type = 'border'; 143 | // range between From date and To date 144 | else if(traceStatus==='A') cell_type = 'solid'; 145 | } 146 | else if (curr_day.rangeIndex == todayRangeIndex) { 147 | cell_type = 'border-dotted'; 148 | } 149 | 150 | const day_styles = getCalenderCellColors(colors, cell_type) 151 | 152 | return ( 153 | 159 | ) 160 | })} 161 | 162 | 163 | ) 164 | })} 165 | 166 |
116 | {week_str} 117 |
this.handleDateUpdate(curr_day.rangeIndex)} 155 | onMouseEnter={this.handleMouseEnter(curr_day.rangeIndex)} 156 | onMouseLeave={this.handleMouseLeave} 157 | style={{opacity, ...day_styles }} > {curr_day.day} 158 |
167 |
168 | ) 169 | } 170 | } -------------------------------------------------------------------------------- /src/components/DatePickerInput.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, useCallback } from 'react'; 2 | 3 | import DatePicker from './DatePicker' 4 | 5 | import { getInitialDateForInput } from '../utils/datepicker.utils'; 6 | import { DatePickerInputProps, DatePickerOutPut } from "../interfaces/datepicker.interfaces"; 7 | 8 | import { useOutsideAlerter } from '../utils/useOutsideAlerter.hook' 9 | 10 | import styles from '../styles/datepicker.css' 11 | import rootstyles from "../styles/root.css"; 12 | 13 | 14 | const DatePickerInput = (props:DatePickerInputProps) => { 15 | 16 | const {date, format, isDisabled} = props 17 | 18 | const wrapperRef = useRef(null); 19 | 20 | const [show_picker, setShow] = useState(false) 21 | const [show_date, setDate] = useState( 22 | getInitialDateForInput(date, format) 23 | ) 24 | 25 | // update state if direct prop update 26 | useEffect(() => { 27 | setDate(getInitialDateForInput(date, format)) 28 | }, [date, format]) 29 | 30 | const handleDateUpdate = (date_obj:DatePickerOutPut) => { 31 | props.onDateUpdate(date_obj) 32 | setDate(date_obj.formatted) 33 | } 34 | 35 | const handleShow = useCallback(() => { 36 | setShow(true) 37 | },[]) 38 | 39 | const handleComplete = useCallback(() => { 40 | setShow(false) 41 | if(props.onComplete) props.onComplete() 42 | }, []) 43 | 44 | useOutsideAlerter(wrapperRef, setShow); 45 | 46 | const inputComponentProps = { 47 | value: show_date || '', 48 | readOnly: true, 49 | disabled: props.isDisabled, 50 | onFocus: handleShow 51 | } 52 | 53 | const inputComponent = React.isValidElement(props.inputComponent) ? 54 | React.cloneElement(props.inputComponent, inputComponentProps) 55 | : 56 | 59 | 60 | 61 | return ( 62 |
63 | 64 | {inputComponent} 65 | 66 | {(show_picker && !isDisabled) && 67 |
69 |
70 | 73 |
74 |
75 | } 76 |
77 | ) 78 | } 79 | 80 | DatePickerInput.defaultProps = { 81 | isDisabled: false 82 | } 83 | 84 | export default DatePickerInput -------------------------------------------------------------------------------- /src/components/DateRangeCalendarPicker.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { startOfWeek, endOfWeek, subWeeks, 3 | startOfMonth, endOfMonth, subMonths, 4 | } from "date-fns"; 5 | 6 | import DatePicker from './DatePicker' 7 | import { generateDatePickerOutput } from '../utils/datepicker.utils'; 8 | 9 | import { DateRangePickerProps, DateRangeCalendarPickerStates } from '../interfaces/rangepicker.interfaces' 10 | import {defaultConfigs as dateDefaultConfigs, DatePickerOutPut } from "../interfaces/datepicker.interfaces" 11 | import { getActivePillColors } from '../styles/rangepicker.colors' 12 | 13 | import styles from "../styles/rangepicker.css"; 14 | 15 | 16 | export default class DateRangeCalendarPicker extends React.Component { 17 | 18 | state = { 19 | // from date is selected as default 20 | is_to_date : false, 21 | // which advance pill is selected 22 | advance_pill : null, 23 | } 24 | 25 | static defaultProps = { 26 | weekStartsOn : dateDefaultConfigs.weekStartsOn, 27 | format : dateDefaultConfigs.format, 28 | closeButtonText: 'Close' 29 | } 30 | 31 | handleToDateUpdate = (date_time:DatePickerOutPut) => { 32 | const {onToDateUpdate} = this.props 33 | 34 | this.setState({advance_pill : null}) 35 | onToDateUpdate(date_time) 36 | } 37 | 38 | handleFromDateUpdate = (date_time:DatePickerOutPut) => { 39 | const {onFromDateUpdate} = this.props 40 | 41 | this.setState({advance_pill : null}) 42 | onFromDateUpdate(date_time) 43 | } 44 | 45 | render = () => { 46 | const {advance_pill} = this.state 47 | const {format, weekStartsOn, 48 | from_date, to_date, 49 | colors 50 | } = this.props 51 | 52 | const common_props = {format, weekStartsOn, otherDateRangeIndex: 0} 53 | 54 | return ( 55 |
56 |
57 | 58 |
59 |
60 |
63 | This Month 64 |
65 |
68 | Last Month 69 |
70 | 71 |
74 | This Week 75 |
76 |
79 | Last Week 80 |
81 |
82 | 83 | {/* from date first */} 84 |
85 | 89 |
90 | 91 |
93 |
95 |
To
96 |
97 |
98 | 99 | {/* To date component */} 100 |
101 | 105 |
106 | 107 |
108 |
109 |
110 | ) 111 | } 112 | 113 | // Advanced picker handler logic 114 | 115 | handleLastMonth = () => { 116 | const {format, 117 | onFromDateUpdate, onToDateUpdate} = this.props 118 | const lastMonth = subMonths(new Date(), 1) 119 | 120 | let from_ts = startOfMonth(lastMonth) 121 | let to_ts = endOfMonth(lastMonth) 122 | this.setState({advance_pill: 'lm'}) 123 | // call related handlers 124 | onFromDateUpdate(generateDatePickerOutput( 125 | from_ts.getDate(), from_ts.getMonth(), from_ts.getFullYear(), format 126 | )) 127 | onToDateUpdate(generateDatePickerOutput( 128 | to_ts.getDate(), to_ts.getMonth(), to_ts.getFullYear(), format 129 | )) 130 | } 131 | 132 | handleThisMonth = () => { 133 | const {format, 134 | onFromDateUpdate, onToDateUpdate} = this.props 135 | const now = new Date() 136 | 137 | const from_ts = startOfMonth(now) 138 | const to_ts = endOfMonth(now) 139 | this.setState({advance_pill: 'tm'}) 140 | // call related handlers 141 | onFromDateUpdate(generateDatePickerOutput( 142 | from_ts.getDate(), from_ts.getMonth(), from_ts.getFullYear(), format 143 | )) 144 | onToDateUpdate(generateDatePickerOutput( 145 | to_ts.getDate(), to_ts.getMonth(), to_ts.getFullYear(), format 146 | )) 147 | } 148 | 149 | handleThisWeek = () => { 150 | const {weekStartsOn, format, 151 | onFromDateUpdate, onToDateUpdate} = this.props 152 | const now = new Date() 153 | // @ts-ignore 154 | const from_ts = startOfWeek(now, {weekStartsOn}) 155 | // @ts-ignore 156 | const to_ts = endOfWeek(now, {weekStartsOn}) 157 | this.setState({advance_pill: 'tw'}) 158 | // call related handlers 159 | onFromDateUpdate(generateDatePickerOutput( 160 | from_ts.getDate(), from_ts.getMonth(), from_ts.getFullYear(), format 161 | )) 162 | onToDateUpdate(generateDatePickerOutput( 163 | to_ts.getDate(), to_ts.getMonth(), to_ts.getFullYear(), format 164 | )) 165 | } 166 | 167 | handleLastWeek = () => { 168 | const {weekStartsOn, format, 169 | onFromDateUpdate, onToDateUpdate} = this.props 170 | const now = new Date() 171 | // @ts-ignore 172 | let from_ts = startOfWeek(now, {weekStartsOn}) 173 | from_ts = subWeeks(from_ts, 1) 174 | // @ts-ignore 175 | let to_ts = endOfWeek(now, {weekStartsOn}) 176 | to_ts = subWeeks(to_ts, 1) 177 | this.setState({advance_pill: 'lw'}) 178 | // call related handlers 179 | onFromDateUpdate(generateDatePickerOutput( 180 | from_ts.getDate(), from_ts.getMonth(), from_ts.getFullYear(), format 181 | )) 182 | onToDateUpdate(generateDatePickerOutput( 183 | to_ts.getDate(), to_ts.getMonth(), to_ts.getFullYear(), format 184 | )) 185 | } 186 | } -------------------------------------------------------------------------------- /src/components/DateRangeCalendarPickerInput.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useCallback } from 'react'; 2 | 3 | import DateRangePicker from "./DateRangeCalendarPicker"; 4 | import { DateRangePickerInputProps } from '../interfaces/rangepicker.interfaces'; 5 | import {defaultConfigs as dateDefaultConfigs } from "../interfaces/datepicker.interfaces" 6 | import { getInitialDateForInput } from '../utils/datetimepicker.utils'; 7 | 8 | import { useOutsideAlerter } from '../utils/useOutsideAlerter.hook' 9 | 10 | import styles from "../styles/rangepicker.css"; 11 | import rootstyles from "../styles/root.css"; 12 | 13 | 14 | const DateRangeCalendarPickerInput = (props: DateRangePickerInputProps) => { 15 | const wrapperRef = useRef(null); 16 | 17 | const [show_picker, setShow] = useState(false) 18 | 19 | useOutsideAlerter(wrapperRef, setShow); 20 | 21 | const handleShow = useCallback(() => { 22 | setShow(true) 23 | },[]) 24 | 25 | const from_date_str = getInitialDateForInput(props.from_date, props.format || dateDefaultConfigs.format) 26 | const to_date_str = getInitialDateForInput(props.to_date, props.format || dateDefaultConfigs.format) 27 | const show_date = `${from_date_str || '-- / -- / -- '} To ${to_date_str || ' -- / -- / --'}` 28 | const {colors} = props 29 | 30 | const inputComponentProps = { 31 | value: show_date, 32 | readOnly: true, 33 | disabled: props.isDisabled, 34 | onFocus: handleShow 35 | } 36 | 37 | const inputComponent = React.isValidElement(props.inputComponent) ? 38 | React.cloneElement(props.inputComponent, inputComponentProps) 39 | : 40 | 41 | 42 | 43 | return ( 44 |
45 | 46 | {inputComponent} 47 | 48 | {(show_picker && !props.isDisabled) && 49 |
51 |
52 | setShow(false)} 59 | colors={colors} /> 60 |
61 |
62 | } 63 |
64 | ) 65 | } 66 | 67 | DateRangeCalendarPickerInput.defaultProps = { 68 | closeButtonText: 'Done', 69 | isDisabled: false 70 | } 71 | 72 | 73 | export default DateRangeCalendarPickerInput -------------------------------------------------------------------------------- /src/components/DateRangePicker.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { startOfWeek, endOfWeek, subWeeks, 3 | startOfMonth, endOfMonth, subMonths, 4 | } from "date-fns"; 5 | 6 | import DatePicker from './DatePicker' 7 | import { getInputDate } from '../utils/datetimepicker.utils'; 8 | import { createRangeIndex, generateDatePickerOutput } from '../utils/datepicker.utils'; 9 | 10 | import { DateRangePickerProps, DateRangePickerStates } from '../interfaces/rangepicker.interfaces' 11 | import { defaultConfigs } from '../interfaces/datetimepicker.interfaces'; 12 | import {defaultConfigs as dateDefaultConfigs, DatePickerOutPut } from "../interfaces/datepicker.interfaces" 13 | import { getActivePillColors } from '../styles/rangepicker.colors' 14 | 15 | import styles from "../styles/rangepicker.css"; 16 | 17 | 18 | export default class DateRangePicker extends React.Component { 19 | 20 | constructor(props:DateRangePickerProps) { 21 | super(props); 22 | 23 | const date_obj = getInputDate(props.to_date) 24 | let otherDateRangeIndex = createRangeIndex(date_obj.day, date_obj.month, date_obj.year) 25 | 26 | this.state = { 27 | // from date is selected as default 28 | is_to_date : false, 29 | // 'F' , user is selecting from date, 30 | // 'T' , user is selecting To date, 'A' : all dates selected 31 | traceStatus: 'F', 32 | // which advance pill is selected 33 | advance_pill : null, 34 | otherDateRangeIndex, 35 | } 36 | } 37 | 38 | static defaultProps = { 39 | from_date : defaultConfigs.date, 40 | to_date : defaultConfigs.date, 41 | weekStartsOn : dateDefaultConfigs.weekStartsOn, 42 | format : dateDefaultConfigs.format, 43 | closeButtonText: 'Close' 44 | } 45 | 46 | handleToDateUpdate = (date_time:DatePickerOutPut, is_date_update=true) => { 47 | const {onToDateUpdate} = this.props 48 | 49 | if(is_date_update) { 50 | const otherDateRangeIndex = createRangeIndex(date_time.day, date_time.month, date_time.year) 51 | this.setState({advance_pill : null, is_to_date: false, otherDateRangeIndex, 52 | traceStatus:'A'}) 53 | } 54 | onToDateUpdate(date_time) 55 | } 56 | 57 | handleFromDateUpdate = (date_time:DatePickerOutPut, is_date_update=true) => { 58 | const {onFromDateUpdate} = this.props 59 | 60 | if(is_date_update) { 61 | const otherDateRangeIndex = createRangeIndex(date_time.day, date_time.month, date_time.year) 62 | this.setState({advance_pill : null, is_to_date: true, otherDateRangeIndex, 63 | traceStatus:'T'}) 64 | } 65 | onFromDateUpdate(date_time) 66 | } 67 | 68 | render = () => { 69 | const {advance_pill, is_to_date, otherDateRangeIndex, 70 | traceStatus} = this.state 71 | const {format, weekStartsOn, 72 | from_date, to_date, 73 | colors 74 | } = this.props 75 | 76 | const currDate = is_to_date ? to_date : from_date 77 | const handler = is_to_date ? this.handleToDateUpdate : this.handleFromDateUpdate 78 | 79 | const datePickerProps = { colors, format, weekStartsOn, otherDateRangeIndex, 80 | onDateUpdate: handler, date: currDate, 81 | showRangeTrace:true, traceStatus } 82 | 83 | return ( 84 |
85 |
86 | 87 |
88 |
89 |
92 | This Month 93 |
94 |
97 | Last Month 98 |
99 | 100 |
103 | This Week 104 |
105 |
108 | Last Week 109 |
110 |
111 | 112 | {/* from date first */} 113 |
114 | 115 |
116 | 117 |
118 |
119 |
120 | ) 121 | } 122 | 123 | // Advanced picker handler logic 124 | 125 | handleLastMonth = () => { 126 | const {format, 127 | onFromDateUpdate, onToDateUpdate} = this.props 128 | const lastMonth = subMonths(new Date(), 1) 129 | 130 | let from_ts = startOfMonth(lastMonth) 131 | let to_ts = endOfMonth(lastMonth) 132 | 133 | // set range again 134 | const date_obj = getInputDate(from_ts) 135 | const otherDateRangeIndex = createRangeIndex(date_obj.day, date_obj.month, date_obj.year) 136 | this.setState({advance_pill: 'lm', is_to_date: true, otherDateRangeIndex, traceStatus: 'A'}) 137 | // call related handlers 138 | onFromDateUpdate(generateDatePickerOutput( 139 | date_obj.day, date_obj.month, date_obj.year, format)) 140 | onToDateUpdate(generateDatePickerOutput( 141 | to_ts.getDate(), to_ts.getMonth(), to_ts.getFullYear(), format)) 142 | } 143 | 144 | handleThisMonth = () => { 145 | const {format, 146 | onFromDateUpdate, onToDateUpdate} = this.props 147 | const now = new Date() 148 | 149 | const from_ts = startOfMonth(now) 150 | const to_ts = endOfMonth(now) 151 | // set range again 152 | const date_obj = getInputDate(from_ts) 153 | const otherDateRangeIndex = createRangeIndex(date_obj.day, date_obj.month, date_obj.year) 154 | this.setState({advance_pill: 'tm', is_to_date: true, otherDateRangeIndex, traceStatus: 'A'}) 155 | // call related handlers 156 | onFromDateUpdate(generateDatePickerOutput( 157 | date_obj.day, date_obj.month, date_obj.year, format)) 158 | onToDateUpdate(generateDatePickerOutput( 159 | to_ts.getDate(), to_ts.getMonth(), to_ts.getFullYear(), format)) 160 | } 161 | 162 | handleThisWeek = () => { 163 | const {weekStartsOn, format, 164 | onFromDateUpdate, onToDateUpdate} = this.props 165 | const now = new Date() 166 | // @ts-ignore 167 | const from_ts = startOfWeek(now, {weekStartsOn}) 168 | // @ts-ignore 169 | const to_ts = endOfWeek(now, {weekStartsOn}) 170 | // set range again 171 | const date_obj = getInputDate(from_ts) 172 | const otherDateRangeIndex = createRangeIndex(date_obj.day, date_obj.month, date_obj.year) 173 | this.setState({advance_pill: 'tw', is_to_date: true, otherDateRangeIndex, traceStatus: 'A'}) 174 | // call related handlers 175 | onFromDateUpdate(generateDatePickerOutput( 176 | date_obj.day, date_obj.month, date_obj.year, format)) 177 | onToDateUpdate(generateDatePickerOutput( 178 | to_ts.getDate(), to_ts.getMonth(), to_ts.getFullYear(), format)) 179 | } 180 | 181 | handleLastWeek = () => { 182 | const {weekStartsOn, format, 183 | onFromDateUpdate, onToDateUpdate} = this.props 184 | const now = new Date() 185 | // @ts-ignore 186 | let from_ts = startOfWeek(now, {weekStartsOn}) 187 | from_ts = subWeeks(from_ts, 1) 188 | // @ts-ignore 189 | let to_ts = endOfWeek(now, {weekStartsOn}) 190 | to_ts = subWeeks(to_ts, 1) 191 | // set range again 192 | const date_obj = getInputDate(from_ts) 193 | const otherDateRangeIndex = createRangeIndex(date_obj.day, date_obj.month, date_obj.year) 194 | this.setState({advance_pill: 'lw', is_to_date: true, otherDateRangeIndex, traceStatus: 'A'}) 195 | // call related handlers 196 | onFromDateUpdate(generateDatePickerOutput( 197 | date_obj.day, date_obj.month, date_obj.year, format)) 198 | onToDateUpdate(generateDatePickerOutput( 199 | to_ts.getDate(), to_ts.getMonth(), to_ts.getFullYear(), format)) 200 | } 201 | } -------------------------------------------------------------------------------- /src/components/DateRangePickerInput.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useCallback } from 'react'; 2 | 3 | import DateRangePicker from './DateRangePicker' 4 | 5 | import { getInitialDateForInput } from '../utils/datepicker.utils'; 6 | import { DateRangePickerInputProps } from '../interfaces/rangepicker.interfaces' 7 | 8 | import { useOutsideAlerter } from '../utils/useOutsideAlerter.hook' 9 | 10 | import styles from '../styles/datepicker.css' 11 | import rootstyles from "../styles/root.css"; 12 | 13 | 14 | const DateRangePickerInput = (props:DateRangePickerInputProps) => { 15 | 16 | const wrapperRef = useRef(null); 17 | 18 | const [show_picker, setShow] = useState(false) 19 | 20 | useOutsideAlerter(wrapperRef, setShow); 21 | 22 | const handleShow = useCallback(() => { 23 | setShow(true) 24 | },[]) 25 | 26 | const handleClose = useCallback(() => { 27 | setShow(false) 28 | if(props.onDone) props.onDone() 29 | },[]) 30 | 31 | const from_date_str = getInitialDateForInput(props.from_date, props.format) 32 | const to_date_str = getInitialDateForInput(props.to_date, props.format) 33 | const show_date = `${from_date_str || '-- / -- / -- '} To ${to_date_str || ' -- / -- / --'}` 34 | const {colors} = props 35 | 36 | const inputComponentProps = { 37 | value: show_date, 38 | readOnly: true, 39 | disabled: props.isDisabled, 40 | onFocus: handleShow 41 | } 42 | 43 | const inputComponent = React.isValidElement(props.inputComponent) ? 44 | React.cloneElement(props.inputComponent, inputComponentProps) 45 | : 46 | 47 | 48 | 49 | 50 | return ( 51 |
52 | 53 | {inputComponent} 54 | 55 | {(show_picker && !props.isDisabled) && 56 |
58 |
59 | 66 |
67 |
68 | } 69 |
70 | ) 71 | } 72 | 73 | DateRangePickerInput.defaultProps = { 74 | closeButtonText: 'Done', 75 | isDisabled: false, 76 | showRangeTrace: true 77 | } 78 | 79 | export default DateRangePickerInput -------------------------------------------------------------------------------- /src/components/DateTimePicker.tsx: -------------------------------------------------------------------------------- 1 | // import React, { useState, useEffect } from 'react' 2 | import React from 'react' 3 | 4 | import DatePicker from './DatePicker' 5 | import TimePicker from './TimePicker' 6 | 7 | import { 8 | DateTimePickerProps, 9 | MainDateTimeObject, 10 | defaultConfigs, 11 | } from "../interfaces/datetimepicker.interfaces"; 12 | import {defaultConfigs as timeDefaultConfigs, OutputTime } from "../interfaces/timepicker.interfaces" 13 | import {defaultConfigs as dateDefaultConfigs, DatePickerOutPut } from "../interfaces/datepicker.interfaces" 14 | 15 | import { getInputDate, generateOutPut } from '../utils/datetimepicker.utils' 16 | import { _is_number } from '../utils/datepicker.utils'; 17 | 18 | import styles from '../styles/date_time_picker.css' 19 | 20 | 21 | export class UnwrappedDateTimePicker extends React.Component { 22 | 23 | constructor(props:DateTimePickerProps) { 24 | super(props) 25 | 26 | this.state = {...getInputDate(props.date, props.format)} 27 | } 28 | 29 | static defaultProps = { 30 | format : defaultConfigs.format, 31 | timeFormat: timeDefaultConfigs.format, 32 | dateFormat: dateDefaultConfigs.format, 33 | weekStartsOn : dateDefaultConfigs.weekStartsOn, 34 | } 35 | 36 | static getDerivedStateFromProps(props:DateTimePickerProps) { 37 | return getInputDate(props.date, props.format) 38 | } 39 | 40 | handleDateUpdate = (date:DatePickerOutPut) => { 41 | const {onDateUpdate, onDateTimeUpdate, format} = this.props 42 | let curr_date = {...this.state} 43 | 44 | // if time is empty; user clicked date than update default time 45 | if(!_is_number(curr_date.hour)) curr_date.hour = 0 46 | if(!_is_number(curr_date.minute)) curr_date.minute = 0 47 | 48 | if(onDateUpdate) onDateUpdate(date) 49 | 50 | if(onDateTimeUpdate) { 51 | onDateTimeUpdate(generateOutPut(curr_date, format, date)) 52 | } 53 | } 54 | 55 | handleTimeUpdate = (time:OutputTime) => { 56 | const {onTimeUpdate, onDateTimeUpdate, format} = this.props 57 | let curr_date = {...this.state} 58 | // set defaults if empty to generate date , formatted outputs 59 | if(!_is_number(curr_date.day)) curr_date.day = 1 60 | if(!_is_number(curr_date.hour)) curr_date.hour = 0 61 | if(!_is_number(curr_date.minute)) curr_date.minute = 0 62 | 63 | if(onTimeUpdate) onTimeUpdate(time) 64 | 65 | if(onDateTimeUpdate) { 66 | onDateTimeUpdate(generateOutPut(curr_date, format, undefined, time)) 67 | } 68 | } 69 | 70 | render = () => { 71 | const curr_date = this.state 72 | const {dateFormat, weekStartsOn, timeFormat, colors } = this.props 73 | 74 | return ( 75 | 76 |
78 | 89 |
90 | 91 |
92 | 100 |
101 |
102 | ) 103 | } 104 | } 105 | 106 | /** 107 | * Wrapper component required for good design of RangePicker 108 | * Unwrapped component only has 2 table cells 109 | * This component wraps table cells into a table 110 | */ 111 | const DateTimePicker = (props:DateTimePickerProps) => ( 112 |
114 | 115 |
116 | ) 117 | 118 | export default DateTimePicker -------------------------------------------------------------------------------- /src/components/DateTimePickerInput.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, useCallback } from 'react'; 2 | 3 | import DateTimePicker from './DateTimePicker' 4 | 5 | import { getInitialDateForInput } from '../utils/datetimepicker.utils'; 6 | import { DateTimePickerInputProps, DateTimePickerOutPut } from "../interfaces/datetimepicker.interfaces"; 7 | 8 | import { useOutsideAlerter } from '../utils/useOutsideAlerter.hook' 9 | 10 | import styles from '../styles/date_time_picker.css' 11 | import rootstyles from "../styles/root.css"; 12 | 13 | 14 | const DateTimePickerInput = (props:DateTimePickerInputProps) => { 15 | 16 | const { colors, closeButtonText } = props 17 | 18 | const wrapperRef = useRef(null); 19 | const [show_picker, setShow] = useState(false) 20 | const [show_date, setDate] = useState( 21 | getInitialDateForInput(props.date, props.format) 22 | ) 23 | 24 | // update state if direct prop update 25 | useEffect(() => { 26 | setDate( 27 | getInitialDateForInput(props.date, props.format) 28 | ) 29 | }, [props.date, props.format]) 30 | 31 | const handleDateUpdate = (date_obj:DateTimePickerOutPut) => { 32 | props.onDateTimeUpdate(date_obj) 33 | setDate(date_obj.formatted) 34 | } 35 | 36 | const handleShow = useCallback(() => { 37 | setShow(true) 38 | },[]) 39 | 40 | const handleComplete = useCallback(() => { 41 | setShow(false) 42 | if(props.onDone) props.onDone() 43 | }, []) 44 | 45 | useOutsideAlerter(wrapperRef, setShow); 46 | 47 | const inputComponentProps = { 48 | value: show_date, 49 | placeholder: 'Click to select date time', 50 | readOnly: true, 51 | disabled: props.isDisabled, 52 | onFocus: handleShow 53 | } 54 | 55 | const inputComponent = React.isValidElement(props.inputComponent) ? 56 | React.cloneElement(props.inputComponent, inputComponentProps) 57 | : 58 | 59 | 60 | 61 | return ( 62 |
63 | 64 | {inputComponent} 65 | 66 | {(show_picker && !props.isDisabled) && 67 |
69 | 70 |
71 |
72 |
75 | {closeButtonText} 76 |
77 |
78 | 79 | 86 |
87 | 88 |
89 | } 90 |
91 | ) 92 | } 93 | 94 | DateTimePickerInput.defaultProps = { 95 | closeButtonText: 'Done', 96 | isDisabled: false 97 | } 98 | 99 | export default DateTimePickerInput -------------------------------------------------------------------------------- /src/components/MonthPicker.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback, useEffect } from 'react' 2 | import { range, isNaN } from 'lodash' 3 | 4 | import { formatMonth, getMonthAndYear } from "../utils/monthpicker.utils"; 5 | import { MonthPickerProps, OutputShape } from "../interfaces/monthpicker.interfaces"; 6 | import { getSetButtonStyles, getMonthPillColors } from "../styles/monthpicker.colors" 7 | 8 | import styles from '../styles/monthpicker.css' 9 | 10 | 11 | 12 | export default ({time=new Date(), colors, onDateUpdate}:MonthPickerProps) => { 13 | 14 | const {month, year} = getMonthAndYear(time) 15 | 16 | const [edit, setEdit] = useState(false) 17 | const [res_year, setYear] = useState(year) 18 | 19 | // crousel scroll in em 20 | const pos_change_delta = 13.8 21 | // calculate and set pos according to current selected month 22 | const [m_pos, setMPos] = useState(-Math.floor(month/3) * pos_change_delta) 23 | 24 | useEffect(() => { 25 | setMPos(-Math.floor(month/3) * pos_change_delta) 26 | }, [month]) 27 | 28 | const handleTimeChange = useCallback((new_time : OutputShape) => { 29 | setYear(new_time.year) 30 | setEdit(false) 31 | if(onDateUpdate) onDateUpdate(new_time); 32 | }, []) 33 | 34 | // change direction : 1 : prev , 0 : next 35 | const handleMonthPosChange = useCallback((change_dir:number) => { 36 | if(change_dir) { 37 | // prev month is dec of last year 38 | if(month === 0) handleTimeChange({month: 11, year: year - 1}) 39 | else if(month > 0) handleTimeChange({month : month - 1, year}) 40 | } else { 41 | // next month is jan of next year 42 | if(month === 11) handleTimeChange({month: 0, year: year + 1}) 43 | else if(month < 11) handleTimeChange({month : month + 1, year}) 44 | } 45 | }, [month, year]) 46 | 47 | const handleYearChange = useCallback((e) => { 48 | const new_year = Number(e.target.value) 49 | // validate year is a number 50 | if(!isNaN(new_year) && new_year < 9999) setYear(new_year) 51 | }, []) 52 | 53 | const month_list = range(0, 12) 54 | 55 | return ( 56 |
58 | {edit ? 59 |
60 | 63 | 64 |
handleTimeChange({month , year : res_year})}> 67 | Set 68 |
69 |
70 | : 71 |
setEdit(true)}> 74 | {year} {formatMonth(month, 'MMMM')} 75 |
76 | } 77 | 78 |
79 |
handleMonthPosChange(1)} 80 | style={{ color: colors.light_font_color }} 81 | className={styles.crousel_btns}> <
82 | 83 |
84 |
85 | 86 | {month_list.map((curr_month) => { 87 | 88 | const m_styles = getMonthPillColors(colors, (curr_month === month)) 89 | 90 | return ( 91 |
handleTimeChange({year:year, month:curr_month})} > 93 | {formatMonth(curr_month, "MMM")} 94 |
95 | ) 96 | })} 97 | 98 |
99 |
100 | 101 |
handleMonthPosChange(0)} 102 | className={styles.crousel_btns}> >
103 |
104 |
105 | ) 106 | } -------------------------------------------------------------------------------- /src/components/RangePicker.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { startOfWeek, endOfWeek, subWeeks, 3 | startOfMonth, endOfMonth, subMonths, 4 | startOfToday, endOfToday, 5 | startOfYesterday, endOfYesterday 6 | } from "date-fns"; 7 | 8 | import { UnwrappedDateTimePicker } from "./DateTimePicker"; 9 | import { getInitialDateForInput, 10 | getInputDate, generateOutPut } from '../utils/datetimepicker.utils'; 11 | 12 | import { RangePickerProps, RangePickerStates } from '../interfaces/rangepicker.interfaces' 13 | import { DateTimePickerOutPut, defaultConfigs } from '../interfaces/datetimepicker.interfaces'; 14 | import { getHeaderFieldColors, getActivePillColors } from '../styles/rangepicker.colors' 15 | 16 | import styles from "../styles/rangepicker.css"; 17 | 18 | 19 | export default class RangePicker extends React.Component { 20 | 21 | constructor(props:RangePickerProps) { 22 | super(props); 23 | 24 | this.state = { 25 | // from date is selected as default 26 | is_to_date : false, 27 | // which advance pill is selected 28 | advance_pill : null, 29 | } 30 | } 31 | 32 | static defaultProps = { 33 | weekStartsOn : defaultConfigs.weekStartsOn, 34 | format : defaultConfigs.format, 35 | closeButtonText: 'Close', 36 | } 37 | 38 | handleDateTimeUpdate = (date_time:DateTimePickerOutPut) => { 39 | const {is_to_date} = this.state 40 | const {onFromDateTimeUpdate, onToDateTimeUpdate} = this.props 41 | 42 | this.setState({advance_pill : null}) 43 | if(is_to_date) { 44 | onToDateTimeUpdate(date_time) 45 | } else { 46 | onFromDateTimeUpdate(date_time) 47 | } 48 | } 49 | 50 | handleToDateSelect = () => { 51 | this.setState({is_to_date: true}) 52 | } 53 | 54 | handleFromDateSelect = () => { 55 | this.setState({is_to_date: false}) 56 | } 57 | 58 | render = () => { 59 | const {is_to_date, advance_pill } = this.state 60 | const {format, timeFormat, dateFormat, weekStartsOn, 61 | from_date, onFromTimeUpdate, onFromDateUpdate, 62 | to_date, onToTimeUpdate, onToDateUpdate, 63 | onDone, colors, closeButtonText 64 | } = this.props 65 | 66 | const commonProps = {format, timeFormat, dateFormat, weekStartsOn } 67 | 68 | const from_date_str = getInitialDateForInput(from_date, format) || 'Click and select FROM date-time' 69 | const to_date_str = getInitialDateForInput(to_date, format) || 'Click and select TO date-time' 70 | 71 | return ( 72 |
73 |
74 | 75 |
76 |
78 | 79 |
From
80 |
84 | {from_date_str} 85 |
86 | 87 |
88 | 89 |
92 |
To
93 |
97 | {to_date_str} 98 |
99 |
100 | 101 | {Boolean(onDone) && 102 |
105 | {closeButtonText} 106 |
107 | } 108 | 109 |
110 | 111 |
112 |
113 |
116 | This Month 117 |
118 |
121 | Last Month 122 |
123 | 124 |
127 | This Week 128 |
129 |
132 | Last Week 133 |
134 | 135 |
138 | Today 139 |
140 |
143 | Yesterday 144 |
145 |
146 | 147 | {is_to_date ? 148 | // To date component 149 | 152 | : 153 | // from date first 154 | 157 | } 158 |
159 |
160 |
161 | ) 162 | } 163 | 164 | // Advanced picker handler logic 165 | 166 | handleToday = () => { 167 | const {format, 168 | onFromDateTimeUpdate, onToDateTimeUpdate} = this.props 169 | 170 | const from_ts = startOfToday() 171 | const to_ts = endOfToday() 172 | 173 | // call related handlers 174 | const fromDateObj = getInputDate(from_ts) 175 | onFromDateTimeUpdate(generateOutPut(fromDateObj, format)) 176 | const toDateObj = getInputDate(to_ts) 177 | onToDateTimeUpdate(generateOutPut(toDateObj, format)) 178 | 179 | this.setState({advance_pill: 't'}) 180 | } 181 | 182 | handleYesterday = () => { 183 | const {format, 184 | onFromDateTimeUpdate, onToDateTimeUpdate} = this.props 185 | 186 | const from_ts = startOfYesterday() 187 | const to_ts = endOfYesterday() 188 | // call related handlers 189 | const fromDateObj = getInputDate(from_ts) 190 | onFromDateTimeUpdate(generateOutPut(fromDateObj, format)) 191 | const toDateObj = getInputDate(to_ts) 192 | onToDateTimeUpdate(generateOutPut(toDateObj, format)) 193 | 194 | this.setState({advance_pill: 'y'}) 195 | } 196 | 197 | handleLastMonth = () => { 198 | const {format, 199 | onFromDateTimeUpdate, onToDateTimeUpdate} = this.props 200 | const lastMonth = subMonths(new Date(), 1) 201 | 202 | let from_ts = startOfMonth(lastMonth) 203 | let to_ts = endOfMonth(lastMonth) 204 | 205 | // call related handlers 206 | const fromDateObj = getInputDate(from_ts) 207 | onFromDateTimeUpdate(generateOutPut(fromDateObj, format)) 208 | const toDateObj = getInputDate(to_ts) 209 | onToDateTimeUpdate(generateOutPut(toDateObj, format)) 210 | 211 | this.setState({advance_pill: 'lm'}) 212 | 213 | } 214 | 215 | handleThisMonth = () => { 216 | const {format, 217 | onFromDateTimeUpdate, onToDateTimeUpdate} = this.props 218 | const now = new Date() 219 | 220 | const from_ts = startOfMonth(now) 221 | const to_ts = endOfMonth(now) 222 | 223 | // call related handlers 224 | const fromDateObj = getInputDate(from_ts) 225 | onFromDateTimeUpdate(generateOutPut(fromDateObj, format)) 226 | const toDateObj = getInputDate(to_ts) 227 | onToDateTimeUpdate(generateOutPut(toDateObj, format)) 228 | 229 | this.setState({advance_pill: 'tm'}) 230 | } 231 | 232 | handleThisWeek = () => { 233 | const {weekStartsOn, format, 234 | onFromDateTimeUpdate, onToDateTimeUpdate} = this.props 235 | const now = new Date() 236 | 237 | // @ts-ignore 238 | const from_ts = startOfWeek(now, {weekStartsOn}) 239 | // @ts-ignore 240 | const to_ts = endOfWeek(now, {weekStartsOn}) 241 | // call related handlers 242 | const fromDateObj = getInputDate(from_ts) 243 | onFromDateTimeUpdate(generateOutPut(fromDateObj, format)) 244 | const toDateObj = getInputDate(to_ts) 245 | onToDateTimeUpdate(generateOutPut(toDateObj, format)) 246 | 247 | this.setState({advance_pill: 'tw'}) 248 | } 249 | 250 | handleLastWeek = () => { 251 | const {weekStartsOn, format, 252 | onFromDateTimeUpdate, onToDateTimeUpdate} = this.props 253 | 254 | const now = new Date() 255 | // @ts-ignore 256 | let from_ts = startOfWeek(now, {weekStartsOn}) 257 | from_ts = subWeeks(from_ts, 1) 258 | // @ts-ignore 259 | let to_ts = endOfWeek(now, {weekStartsOn}) 260 | to_ts = subWeeks(to_ts, 1) 261 | // call related handlers 262 | const fromDateObj = getInputDate(from_ts) 263 | onFromDateTimeUpdate(generateOutPut(fromDateObj, format)) 264 | const toDateObj = getInputDate(to_ts) 265 | onToDateTimeUpdate(generateOutPut(toDateObj, format)) 266 | 267 | this.setState({advance_pill: 'lw'}) 268 | } 269 | } -------------------------------------------------------------------------------- /src/components/RangePickerInput.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useCallback } from 'react'; 2 | 3 | import RangePicker from "./RangePicker"; 4 | import { RangePickerInputProps } from '../interfaces/rangepicker.interfaces'; 5 | import { getInitialDateForInput } from '../utils/datetimepicker.utils'; 6 | 7 | import { useOutsideAlerter } from '../utils/useOutsideAlerter.hook' 8 | 9 | import styles from "../styles/rangepicker.css"; 10 | import rootstyles from "../styles/root.css"; 11 | 12 | 13 | const RangePickerInput = (props: RangePickerInputProps) => { 14 | const wrapperRef = useRef(null); 15 | 16 | const [show_picker, setShow] = useState(false) 17 | 18 | useOutsideAlerter(wrapperRef, setShow); 19 | 20 | const handleShow = useCallback(() => { 21 | setShow(true) 22 | },[]) 23 | 24 | const handleClose = useCallback(() => { 25 | setShow(false) 26 | if(props.onDone) props.onDone() 27 | },[]) 28 | 29 | const from_date_str = getInitialDateForInput(props.from_date, props.format) 30 | const to_date_str = getInitialDateForInput(props.to_date, props.format) 31 | const show_date = `${from_date_str || '-- / -- / -- , --:-- '} To ${to_date_str || ' -- / -- / -- , --:--'}` 32 | const {colors} = props 33 | 34 | const inputComponentProps = { 35 | value: show_date, 36 | readOnly: true, 37 | disabled: props.isDisabled, 38 | onFocus: handleShow 39 | } 40 | 41 | const inputComponent = React.isValidElement(props.inputComponent) ? 42 | React.cloneElement(props.inputComponent, inputComponentProps) 43 | : 44 | 45 | 46 | return ( 47 |
48 | 49 | {inputComponent} 50 | 51 | {(show_picker && !props.isDisabled) && 52 |
54 |
55 | 64 |
65 |
66 | } 67 |
68 | ) 69 | } 70 | 71 | RangePickerInput.defaultProps = { 72 | closeButtonText: 'Close', 73 | isDisabled: false, 74 | } 75 | 76 | 77 | export default RangePickerInput -------------------------------------------------------------------------------- /src/components/TimePicker.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState, useEffect } from "react"; 2 | import { isEmpty, isNaN, padStart } from 'lodash' 3 | 4 | import ClockFace from './ClockFace' 5 | 6 | import { createInputTime, generateTimeOutput } from "../utils/timepicker.utils"; 7 | 8 | import { 9 | TimeTitleWrapperProps, 10 | TimePickerProps, 11 | MainTime, 12 | defaultConfigs 13 | } from "../interfaces/timepicker.interfaces"; 14 | 15 | import { getSetButtonStyles } from '../styles/monthpicker.colors' 16 | import styles from '../styles/timepicker.css' 17 | 18 | 19 | export default ({ 20 | time, format = defaultConfigs.format, 21 | colors, 22 | onTimeUpdate 23 | }: TimePickerProps) => { 24 | const curr_time = createInputTime(time) 25 | 26 | const handleTimeChange = useCallback((newTime: MainTime) => { 27 | let resTime = generateTimeOutput(newTime, format) 28 | if (!resTime.formatted) { 29 | // formatted string is empty even after user clicked smt 30 | if (!isNaN(Number(resTime.time.minute))) { 31 | // @ts-ignore; user clicked minute so default hour to 0 and add mins 32 | resTime.formatted = `00:${padStart(resTime.time.minute, 2, '0')} AM` 33 | } 34 | else if (!isNaN(Number(resTime.time.hour))) { 35 | // @ts-ignore; user clicked hour so default min to 0 and add hour 36 | resTime.formatted = `${padStart(resTime.time.hour, 2, '0')}:00 AM` 37 | } 38 | else if (resTime.time.meridiem === 'PM') { 39 | // user Clicked on PM btn 40 | resTime.formatted = '00:00 PM' 41 | } 42 | } 43 | onTimeUpdate(resTime) 44 | }, []) 45 | 46 | 47 | const wrapper_style = { 48 | background: colors.primary_color, 49 | color: colors.primary_font_color 50 | } 51 | 52 | return ( 53 |
54 | 55 | 60 | 61 | 64 |
65 | ) 66 | } 67 | 68 | 69 | const TimeTitleWrapper = ({ 70 | hour, minute, meridiem, time_format, colors, onTimeUpdate 71 | }: TimeTitleWrapperProps) => { 72 | const [edit, setEdit] = useState(false) 73 | const [resHour, setHour] = useState(hour) 74 | const [resMinute, setMinute] = useState(minute) 75 | const [resMeridiem, setMeridiem] = useState(meridiem) 76 | const [errors, setErrors] = useState({}) // { hour: false, min: false } 77 | 78 | useEffect(() => { 79 | // handle props update 80 | setHour(hour) 81 | setMinute(minute) 82 | setMeridiem(meridiem) 83 | }, [hour, minute, meridiem]) 84 | 85 | const { formatted } = generateTimeOutput({ hour, minute, meridiem }, time_format) 86 | 87 | const handleHourChange = useCallback((e) => { 88 | setHour(e.target.value) 89 | }, []) 90 | 91 | const handleMinuteChange = useCallback((e) => { 92 | setMinute(e.target.value) 93 | }, []) 94 | 95 | const handleMeridiemChange = useCallback((e) => { 96 | setMeridiem(e.target.value) 97 | }, []) 98 | 99 | const handleTimeChange = useCallback(() => { 100 | let errors = {}; 101 | if (!validHour(String(resHour))) errors['hour'] = true; 102 | if (!validMinute(String(resMinute))) errors['min'] = true; 103 | // error in input 104 | if (!isEmpty(errors)) { 105 | setErrors(errors) 106 | return 107 | } 108 | // update data 109 | onTimeUpdate({ 110 | hour: Number(resHour), 111 | minute: Number(resMinute), 112 | meridiem: Number(resHour) > 12 113 | ? "PM" : resMeridiem, 114 | }) 115 | setErrors({}) 116 | setEdit(false) 117 | }, [resHour, resMinute, resMeridiem]) 118 | 119 | if (edit) { 120 | return ( 121 |
122 | 125 |
:
126 | 129 | 135 |
138 | Set 139 |
140 |
141 | ) 142 | } 143 | return ( 144 |
setEdit(true)}> 147 | {formatted || '-- : -- AM'} 148 |
149 | ) 150 | } 151 | 152 | // update input class based on field error 153 | const getInputClass = ( 154 | fieldName: string, 155 | errors: { 156 | hour?: boolean, 157 | min?: boolean 158 | }) => { 159 | return [ 160 | styles.time_edit_input, 161 | errors[fieldName] && styles.time_edit_input_error 162 | ].join(' ') 163 | } 164 | 165 | // validate hour 166 | const validHour = (hour: string): boolean => { 167 | // empty string handle 168 | if (!!hour.trim()) { 169 | // number validation 170 | let newHour = Number(hour) 171 | return !isNaN(newHour) && newHour <= 23 && newHour >= 0 172 | } else { 173 | return false 174 | } 175 | } 176 | 177 | // validate minute 178 | const validMinute = (minute: string): boolean => { 179 | // empty string handle 180 | if (!!minute.trim()) { 181 | // number validation 182 | let newMinute = Number(minute) 183 | return !isNaN(newMinute) && newMinute < 60 && newMinute >= 0 184 | } else { 185 | return false 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/components/TimePickerInput.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect, useCallback } from 'react'; 2 | 3 | import TimePicker from "./TimePicker"; 4 | 5 | import { createInputTime, generateTimeOutput } from '../utils/timepicker.utils'; 6 | import { TimePickerInputProps, OutputTime, defaultConfigs } from '../interfaces/timepicker.interfaces'; 7 | 8 | import { useOutsideAlerter } from '../utils/useOutsideAlerter.hook' 9 | 10 | import styles from '../styles/timepicker.css' 11 | import rootstyles from "../styles/root.css"; 12 | 13 | 14 | const TimePickerInput = (props:TimePickerInputProps) => { 15 | 16 | const wrapperRef = useRef(null); 17 | const [show_picker, setShow] = useState(false) 18 | const [showTime, setTime] = useState( 19 | // create initial string representaion to show in input 20 | generateTimeOutput( 21 | createInputTime(props.time), props.format || defaultConfigs.format 22 | ).formatted 23 | ) 24 | 25 | // update state if direct prop update 26 | useEffect(() => { 27 | setTime( 28 | generateTimeOutput( 29 | createInputTime(props.time), props.format || defaultConfigs.format 30 | ).formatted 31 | ) 32 | }, [props.time, props.format]) 33 | 34 | 35 | const handleTimeUpdate = (timeObj:OutputTime) => { 36 | props.onTimeUpdate(timeObj) 37 | setTime(timeObj.formatted) 38 | } 39 | 40 | const handleShow = useCallback(() => { 41 | setShow(true) 42 | },[]) 43 | 44 | const handleComplete = useCallback(() => { 45 | setShow(false) 46 | if(props.onDone) props.onDone() 47 | }, []) 48 | 49 | useOutsideAlerter(wrapperRef, setShow); 50 | 51 | const inputComponentProps = { 52 | value: showTime, 53 | placeholder: 'Click to select time', 54 | readOnly: true, 55 | disabled: props.isDisabled, 56 | onFocus: handleShow 57 | } 58 | 59 | const inputComponent = React.isValidElement(props.inputComponent) ? 60 | React.cloneElement(props.inputComponent, inputComponentProps) 61 | : 62 | 63 | 64 | return ( 65 |
66 | 67 | {inputComponent} 68 | 69 | {(show_picker && !props.isDisabled) && 70 |
72 |
73 | 76 |
77 |
78 | } 79 |
80 | ) 81 | } 82 | 83 | TimePickerInput.defaultProps = { 84 | isDisabled: false 85 | } 86 | 87 | export default TimePickerInput -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentType } from 'react'; 2 | import StyleWrapper from './utils/style.utils'; 3 | 4 | import TimePickerModule from "./components/TimePicker"; 5 | import TimePickerInputModule from "./components/TimePickerInput"; 6 | import MonthPickerModule from "./components/MonthPicker"; 7 | 8 | import DatePickerModule from "./components/DatePicker"; 9 | import DatePickerInputModule from "./components/DatePickerInput"; 10 | 11 | import DateTimePickerModuel from "./components/DateTimePicker"; 12 | import DateTimePickerInputModuel from "./components/DateTimePickerInput"; 13 | 14 | import RangePicker from "./components/RangePicker"; 15 | import DateTimeRangePickerInputModule from "./components/RangePickerInput"; 16 | 17 | import DateRangeCalendarPickerModule from "./components/DateRangeCalendarPicker"; 18 | import DateRangeCalendarPickerInputModule from "./components/DateRangeCalendarPickerInput"; 19 | 20 | import DateRangePickerModule from './components/DateRangePicker' 21 | import DateRangePickerInputModule from './components/DateRangePickerInput' 22 | // TypeScript imports 23 | import { 24 | TimePickerInputProps, TimePickerProps 25 | } from './interfaces/timepicker.interfaces' 26 | import { MonthPickerProps } from './interfaces/monthpicker.interfaces' 27 | import { 28 | DatePickerInputProps, DatePickerProps 29 | } from './interfaces/datepicker.interfaces' 30 | import { 31 | DateTimePickerInputProps, 32 | DateTimePickerProps 33 | } from './interfaces/datetimepicker.interfaces' 34 | import { 35 | DateRangePickerInputProps, DateRangePickerProps, 36 | RangePickerInputProps, RangePickerProps 37 | } from './interfaces/rangepicker.interfaces' 38 | 39 | 40 | export const TimePicker:ComponentType = 41 | StyleWrapper(TimePickerModule) 42 | export const TimePickerInput:ComponentType = 43 | StyleWrapper(TimePickerInputModule) 44 | 45 | export const MonthPicker:ComponentType = 46 | StyleWrapper(MonthPickerModule) 47 | export const DatePicker:ComponentType = 48 | StyleWrapper(DatePickerModule) 49 | export const DatePickerInput:ComponentType = 50 | StyleWrapper(DatePickerInputModule) 51 | 52 | export const DateTimePicker:ComponentType = 53 | StyleWrapper(DateTimePickerModuel) 54 | export const DateTimePickerInput:ComponentType = 55 | StyleWrapper(DateTimePickerInputModuel) 56 | 57 | export const DateTimeRangePicker:ComponentType = 58 | StyleWrapper(RangePicker) 59 | export const DateTimeRangePickerInput:ComponentType = 60 | StyleWrapper(DateTimeRangePickerInputModule) 61 | 62 | export const DateRangeCalendarPicker:ComponentType = 63 | StyleWrapper(DateRangeCalendarPickerModule) 64 | export const DateRangeCalendarPickerInput:ComponentType = 65 | StyleWrapper(DateRangeCalendarPickerInputModule) 66 | 67 | export const DateRangePicker:ComponentType = 68 | StyleWrapper(DateRangePickerModule) 69 | export const DateRangePickerInput:ComponentType = 70 | StyleWrapper(DateRangePickerInputModule) -------------------------------------------------------------------------------- /src/interfaces/datepicker.interfaces.ts: -------------------------------------------------------------------------------- 1 | import { ComponentTheme } from './style.interfaces'; 2 | 3 | export interface MainDate { 4 | day : number | undefined, 5 | month : number, 6 | year : number, 7 | } 8 | 9 | export interface DatePickerOutPut extends MainDate { 10 | date : Date | undefined, 11 | formatted : string 12 | } 13 | 14 | export interface DatePickerState extends MainDate { 15 | dateRangeIndex: number | undefined, 16 | // hover states 17 | hoverOn: boolean, 18 | hoverRangeIndex: number, 19 | } 20 | 21 | export interface DatePickerProps { 22 | /** default value : new Date() ; i.e. current time */ 23 | date : Date | MainDate | string | undefined, 24 | /** default value : 0, Sunday */ 25 | weekStartsOn : number, 26 | /** default value : 'dd/MM/YYY' */ 27 | format : string, 28 | colors: ComponentTheme, 29 | onDateUpdate : ({}:DatePickerOutPut, is_date_update?:boolean) => void, 30 | onComplete? : () => void, 31 | // internal props 32 | /** created with func datepicker.utils => createRangeIndex */ 33 | otherDateRangeIndex: number, 34 | showRangeTrace?: boolean, 35 | traceStatus?: string 36 | } 37 | 38 | export interface DayListShape { 39 | day : number, 40 | // this will also be used as ID -> YYYYMMDD 41 | rangeIndex: number, 42 | // is this current month or not 43 | curr_month : boolean, 44 | } 45 | 46 | export interface DatePickerInputProps extends DatePickerProps { 47 | inputStyle? : React.CSSProperties, 48 | popupStyle? : React.CSSProperties, 49 | className? : string, 50 | popupClassName? : string, 51 | isDisabled?: boolean, 52 | inputComponent?: React.ReactElement, 53 | } 54 | 55 | export const defaultConfigs = { 56 | date : new Date(), 57 | format : 'do MMMM yyyy', 58 | weekStartsOn : 0 59 | } -------------------------------------------------------------------------------- /src/interfaces/datetimepicker.interfaces.ts: -------------------------------------------------------------------------------- 1 | import { TimeObject, OutputTime, MainTime } from "./timepicker.interfaces"; 2 | import { MainDate, DatePickerOutPut } from "./datepicker.interfaces"; 3 | import { ComponentTheme } from './style.interfaces'; 4 | 5 | export interface DateObject extends TimeObject, MainDate { 6 | // { day, month, year, hour? , hour24?, minute?, meridem?} 7 | } 8 | 9 | export interface DateTimePickerProps { 10 | date : Date | DateObject | string, 11 | // optional props 12 | /** default value : 'dd/MM/YYY hh:mm aaa' */ 13 | format : string, 14 | timeFormat : string, 15 | dateFormat : string, 16 | weekStartsOn : number, 17 | 18 | colors: ComponentTheme, 19 | 20 | onDateTimeUpdate : ({}:DateTimePickerOutPut) => void, 21 | onTimeUpdate? : (time:OutputTime) => void, 22 | onDateUpdate? : ({}:DatePickerOutPut) => void, 23 | } 24 | 25 | export interface MainDateTimeObject extends MainDate, MainTime { 26 | 27 | } 28 | 29 | export interface OutPutDateTime extends MainDate { 30 | // { day, month, year, hour , hour24, minute, meridem } 31 | date : Date, 32 | /** default value : 8 */ 33 | hour :number | undefined, 34 | /** default value : undefined */ 35 | hour24 :number | undefined, 36 | /** default value : 0 */ 37 | minute :number | undefined, 38 | /** default value : AM */ 39 | meridiem : string 40 | } 41 | 42 | export interface DateTimePickerOutPut { 43 | date : OutPutDateTime, 44 | formatted: string 45 | } 46 | 47 | 48 | export interface DateTimePickerInputProps extends DateTimePickerProps { 49 | inputStyle? : React.CSSProperties, 50 | popupStyle? : React.CSSProperties, 51 | className? : string, 52 | popupClassName? : string, 53 | closeButtonText?: String, 54 | isDisabled?: boolean, 55 | inputComponent?: React.ReactElement, 56 | onDone? : () => void 57 | } 58 | 59 | export const defaultConfigs = { 60 | date : new Date(), 61 | format : 'do MMMM yyyy, hh:mm aaa', 62 | weekStartsOn : 0 63 | } -------------------------------------------------------------------------------- /src/interfaces/monthpicker.interfaces.ts: -------------------------------------------------------------------------------- 1 | import { ComponentTheme } from './style.interfaces'; 2 | 3 | export interface OutputShape { 4 | // 0 -> 11, 0 = Jan; 11 = Dec 5 | month : number, 6 | year : number 7 | } 8 | 9 | export interface MonthPickerProps { 10 | /** default value : new Date() ; i.e. current time */ 11 | time?: OutputShape | Date, 12 | colors: ComponentTheme 13 | onDateUpdate: ({}:OutputShape) => void 14 | } -------------------------------------------------------------------------------- /src/interfaces/rangepicker.interfaces.ts: -------------------------------------------------------------------------------- 1 | import { OutputTime } from "./timepicker.interfaces"; 2 | import { DatePickerOutPut, MainDate } from "./datepicker.interfaces"; 3 | import { DateTimePickerOutPut, DateObject } from "./datetimepicker.interfaces"; 4 | import { ComponentTheme } from './style.interfaces'; 5 | import React from "react"; 6 | 7 | 8 | /************************************* 9 | * Date Time Range Picker Interfaces * 10 | *************************************/ 11 | 12 | // use by : RangePicker ( date time range picker ) 13 | export interface RangePickerProps { 14 | from_date : Date | DateObject | string, 15 | to_date : Date | DateObject | string, 16 | // optional props 17 | /** default value : 'dd/MM/YYY hh:mm aaa' */ 18 | format : string, 19 | timeFormat : string, 20 | dateFormat : string, 21 | weekStartsOn : number, 22 | 23 | colors: ComponentTheme, 24 | 25 | closeButtonText?: String, 26 | 27 | onFromDateTimeUpdate : ({}:DateTimePickerOutPut) => void, 28 | onFromTimeUpdate? : (time:OutputTime) => void, 29 | onFromDateUpdate? : ({}:DatePickerOutPut) => void, 30 | 31 | onToDateTimeUpdate : ({}:DateTimePickerOutPut) => void, 32 | onToTimeUpdate? : (time:OutputTime) => void, 33 | onToDateUpdate? : ({}:DatePickerOutPut) => void, 34 | 35 | onDone? : () => void 36 | } 37 | 38 | // use by : RangePickerInput 39 | export interface RangePickerInputProps extends RangePickerProps { 40 | inputStyle? : React.CSSProperties, 41 | popupStyle? : React.CSSProperties, 42 | className? : string, 43 | popupClassName? : string, 44 | isDisabled?: boolean, 45 | inputComponent?: React.ReactElement, 46 | } 47 | 48 | // use by : RangePicker ( date time range picker ) 49 | export interface RangePickerStates { 50 | is_to_date: boolean, 51 | advance_pill: string | null, 52 | } 53 | 54 | // use by : DateRangePicker 55 | export interface DateRangePickerStates { 56 | is_to_date: boolean, 57 | // show range trace on single calendar 58 | traceStatus?: string, 59 | advance_pill: string | null, 60 | otherDateRangeIndex: number | undefined, 61 | } 62 | 63 | /******************************** 64 | * Date Range Picker Interfaces * 65 | ********************************/ 66 | 67 | // use by : DateRangeCalendarPicker, DateRangePicker 68 | export interface DateRangePickerProps { 69 | /** default value : new Date() ; i.e. current time */ 70 | from_date : Date | MainDate | string, 71 | to_date : Date | MainDate | string, 72 | // optional props 73 | /** default value : 'dd/MM/YYY' */ 74 | format : string, 75 | /** default value : 0, Sunday */ 76 | weekStartsOn : number, 77 | 78 | colors: ComponentTheme, 79 | 80 | closeButtonText?: String, 81 | 82 | // if input is DateTime output than make sure to reset time values 83 | onFromDateUpdate : ({}:DateTimePickerOutPut | DatePickerOutPut) => void, 84 | onToDateUpdate : ({}:DateTimePickerOutPut | DatePickerOutPut) => void, 85 | 86 | onDone? : () => void 87 | } 88 | 89 | // use by : DateRangePickerInput, DateRangeCalendarPickerInput 90 | export interface DateRangePickerInputProps extends DateRangePickerProps { 91 | inputStyle? : React.CSSProperties, 92 | popupStyle? : React.CSSProperties, 93 | className? : string, 94 | popupClassName? : string, 95 | isDisabled?: boolean, 96 | inputComponent?: React.ReactElement, 97 | } 98 | 99 | // use by : DateRangeCalendarPicker 100 | export interface DateRangeCalendarPickerStates { 101 | is_to_date: boolean, 102 | advance_pill: string | null, 103 | } -------------------------------------------------------------------------------- /src/interfaces/style.interfaces.ts: -------------------------------------------------------------------------------- 1 | import { CSSProperties } from "react" 2 | 3 | export type ComponentThemeOptional = { 4 | primary_color?: string, 5 | primary_font_color?: string, 6 | light_font_color?: string, 7 | 8 | secondary_color?: string, 9 | 10 | primary_highlight_color?: string, 11 | secondary_highlight_color?: string, 12 | } 13 | 14 | export type ComponentTheme = { 15 | primary_color: string, 16 | /** 17 | * Used location of primary_color 18 | * - TimePicker 19 | * - in wrapper, main background 20 | * - active MM & hover font color 21 | * - active HH & hover font color 22 | * - meridiem block background while inactive 23 | * - meridiem active, hover font color 24 | * - MonthPicker 25 | * - in wrapper, main background 26 | * - year set btn font color while hover 27 | * - DatePicker 28 | * - in wrapper, main background 29 | * - calender cell active & hover font color 30 | * - DateTimePicker 31 | * - in wrapper, main background 32 | * - RangePicker 33 | * - in wrapper, main background 34 | * - from, to label color 35 | * - active from / to background color 36 | * - inactive from / to font color 37 | * - side month / week selection active and hover font color 38 | */ 39 | primary_font_color: string, 40 | /** 41 | * Used location of primary_font_color 42 | * - TimePicker 43 | * - in wrapper, main font color 44 | * which effect all inactive HH, MM font color 45 | * - inactive meridiem font color 46 | * - MonthPicker 47 | * - in wrapper, main font color but not effect anything 48 | * - right carousal arrow color 49 | * - DatePicker 50 | * - in wrapper, main font color but not effect anything 51 | * - DateTimePicker 52 | * - in wrapper, main font color but not effect anything 53 | * - RangePicker 54 | * - in wrapper, main font color but not effect anything 55 | */ 56 | light_font_color: string, 57 | /** 58 | * Used location of light_font_color 59 | * - MonthPicker 60 | * - left carousal arrow color 61 | * - month pill inactive font color 62 | */ 63 | secondary_color: string, 64 | /** 65 | * Used location of secondary_color 66 | * - TimePicker 67 | * - clock face background color 68 | * - MonthPicker 69 | * - month pill background color 70 | * - DateTimePicker 71 | * - right border color 72 | */ 73 | primary_highlight_color: string, 74 | /** 75 | * Used location of primary_highlight_color 76 | * - TimePicker 77 | * - time title color 78 | * - minute hand color 79 | * - active minute pill background color 80 | * - MonthPicker 81 | * - title color 82 | * - active month pill font color 83 | * - DatePicker 84 | * - week day title 85 | * - DateTimePickerInput 86 | * - done arrow color 87 | * - RangePicker 88 | * - done arrow color 89 | */ 90 | secondary_highlight_color: string, 91 | /** 92 | * Used location of secondary_highlight_color 93 | * - TimePicker 94 | * - hour hand color 95 | * - active hour pill background color 96 | * - active and hover meridiem background color 97 | * - MonthPicker 98 | * - inactive set btn font color 99 | * - hover set btn background color 100 | * - DatePicker 101 | * - active calender cell background color 102 | * - inactive calender cell font color 103 | * - RangePicker 104 | * - header background color 105 | * - side advance column background color 106 | */ 107 | } 108 | 109 | export type InlineCssStyles = CSSProperties | { [key: string]: string } -------------------------------------------------------------------------------- /src/interfaces/timepicker.interfaces.ts: -------------------------------------------------------------------------------- 1 | import { ComponentTheme } from './style.interfaces'; 2 | 3 | export interface TimeObject { 4 | /** default value : 8 */ 5 | hour? :number | string, 6 | /** default value : undefined */ 7 | hour24? :number | string, 8 | /** default value : 0 */ 9 | minute?:number | string, 10 | /** default value : AM */ 11 | meridiem? : string 12 | } 13 | 14 | export interface TimePickerProps { 15 | /** string fromat = "HH:mm" ( 24 hrs ) | "hh:mm aa" ( 12 hrs ) */ 16 | /** default value : 08:00 AM */ 17 | time : undefined | string | TimeObject, 18 | /** default value : hh:mm aaa */ 19 | format? : string 20 | // styling props 21 | colors : ComponentTheme 22 | // handler props 23 | onTimeUpdate : (time:OutputTime) => void 24 | onDone? : () => void 25 | } 26 | 27 | /** 28 | * Time object used internally for render and updates 29 | */ 30 | export interface MainTime { 31 | hour : number | undefined, // 12 hours format 32 | minute : number | undefined, 33 | // AM | PM 34 | meridiem : string 35 | } 36 | 37 | export interface OutputTime { 38 | formatted : string, 39 | time : { 40 | /** default value : 8 */ 41 | hour :number | undefined, 42 | /** default value : undefined */ 43 | hour24 :number | undefined, 44 | /** default value : 0 */ 45 | minute :number | undefined, 46 | /** default value : AM */ 47 | meridiem : string 48 | } 49 | } 50 | 51 | export const defaultConfigs = { 52 | format : 'hh:mm aaa' 53 | } 54 | 55 | export interface TimeTitleWrapperProps extends MainTime { 56 | time_format : string, 57 | colors: ComponentTheme, 58 | onTimeUpdate : ({}:MainTime) => void, 59 | } 60 | 61 | export interface ClockFaceProps extends MainTime { 62 | onTimeUpdate : ({}:MainTime) => void, 63 | colors: ComponentTheme 64 | } 65 | 66 | export interface TimePickerInputProps extends TimePickerProps { 67 | inputStyle? : React.CSSProperties, 68 | popupStyle? : React.CSSProperties, 69 | className? : string, 70 | popupClassName? : string, 71 | isDisabled?: boolean, 72 | inputComponent?: React.ReactElement, 73 | } -------------------------------------------------------------------------------- /src/styles/clockface.colors.ts: -------------------------------------------------------------------------------- 1 | import { ComponentTheme, InlineCssStyles } from '../interfaces/style.interfaces'; 2 | 3 | export const getTickColors = ( 4 | colors:ComponentTheme, rotation:number, 5 | is_hh_active:boolean, is_mm_active:boolean 6 | ) => { 7 | const active_hh_style:InlineCssStyles = { 8 | background : colors.secondary_highlight_color, 9 | color: colors.primary_color, 10 | '--hover-bg-color': colors.secondary_highlight_color, 11 | '--hover-color': colors.primary_color, 12 | '--font-rotation': `${-rotation}deg`, 13 | } 14 | 15 | const inactive_hh_style:InlineCssStyles = { 16 | '--hover-bg-color': colors.secondary_highlight_color, 17 | '--hover-color': colors.primary_color, 18 | '--font-rotation': `${-rotation}deg`, 19 | } 20 | 21 | const active_mm_style:InlineCssStyles = { 22 | background : colors.primary_highlight_color, 23 | color: colors.primary_color, 24 | '--hover-bg-color': colors.primary_highlight_color, 25 | '--hover-color': colors.primary_color, 26 | '--font-rotation': `${-rotation}deg`, 27 | } 28 | 29 | const inactive_mm_style:InlineCssStyles = { 30 | '--hover-bg-color': colors.primary_highlight_color, 31 | '--hover-color': colors.primary_color, 32 | '--font-rotation': `${-rotation}deg`, 33 | } 34 | 35 | const hh_style = is_hh_active ? active_hh_style : inactive_hh_style 36 | const mm_style = is_mm_active ? active_mm_style : inactive_mm_style 37 | 38 | return { hh_style, mm_style } 39 | } 40 | 41 | export const getMeridiemStyles = (colors:ComponentTheme) => { 42 | const meridiem_active_style = { 43 | '--hover-color' : colors.primary_color, 44 | '--hover-bg-color': colors.secondary_highlight_color, 45 | color : colors.primary_color, 46 | background: colors.secondary_highlight_color 47 | } 48 | 49 | const meridiem_style = { 50 | '--hover-color' : colors.primary_color, 51 | '--hover-bg-color': colors.secondary_highlight_color, 52 | background: colors.primary_color, 53 | color: colors.primary_font_color 54 | } 55 | return { meridiem_style, meridiem_active_style } 56 | } -------------------------------------------------------------------------------- /src/styles/date_time_picker.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | box-sizing: border-box; 3 | margin : 0 auto; 4 | font-family: Geneva, Tahoma, Verdana, sans-serif; 5 | 6 | display: table; 7 | } 8 | 9 | .table_cell { 10 | display: table-cell; 11 | vertical-align: middle; 12 | padding: .8em; 13 | } 14 | 15 | .calender { 16 | padding-right: 1.6em; 17 | } 18 | 19 | .table_cell:last-child { 20 | padding-left: 1.6em; 21 | } 22 | 23 | .picker_input_wrapper { 24 | position: relative; 25 | } 26 | 27 | .picker_input { 28 | composes: input from "./root.css"; 29 | /* 1.7 is padding in input */ 30 | width: calc(100% - 1.7em); 31 | text-align: center; 32 | min-width: 13em; 33 | } 34 | 35 | .picker_input:disabled { 36 | background-color: #e9e9e9; 37 | } 38 | 39 | 40 | .picker_header_wrapper { 41 | position: relative; 42 | } 43 | 44 | 45 | .picker_header_btn { 46 | position: absolute; 47 | padding: .7em; 48 | right: 0; 49 | cursor: pointer; 50 | } 51 | 52 | .picker_header_btn:hover { 53 | text-decoration: underline; 54 | } -------------------------------------------------------------------------------- /src/styles/datepicker.color.ts: -------------------------------------------------------------------------------- 1 | import { ComponentTheme, InlineCssStyles } from '../interfaces/style.interfaces'; 2 | 3 | export const getCalenderCellColors = ( 4 | colors:ComponentTheme, cell_type:string 5 | ) : InlineCssStyles => { 6 | if(cell_type === 'border') { 7 | return { 8 | 'borderBottomColor': colors.secondary_highlight_color, 9 | 'borderBottomStyle': 'solid', 10 | 'borderBottomWidth': '1px', 11 | 'borderTopColor': colors.secondary_highlight_color, 12 | 'borderTopStyle': 'solid', 13 | 'borderTopWidth': '1px', 14 | color: colors.secondary_highlight_color, 15 | '--calender-cell-hover-color': colors.primary_color, 16 | '--calender-cell-hover-bg-color': colors.secondary_highlight_color, 17 | } 18 | } 19 | else if(cell_type === 'border-dotted') { 20 | return { 21 | 'borderRightColor': colors.secondary_highlight_color, 22 | 'borderRightStyle': 'dotted', 23 | 'borderRightWidth': '1px', 24 | 'borderLeftColor': colors.secondary_highlight_color, 25 | 'borderLeftStyle': 'dotted', 26 | 'borderLeftWidth': '1px', 27 | color: colors.secondary_highlight_color, 28 | '--calender-cell-hover-color': colors.primary_color, 29 | '--calender-cell-hover-bg-color': colors.secondary_highlight_color, 30 | } 31 | } 32 | else if(cell_type === 'solid') { 33 | return { 34 | background: colors.secondary_highlight_color, 35 | color: colors.primary_color, 36 | '--calender-cell-hover-color': colors.primary_color, 37 | '--calender-cell-hover-bg-color': colors.secondary_highlight_color, 38 | } 39 | } else { // normal cell color style 40 | return { 41 | color: colors.secondary_highlight_color, 42 | '--calender-cell-hover-color': colors.primary_color, 43 | '--calender-cell-hover-bg-color': colors.secondary_highlight_color, 44 | } 45 | } 46 | } 47 | 48 | export const getWrapperStyles = (colors: ComponentTheme) => ({ 49 | background: colors.primary_color, 50 | color: colors.primary_font_color 51 | }) -------------------------------------------------------------------------------- /src/styles/datepicker.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | box-sizing: border-box; 3 | margin : 0 auto; 4 | font-family: Geneva, Tahoma, Verdana, sans-serif; 5 | 6 | /* other element sizes */ 7 | --calender-pill-size : 3em; 8 | 9 | padding-bottom: .8em; 10 | width: calc( (var(--calender-pill-size) * 7) + 1.6em); 11 | } 12 | 13 | 14 | .calender_wrapper { 15 | margin: 0 auto; 16 | /* month may have 6 or 5 rows, keep calender hight same for both */ 17 | min-height: 16em; 18 | border-collapse: collapse; 19 | } 20 | 21 | .week_day_header { 22 | text-align: center; 23 | font-weight: bold; 24 | } 25 | 26 | .week_day_title { 27 | padding : .5em 1em; 28 | } 29 | 30 | .calender_cell { 31 | text-align: center; 32 | cursor: pointer; 33 | font-weight: bold; 34 | padding : .5em .8em; 35 | transition: all .2s linear; 36 | /* transparent border so when hover adds 1 px border UI do not change size */ 37 | border-bottom-color: transparent; 38 | border-bottom-style: solid; 39 | border-bottom-width: 1px; 40 | border-top-color: transparent; 41 | border-top-style: solid; 42 | border-top-width: 1px; 43 | } 44 | 45 | .calender_cell:hover { 46 | background: var(--calender-cell-hover-bg-color); 47 | color : var(--calender-cell-hover-color) !important; 48 | } 49 | 50 | .picker_input_wrapper { 51 | position: relative; 52 | } 53 | 54 | .picker_input { 55 | composes: input from "./root.css"; 56 | /* 1.7 is padding in input */ 57 | width: calc(100% - 1.7em); 58 | text-align: center; 59 | } 60 | 61 | .picker_input:disabled { 62 | background-color: #e9e9e9; 63 | } 64 | -------------------------------------------------------------------------------- /src/styles/monthpicker.colors.ts: -------------------------------------------------------------------------------- 1 | import { ComponentTheme, InlineCssStyles } from '../interfaces/style.interfaces'; 2 | 3 | export const getMonthPillColors = ( 4 | colors:ComponentTheme, is_active:boolean 5 | ) : InlineCssStyles => { 6 | if(is_active) { 7 | return { 8 | background: colors.secondary_color, 9 | color: colors.primary_highlight_color, 10 | fontWeight: 'bold' 11 | } 12 | } else { 13 | return { 14 | color: colors.light_font_color, 15 | background: colors.secondary_color, 16 | } 17 | } 18 | } 19 | 20 | // common use in month picker and time picker 21 | export const getSetButtonStyles = (colors: ComponentTheme) => ({ 22 | color: colors.secondary_highlight_color, 23 | '--year-edit-hover-color': colors.primary_color, 24 | '--year-edit-hover-bg-color': colors.secondary_highlight_color 25 | }) -------------------------------------------------------------------------------- /src/styles/monthpicker.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | box-sizing: border-box; 3 | margin : 0 auto; 4 | font-family: Geneva, Tahoma, Verdana, sans-serif; 5 | 6 | /* other element sizes */ 7 | --month-pill-size : 4em; 8 | --month-pill-margin : .3em; 9 | 10 | padding-top: .8em; 11 | width: calc( (var(--month-pill-size) * 4) + (var(--month-pill-margin) * 9) ); 12 | } 13 | 14 | /* --------- Year Picker ------------ */ 15 | 16 | .year_show { 17 | font-size: 1.5em; 18 | text-align: center; 19 | padding-bottom: .4em; 20 | } 21 | 22 | .year_edit { 23 | padding-bottom: .4em; 24 | text-align: center; 25 | } 26 | 27 | .year_edit_input { 28 | composes: input from "./root.css"; 29 | display: inline-block; 30 | text-align: center; 31 | width: 6em; 32 | } 33 | 34 | .year_edit_submit { 35 | cursor: pointer; 36 | padding : .5em; 37 | display: inline-block; 38 | transition: all .2s linear; 39 | border-radius: .5em; 40 | font-weight: bold; 41 | margin: 0 0.6em; 42 | } 43 | 44 | .year_edit_submit:hover { 45 | color: var(--year-edit-hover-color) !important; 46 | background: var(--year-edit-hover-bg-color); 47 | } 48 | 49 | /* --------- Month Picker ------------ */ 50 | 51 | .month_wrapper { 52 | display: flex; 53 | justify-content: center; 54 | align-items: center; 55 | min-width: 18.7em; 56 | } 57 | 58 | .month_pill_wrapper { 59 | display: inline-block; 60 | padding : .1em; 61 | white-space: nowrap; 62 | overflow: hidden; 63 | width: calc( (var(--month-pill-size) * 3) + (var(--month-pill-margin) * 6) ); 64 | } 65 | 66 | .month_pill_crousel { 67 | transition: all .2s linear; 68 | } 69 | 70 | .crousel_btns { 71 | display: inline-block; 72 | cursor: pointer; 73 | width: calc(var(--month-pill-size)/2); 74 | border-radius: .8em; 75 | margin: var(--month-pill-margin); 76 | height: 1.2em; 77 | line-height: 1.2em; 78 | text-align: center; 79 | composes: no_select from "./root.css"; 80 | } 81 | 82 | .month_btn { 83 | display: inline-block; 84 | cursor: pointer; 85 | padding : .3em 0; 86 | width: var(--month-pill-size); 87 | border-radius: .8em; 88 | margin: var(--month-pill-margin); 89 | line-height: 1.2em; 90 | text-align: center; 91 | transition: all .2s linear; 92 | composes: no_select from "./root.css"; 93 | } -------------------------------------------------------------------------------- /src/styles/rangepicker.colors.ts: -------------------------------------------------------------------------------- 1 | import { ComponentTheme, InlineCssStyles } from '../interfaces/style.interfaces'; 2 | 3 | export const getHeaderFieldColors = ( 4 | colors:ComponentTheme, is_active:boolean 5 | ) : InlineCssStyles => { 6 | if(is_active) { 7 | return { 8 | background: colors.primary_color, 9 | color: colors.secondary_highlight_color, 10 | boxShadow: 'none', 11 | WebkitBoxShadow: 'none', 12 | textShadow: `0 0 .65px ${colors.secondary_highlight_color}, 0 0 .65px ${colors.secondary_highlight_color}` 13 | } 14 | } else { 15 | return { 16 | color: colors.primary_color, 17 | background: colors.secondary_highlight_color, 18 | WebkitFilter:'brightness(113%)', 19 | filter:'brightness(113%)' 20 | } 21 | } 22 | } 23 | 24 | export const getActivePillColors = ( 25 | colors:ComponentTheme, is_active:boolean 26 | ) : InlineCssStyles => { 27 | if(is_active) { 28 | return { 29 | color: colors.primary_color, 30 | borderLeftColor: 'var(--active-pill-hover-color)', 31 | '--active-pill-hover-color': colors.primary_color, 32 | } 33 | } else { 34 | return { '--active-pill-hover-color': colors.primary_color } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/styles/rangepicker.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | box-sizing: border-box; 3 | margin : 0 auto; 4 | font-family: Geneva, Tahoma, Verdana, sans-serif; 5 | } 6 | 7 | .header { 8 | display: table-row; 9 | text-align: left; 10 | } 11 | 12 | .header_div { 13 | display: inline-block; 14 | margin: 1em 1.5em; 15 | cursor: pointer; 16 | } 17 | 18 | .header_label { 19 | font-size: .86em; 20 | padding-left: .5em; 21 | font-weight: bold; 22 | } 23 | 24 | .header_field_abs { 25 | font-size: 1em; 26 | min-height: 2em; 27 | line-height: 2em; 28 | padding: 0em 1em; 29 | border-radius: .8em; 30 | margin-top: .5em; 31 | transition: all .2s linear; 32 | min-width: 19em; 33 | text-align: center; 34 | box-shadow: 0px 0px 8px 0px rgba(0,0,0,0.16); 35 | -webkit-box-shadow: 0px 0px 8px 0px rgba(0,0,0,0.16); 36 | -moz-box-shadow: 0px 0px 8px 0px rgba(0,0,0,0.16); 37 | } 38 | 39 | .header_field { 40 | composes : header_field_abs; 41 | background: #4dc4ca; 42 | color : var(--primary-color); 43 | } 44 | 45 | .header_field_active { 46 | composes : header_field_abs; 47 | font-weight: bold; 48 | background: var(--primary-color); 49 | color : var(--secondary-highlight-color); 50 | } 51 | 52 | .done_btn { 53 | float: right; 54 | margin: 1.7em 1.5em; 55 | padding : .4em .8em; 56 | border-radius: .3em; 57 | cursor: pointer; 58 | font-weight: bold; 59 | transition: all .2s linear; 60 | min-height: 2em; 61 | line-height: 2em; 62 | } 63 | 64 | .done_btn:hover { 65 | text-decoration: underline; 66 | } 67 | 68 | .advance_cell { 69 | display: table-cell; 70 | vertical-align: middle; 71 | text-align: left; 72 | min-width: 10em; 73 | height: 27em; 74 | /* min height not work on td; */ 75 | } 76 | .advance_pill { 77 | cursor: pointer; 78 | color : #ffffffbd; 79 | font-weight: bold; 80 | padding:1em 1em .8em 1em; 81 | border-left: .5em solid transparent; 82 | transition: all .2s linear; 83 | min-width: 8em; 84 | position: relative; 85 | } 86 | 87 | .advance_pill::after { 88 | content: ""; 89 | position: absolute; 90 | left: -0.5em; 91 | right: 0; 92 | bottom: 0; 93 | height: 1px; 94 | background: var(--active-pill-hover-color); 95 | opacity: 0.28; 96 | } 97 | 98 | .advance_pill:last-child::after { 99 | height: 0px; 100 | } 101 | 102 | .advance_pill_active { 103 | composes : advance_pill; 104 | border-left: .5em solid; 105 | } 106 | 107 | .advance_pill:hover { 108 | color: var(--active-pill-hover-color); 109 | border-left: .5em solid; 110 | } 111 | 112 | .table_wrapper { 113 | display: table; 114 | margin: 0 auto; 115 | } 116 | 117 | .picker_input_wrapper { 118 | position: relative; 119 | } 120 | 121 | .picker_input { 122 | composes: input from "./root.css"; 123 | /* 1.7 is padding in input */ 124 | width: calc(100% - 1.7em); 125 | text-align: center; 126 | min-width: 30em; 127 | } 128 | 129 | /** 130 | Date range picker style 131 | **/ 132 | 133 | .table_cell { 134 | display: table-cell; 135 | vertical-align: middle; 136 | padding: .8em; 137 | } 138 | 139 | .separator_wrapper { 140 | padding-left: 0; 141 | padding-right: 0; 142 | position: relative; 143 | } 144 | 145 | .separator_wrapper::before { 146 | position: absolute; 147 | content: ""; 148 | height: calc(50% - 1.6em); 149 | border-right: 1px solid; 150 | top: 0; 151 | } 152 | 153 | .separator_wrapper::after { 154 | position: absolute; 155 | content: ""; 156 | height: calc(50% - 1.6em); 157 | border-right: 1px solid; 158 | bottom: 0; 159 | } 160 | 161 | .separator_circle { 162 | height: 2em; 163 | width: 2em; 164 | border: 1px solid; 165 | border-radius: 50%; 166 | font-size: 1.2em; 167 | } 168 | 169 | .separator { 170 | text-align: center; 171 | line-height: 2em; 172 | } 173 | 174 | .picker_pad_left { 175 | padding-left: 0; 176 | } 177 | 178 | .picker_pad_right { 179 | padding-right: 0; 180 | } -------------------------------------------------------------------------------- /src/styles/root.css: -------------------------------------------------------------------------------- 1 | .no_select { 2 | -webkit-user-select: none; 3 | -moz-user-select: none; 4 | -ms-user-select: none; 5 | user-select: none; 6 | } 7 | 8 | .input { 9 | border:none; 10 | outline: none; 11 | font-size: 0.9em; 12 | height: 2.5em; 13 | padding: .2em 1.2em .2em .5em; 14 | border-radius: 0.417em; 15 | background-color: #f7f7f7; 16 | color: #6c6b6b; 17 | } 18 | 19 | .picker_model { 20 | position: absolute; 21 | z-index: 2; 22 | left: 50%; 23 | -webkit-transform: translate(-50%, 0); 24 | -ms-transform: translate(-50%, 0); 25 | transform: translate(-50%, 0) 26 | } 27 | 28 | .picker_model_inside { 29 | margin: 0 auto; 30 | display: inline-block; 31 | margin-top: 1px; 32 | box-shadow: -4px 4px 44px -4px rgba(0,0,0,0.24); 33 | -webkit-box-shadow: -4px 4px 44px -4px rgba(0,0,0,0.24); 34 | -moz-box-shadow: -4px 4px 44px -4px rgba(0,0,0,0.24); 35 | } -------------------------------------------------------------------------------- /src/styles/timepicker.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | box-sizing: border-box; 3 | margin: 0 auto; 4 | font-family: Geneva, Tahoma, Verdana, sans-serif; 5 | 6 | /** clock size */ 7 | --clock-size: 16em; 8 | --clock-padding: 0.8em; 9 | --clock-hand-height: 1.8em; 10 | 11 | padding-top: 0.8em; 12 | width: calc(var(--clock-size) + (var(--clock-padding) * 2)); 13 | } 14 | 15 | .title { 16 | text-align: center; 17 | font-size: 1.5em; 18 | padding-bottom: 0.4em; 19 | } 20 | 21 | .clockface { 22 | position: relative; 23 | padding: var(--clock-padding); 24 | } 25 | 26 | .clock { 27 | height: var(--clock-size); 28 | width: var(--clock-size); 29 | border-radius: 50%; 30 | } 31 | 32 | .hand_wrapper { 33 | position: absolute; 34 | top: calc(var(--clock-size) / 2); 35 | width: calc(var(--clock-size) / 2); 36 | height: var(--clock-hand-height); 37 | transform-origin: left; 38 | } 39 | 40 | .btns { 41 | position: relative; 42 | z-index: 2; 43 | cursor: pointer; 44 | width: var(--clock-hand-height); 45 | height: var(--clock-hand-height); 46 | line-height: var(--clock-hand-height); 47 | text-align: center; 48 | display: inline-block; 49 | float: right; 50 | margin-left: 0.5em; 51 | margin-right: 0.4em; 52 | border-radius: 50%; 53 | transition: all 0.2s linear; 54 | transform: scale(1) rotate(var(--font-rotation)); 55 | composes: no_select from "./root.css"; 56 | } 57 | 58 | .hh { 59 | composes: btns; 60 | } 61 | 62 | .mm { 63 | composes: btns; 64 | } 65 | 66 | .hh:hover, 67 | .mm:hover { 68 | background: var(--hover-bg-color) !important; 69 | color: var(--hover-color) !important; 70 | transform: scale(1.2) rotate(var(--font-rotation)); 71 | } 72 | 73 | .hand { 74 | z-index: 1; 75 | pointer-events: none; 76 | position: absolute; 77 | height: 1px; 78 | transform-origin: left; 79 | transition: all 0.2s linear; 80 | } 81 | 82 | .mm_hand { 83 | composes: hand; 84 | width: calc((var(--clock-size) / 2) - var(--clock-hand-height)); 85 | } 86 | 87 | .hh_hand { 88 | composes: hand; 89 | width: calc((var(--clock-size) / 2) - (var(--clock-hand-height) * 2.5)); 90 | } 91 | 92 | .meridiem { 93 | cursor: pointer; 94 | position: absolute; 95 | bottom: 0.4em; 96 | height: 2.5em; 97 | line-height: 2.5em; 98 | width: 2.5em; 99 | text-align: center; 100 | border-radius: 50%; 101 | transition: all 0.2s linear; 102 | } 103 | 104 | .meridiem_am { 105 | left: 0.4em; 106 | composes: no_select from "./root.css"; 107 | } 108 | 109 | .meridiem_pm { 110 | right: 0.4em; 111 | composes: no_select from "./root.css"; 112 | } 113 | 114 | .meridiem:hover { 115 | color: var(--hover-color) !important; 116 | background: var(--hover-bg-color) !important; 117 | } 118 | 119 | .picker_input_wrapper { 120 | position: relative; 121 | } 122 | 123 | .picker_input { 124 | composes: input from "./root.css"; 125 | /* 1.7 is padding in input */ 126 | width: calc(100% - 1.7em); 127 | text-align: center; 128 | } 129 | 130 | /* --------- Time Input ------------ */ 131 | 132 | .time_edit { 133 | padding-bottom: 0.4em; 134 | text-align: center; 135 | } 136 | 137 | .time_edit_input { 138 | composes: input from "./root.css"; 139 | padding: 0.2em 0.5em 0.2em 0.5em !important; 140 | display: inline-block; 141 | text-align: center; 142 | width: 3em; 143 | } 144 | 145 | .time_edit_input::-webkit-outer-spin-button { 146 | -webkit-appearance: none; 147 | } 148 | 149 | .time_edit_input::-webkit-inner-spin-button { 150 | -webkit-appearance: none; 151 | } 152 | 153 | input[type=number].time_edit_input { 154 | -moz-appearance: textfield; 155 | } 156 | 157 | .time_edit_input_error { 158 | border: 1px solid red !important; 159 | } 160 | 161 | .time_edit_select { 162 | width: 4em; 163 | margin-left: 1em; 164 | background-image: url("data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0Ljk1IDEwIj48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6I2Y3ZjdmNzt9LmNscy0ye2ZpbGw6IzQ0NDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPmFycm93czwvdGl0bGU+PHJlY3QgY2xhc3M9ImNscy0xIiB3aWR0aD0iNC45NSIgaGVpZ2h0PSIxMCIvPjxwb2x5Z29uIGNsYXNzPSJjbHMtMiIgcG9pbnRzPSIxLjQxIDQuNjcgMi40OCAzLjE4IDMuNTQgNC42NyAxLjQxIDQuNjciLz48cG9seWdvbiBjbGFzcz0iY2xzLTIiIHBvaW50cz0iMy41NCA1LjMzIDIuNDggNi44MiAxLjQxIDUuMzMgMy41NCA1LjMzIi8+PC9zdmc+"); 165 | background-repeat: no-repeat; 166 | background-position: 95% 50%; 167 | -moz-appearance: none; 168 | -webkit-appearance: none; 169 | appearance: none; 170 | } 171 | 172 | .time_edit_select:focus { 173 | outline: none; 174 | } 175 | 176 | .time_edit_colon { 177 | display: inline-block; 178 | text-align: center; 179 | padding: 0 0.4em; 180 | } 181 | 182 | .time_edit_submit { 183 | cursor: pointer; 184 | padding: 0.5em; 185 | display: inline-block; 186 | transition: all 0.2s linear; 187 | border-radius: 0.5em; 188 | font-weight: bold; 189 | margin: 0 0.6em; 190 | } 191 | 192 | .time_edit_submit:hover { 193 | color: var(--year-edit-hover-color) !important; 194 | background: var(--year-edit-hover-bg-color); 195 | } 196 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Default CSS definition for typescript, 3 | * will be overridden with file-specific definitions by rollup 4 | */ 5 | declare module '*.css' { 6 | const content: { [className: string]: string }; 7 | export default content; 8 | } 9 | 10 | interface SvgrComponent extends React.StatelessComponent> {} 11 | 12 | declare module '*.svg' { 13 | const svgUrl: string; 14 | const svgComponent: SvgrComponent; 15 | export default svgUrl; 16 | export { svgComponent as ReactComponent } 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/datepicker.utils.ts: -------------------------------------------------------------------------------- 1 | import { startOfMonth, startOfWeek, isValid, 2 | endOfMonth, addDays, format as date_format, parse } from "date-fns"; 3 | import {chunk, get, isString, isNaN} from 'lodash' 4 | 5 | import { DayListShape, DatePickerOutPut, MainDate, 6 | defaultConfigs } from "../interfaces/datepicker.interfaces"; 7 | 8 | 9 | export const _type_safe_isValidDate = (time:any):time is Date => { 10 | return isValid(time) 11 | } 12 | 13 | export const _is_number = (num: number | undefined | null): num is number => { 14 | // check if num is number ( even 0 ); return false only if its undefined / null 15 | return !isNaN(Number(num)) 16 | } 17 | 18 | export const formatDate = (date:Date | MainDate | string | undefined, format=defaultConfigs.format) => { 19 | if(_type_safe_isValidDate(date)) { 20 | return {day : date.getDate(), month : date.getMonth(), year : date.getFullYear()} 21 | 22 | } else if (isString(date)) { 23 | const ip_date = parse(date, format, new Date()) 24 | if(!_type_safe_isValidDate(ip_date)) { 25 | console.log('Warning : You have passed invalid date in props !') 26 | const now = new Date() 27 | return {day : now.getDate(), month : now.getMonth(), year : now.getFullYear()} 28 | } 29 | return {day : ip_date.getDate(), month : ip_date.getMonth(), year : ip_date.getFullYear()} 30 | } else { 31 | // if(date.day < 0 || date.day > 31) { 32 | // throw `invalid date : ${date.day}` 33 | // } 34 | const now = defaultConfigs.date 35 | let ip_obj = { 36 | day : get(date , 'day'), 37 | month : get(date , 'month', now.getMonth() ), 38 | year : get(date , 'year', now.getFullYear() ) 39 | } 40 | // validate if date is correct else reset day 41 | // if day is 31 feb or something else 42 | if(_is_number(ip_obj.day)) { 43 | const test_date = new Date(ip_obj.year, ip_obj.month, ip_obj.day) 44 | if(test_date.getMonth() !== ip_obj.month) { 45 | ip_obj.day = 1 46 | } 47 | } 48 | return ip_obj 49 | } 50 | 51 | } 52 | 53 | const _WEEK_MAPPER = { 54 | 0 : 'S', 55 | 1 : 'M', 56 | 2 : 'T', 57 | 3 : 'W', 58 | 4 : 'T', 59 | 5 : 'F', 60 | 6 : 'S' 61 | } 62 | 63 | /** 64 | * 0 based circular array with given length that has inc and desc func 65 | * 66 | * @param {Number} value current value that needs to increment 67 | * @param {Number} length Length of the entire circular array 68 | */ 69 | export function incrementCircularData(value:number, length:number) { 70 | return (value + 1) % length 71 | } 72 | 73 | export const getWeekList = (weekStartsOn=defaultConfigs.weekStartsOn):string[] => { 74 | let res_week_list = [] 75 | let curr_pointer = weekStartsOn 76 | for (let index = 0; index < 7; index++) { 77 | res_week_list.push(_WEEK_MAPPER[curr_pointer]) 78 | curr_pointer = incrementCircularData(curr_pointer, 7) 79 | } 80 | return res_week_list 81 | } 82 | 83 | export const createRangeIndex = (day:number | undefined, month:number, year:number):number | undefined => { 84 | if(_is_number(day)) return (year*10000) + (month*100) + day; 85 | return undefined 86 | } 87 | 88 | export const parseRangeIndex = (rangeIndex:number):number[] => { 89 | const day = rangeIndex % 100 90 | const m_y_temp = Math.round(rangeIndex/100) 91 | const month = m_y_temp % 100 92 | const year = Math.round(m_y_temp / 100) 93 | 94 | return [ day, month, year] 95 | } 96 | 97 | export const getDayList = ( 98 | day:number | undefined, month:number, year:number, weekStartsOn:any = defaultConfigs.weekStartsOn 99 | ) : DayListShape[][] => { 100 | const curr_date = !!(day) ? new Date(year, month, day) : new Date(year, month) 101 | // start month date 102 | const sm_date = startOfMonth(curr_date) 103 | const sw_date = startOfWeek(sm_date, {weekStartsOn}) 104 | const em_date = endOfMonth(curr_date) 105 | 106 | const sm_day = sm_date.getDate() 107 | const em_day = em_date.getDate() 108 | // get month days in list 109 | let res_list = [] 110 | for(let index = sm_day; index <= em_day; index++) { 111 | res_list.push({ 112 | day : index, 113 | rangeIndex: createRangeIndex(index, month, year), 114 | curr_month : true, 115 | }) 116 | } 117 | // get padding week days of previous month 118 | let start_delta = sm_date.getDay() - weekStartsOn 119 | // for week start day greater than current day 120 | if(start_delta < 0) start_delta = 7 + start_delta 121 | 122 | const sw_day = sw_date.getDate() 123 | const sw_month = sw_date.getMonth() 124 | const sw_year = sw_date.getFullYear() 125 | for(let index = start_delta - 1; index >= 0; index--) { 126 | const this_day = sw_day + index 127 | // add at the front 128 | res_list.unshift({ 129 | day : this_day, 130 | rangeIndex: createRangeIndex(this_day, sw_month, sw_year), 131 | curr_month : false, 132 | }) 133 | } 134 | 135 | // calculate last padding 136 | const chunked_res_list = chunk(res_list, 7) 137 | const last_week_ind = (chunked_res_list.length - 1) 138 | const end_delta = 7 - chunked_res_list[last_week_ind].length 139 | 140 | const next_month_first_day = addDays(em_date, 1) 141 | // get next month 142 | const next_month = next_month_first_day.getMonth() 143 | // year may change for next month 144 | const next_year = next_month_first_day.getFullYear() 145 | for(let index = 1; index <= end_delta; index++) { 146 | chunked_res_list[last_week_ind].push({ 147 | day : index, 148 | rangeIndex: createRangeIndex(index, next_month, next_year), 149 | curr_month : false, 150 | }) 151 | } 152 | // @ts-ignore; rangeIndex will not be null for any createRangeIndex here 153 | return chunked_res_list 154 | } 155 | 156 | 157 | export const generateDatePickerOutput = ( 158 | day:number | undefined, month:number, year:number, 159 | format:string) : DatePickerOutPut => { 160 | 161 | let date; 162 | let formatted = '' 163 | if(!isNaN(Number(day))) { 164 | date = new Date(year, month, day) 165 | formatted = date_format(date, format) 166 | 167 | if(date.getDate() !== day) { 168 | // reset day as this month don't have that day 169 | day = 1 170 | date = new Date(year, month, day) 171 | } 172 | } 173 | 174 | return {date, formatted, day, month, year} 175 | } 176 | 177 | export const getInitialDateForInput = ( 178 | date : Date | MainDate | string | undefined, format : string=defaultConfigs.format 179 | ):string => { 180 | if(!date) return '' 181 | const {day, month, year} = formatDate(date, format) 182 | return generateDatePickerOutput(day, month, year, format).formatted 183 | } -------------------------------------------------------------------------------- /src/utils/datetimepicker.utils.ts: -------------------------------------------------------------------------------- 1 | import { format, parse } from "date-fns"; 2 | import { isString } from "lodash"; 3 | 4 | import { formatDate, _type_safe_isValidDate } from "./datepicker.utils"; 5 | import { createInputTime, generateTimeOutput } from "./timepicker.utils"; 6 | 7 | import { 8 | DateTimePickerProps, 9 | MainDateTimeObject, 10 | DateTimePickerOutPut, 11 | DateObject, 12 | defaultConfigs 13 | } from "../interfaces/datetimepicker.interfaces"; 14 | import { defaultConfigs as timeDefaultConfig, OutputTime } from "../interfaces/timepicker.interfaces"; 15 | import { DatePickerOutPut } from "../interfaces/datepicker.interfaces"; 16 | 17 | 18 | export const getInputDate = ( 19 | date_time_input: DateTimePickerProps["date"], dt_format=defaultConfigs.format 20 | ): MainDateTimeObject => { 21 | if(_type_safe_isValidDate(date_time_input)) { 22 | const time_str = format(date_time_input, timeDefaultConfig.format) 23 | const time_obj = createInputTime(time_str) 24 | return { 25 | day : date_time_input.getDate(), 26 | month : date_time_input.getMonth(), 27 | year : date_time_input.getFullYear(), 28 | ...time_obj 29 | } 30 | } else if (isString(date_time_input)) { 31 | const ip_date = parse(date_time_input, dt_format, new Date()) 32 | const time_str = format(ip_date, timeDefaultConfig.format) 33 | const time_obj = createInputTime(time_str) 34 | return { 35 | day : ip_date.getDate(), 36 | month : ip_date.getMonth(), 37 | year : ip_date.getFullYear(), 38 | ...time_obj 39 | } 40 | } 41 | 42 | let date_output = formatDate(date_time_input) 43 | const time_output = createInputTime(date_time_input) 44 | 45 | return {...date_output, ...time_output} 46 | } 47 | 48 | 49 | 50 | export const generateOutPut = ( 51 | curr_date:MainDateTimeObject, date_format:string, 52 | date? : DatePickerOutPut, time? : OutputTime, 53 | ):DateTimePickerOutPut => { // DateTimePickerOutPut 54 | let result, formatted = ''; 55 | 56 | if(date) { 57 | // date object given; passed from calender component 58 | const new_time_json = generateTimeOutput({...curr_date}, timeDefaultConfig.format) 59 | const current_date_obj = new Date( 60 | date.year, date.month, date.day, 61 | new_time_json.time.hour24, new_time_json.time.minute 62 | ) 63 | try { 64 | formatted = format(current_date_obj, date_format) 65 | } catch (error) { 66 | // pass; can not parse cause time maybe empty 67 | } 68 | 69 | result = { 70 | date : current_date_obj, 71 | day : date.day, month: date.month, year: date.year, 72 | ...new_time_json.time 73 | } 74 | } else if(time) { 75 | // time given; passed from clock component 76 | const current_date_obj = new Date( 77 | curr_date.year, curr_date.month, curr_date.day, 78 | time.time.hour24 || 0, time.time.minute || 0 79 | ) 80 | try { 81 | formatted = format(current_date_obj, date_format) 82 | } catch (error) { 83 | // pass; can not parse cause time maybe empty 84 | } 85 | 86 | result = { 87 | day : curr_date.day, month: curr_date.month, year: curr_date.year, 88 | date : current_date_obj, 89 | ...time.time 90 | } 91 | } else { 92 | // only current date given 93 | const new_time_json = generateTimeOutput({...curr_date}, timeDefaultConfig.format) 94 | const current_date_obj = new Date( 95 | curr_date.year, curr_date.month, curr_date.day, 96 | new_time_json.time.hour24, new_time_json.time.minute 97 | ) 98 | formatted = format(current_date_obj, date_format) 99 | 100 | result = { 101 | date : current_date_obj, 102 | day : curr_date.day, month: curr_date.month, year: curr_date.year, 103 | ...new_time_json.time 104 | } 105 | } 106 | 107 | return { 108 | date : result, 109 | formatted 110 | } 111 | } 112 | 113 | 114 | export const getInitialDateForInput = ( 115 | date:Date | DateObject | string , format:string=defaultConfigs.format 116 | ): string => { 117 | if(!date) return '' 118 | const curr_date = getInputDate(date, format) 119 | return generateOutPut(curr_date, format).formatted 120 | } -------------------------------------------------------------------------------- /src/utils/monthpicker.utils.ts: -------------------------------------------------------------------------------- 1 | import { get } from "lodash"; 2 | 3 | import { _type_safe_isValidDate } from "./datepicker.utils"; 4 | import { OutputShape } from "../interfaces/monthpicker.interfaces"; 5 | 6 | const MONTH_SUMMERY = { 7 | 0 : {M: "1", Mo: "1st", MM: "01", MMM: "Jan", MMMM: "January", MMMMM: "J"}, 8 | 1 : {M: "2", Mo: "2nd", MM: "02", MMM: "Feb", MMMM: "February", MMMMM: "F"}, 9 | 2 : {M: "3", Mo: "3rd", MM: "03", MMM: "Mar", MMMM: "March", MMMMM: "M"}, 10 | 3 : {M: "4", Mo: "4th", MM: "04", MMM: "Apr", MMMM: "April", MMMMM: "A"}, 11 | 4 : {M: "5", Mo: "5th", MM: "05", MMM: "May", MMMM: "May", MMMMM: "M"}, 12 | 5 : {M: "6", Mo: "6th", MM: "06", MMM: "Jun", MMMM: "June", MMMMM: "J"}, 13 | 6 : {M: "7", Mo: "7th", MM: "07", MMM: "Jul", MMMM: "July", MMMMM: "J"}, 14 | 7 : {M: "8", Mo: "8th", MM: "08", MMM: "Aug", MMMM: "August", MMMMM: "A"}, 15 | 8 : {M: "9", Mo: "9th", MM: "09", MMM: "Sep", MMMM: "September", MMMMM: "S"}, 16 | 9 : {M: "10", Mo: "10th", MM: "10", MMM: "Oct", MMMM: "October", MMMMM: "O"}, 17 | 10 : {M: "11", Mo: "11th", MM: "11", MMM: "Nov", MMMM: "November", MMMMM: "N"}, 18 | 11 : {M: "12", Mo: "12th", MM: "12", MMM: "Dec", MMMM: "December", MMMMM: "D"}, 19 | } 20 | 21 | export const formatMonth = (month:number, str_format:string = 'MMM'):string => { 22 | return MONTH_SUMMERY[month][str_format] 23 | } 24 | 25 | export const getMonthAndYear = (time:OutputShape | Date): OutputShape => { 26 | 27 | if(_type_safe_isValidDate(time)) { 28 | // time is a date object 29 | return {month: time.getMonth(), year: time.getFullYear()} 30 | } else { 31 | // time is a month object 32 | const now = new Date() 33 | return { 34 | month : get(time , 'month', now.getMonth() ), 35 | year : get(time , 'year', now.getFullYear() ) 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/utils/style.theme.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentTheme } from '../interfaces/style.interfaces' 2 | 3 | 4 | const LIGHT_THEME_COLORS:ComponentTheme = { 5 | primary_color : 'white', 6 | primary_font_color : 'rgba(0, 0, 0, 0.57)', 7 | light_font_color : 'rgba(0,0,0, 0.35)', 8 | 9 | secondary_color: '#efefef', 10 | 11 | primary_highlight_color: '#88b04b', 12 | secondary_highlight_color: '#00aab2', 13 | } 14 | 15 | const DARK_THEME_COLORS:ComponentTheme = { 16 | primary_color : '#36465D', 17 | primary_font_color : '#8897b9', 18 | light_font_color : '#8897b9', 19 | 20 | secondary_color: '#2b303b', 21 | 22 | primary_highlight_color: '#CDB274', 23 | secondary_highlight_color: 'rgba(165,201,213,.61)', 24 | } 25 | 26 | const COLORFUL_THEME_COLORS:ComponentTheme = { 27 | primary_color : 'ghostwhite', 28 | primary_font_color : 'SlateGray', 29 | light_font_color : 'grey', 30 | 31 | secondary_color: '#ffd180', 32 | 33 | primary_highlight_color: '#88b999', 34 | secondary_highlight_color: '#8897b9', 35 | } 36 | 37 | export const getThemeColors = (theme = 'light'):ComponentTheme => { 38 | switch (theme) { 39 | case 'light': 40 | return {...LIGHT_THEME_COLORS} 41 | 42 | case 'dark': 43 | return {...DARK_THEME_COLORS} 44 | 45 | case 'colorful': 46 | return {...COLORFUL_THEME_COLORS} 47 | 48 | default: // default theme light 49 | return {...LIGHT_THEME_COLORS}; 50 | } 51 | } -------------------------------------------------------------------------------- /src/utils/style.utils.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { assign } from 'lodash' 3 | import { getThemeColors } from './style.theme' 4 | // TypeScript imports 5 | import { ComponentTheme } from '../interfaces/style.interfaces' 6 | import { 7 | TimePickerInputProps, TimePickerProps 8 | } from '../interfaces/timepicker.interfaces' 9 | import { MonthPickerProps } from '../interfaces/monthpicker.interfaces' 10 | import { 11 | DatePickerInputProps, DatePickerProps 12 | } from '../interfaces/datepicker.interfaces' 13 | import { 14 | DateTimePickerInputProps, 15 | DateTimePickerProps 16 | } from '../interfaces/datetimepicker.interfaces' 17 | import { 18 | DateRangePickerInputProps, DateRangePickerProps, 19 | RangePickerInputProps, RangePickerProps 20 | } from '../interfaces/rangepicker.interfaces' 21 | 22 | 23 | type WrapperProps = 24 | TimePickerInputProps | 25 | TimePickerProps | 26 | MonthPickerProps | 27 | DatePickerProps | 28 | DatePickerInputProps | 29 | DateTimePickerProps | 30 | DateTimePickerInputProps | 31 | RangePickerProps | 32 | RangePickerInputProps | 33 | DateRangePickerProps | 34 | DateRangePickerInputProps 35 | 36 | /** 37 | * Wrapper component to all exported library components 38 | * 39 | * Merge theme colors with prop colors or default colors 40 | * and create colors object to be used by all component 41 | */ 42 | const StyleWrapper =

( 43 | WrappedComponent: React.ComponentType

44 | ):React.FC

=> ({ 45 | colors, theme, ...otherProps 46 | }) => { 47 | // override theme colors provided by props 48 | let themeColors = getThemeColors(theme) 49 | themeColors = assign(themeColors, colors) 50 | // pass-on other props with new colors 51 | const mergeProps = {...otherProps, colors: themeColors} 52 | 53 | return 54 | } 55 | 56 | export default StyleWrapper -------------------------------------------------------------------------------- /src/utils/timepicker.utils.ts: -------------------------------------------------------------------------------- 1 | import { isObject, isString, isUndefined, isNull, trim, upperCase } from "lodash"; 2 | import { format } from "date-fns"; 3 | 4 | 5 | import { TimePickerProps, MainTime, OutputTime } from "../interfaces/timepicker.interfaces"; 6 | 7 | 8 | export const createInputTime = (input_time : TimePickerProps["time"]):MainTime => { 9 | // default values 10 | let res_hour, res_minute, res_meridiem = 'AM'; 11 | 12 | 13 | if(isObject(input_time)) { 14 | const {hour, hour24, minute, meridiem} = input_time 15 | if( (isUndefined(hour) || isNull(hour)) && 16 | (isUndefined(hour24) || isNull(hour24)) ){ 17 | res_meridiem = meridiem ? meridiem : res_meridiem 18 | } 19 | // input has hour | hour24 20 | else if(isUndefined(hour)) { 21 | // 24 hour format given 22 | const modulo = Number(hour24) % 12 23 | res_hour = modulo === 0 ? 12 : modulo 24 | res_minute = Number(minute) 25 | res_meridiem = Number(hour24) >= 12 ? 'PM' : 'AM' 26 | } 27 | else { 28 | // 12 hour format given 29 | res_hour = Number(hour) === 0 ? 12 : Number(hour) 30 | res_minute = Number(minute) 31 | res_meridiem = meridiem ? meridiem : res_meridiem 32 | } 33 | } else if (isString(input_time)) { 34 | if(input_time.includes("M") || input_time.includes("m")) { 35 | // 12 hrs format 36 | const [hhmm, meridiem] = trim(input_time).split(" ") 37 | const [hour, minute] = trim(hhmm).split(":") 38 | 39 | res_hour = Number(hour) === 0 ? 12 : Number(hour) 40 | res_minute = Number(minute) 41 | if(!!meridiem) { 42 | // meridiem can be a.m. | AM | am | a; result must be AM / PM 43 | // remove all space and '.' 44 | res_meridiem = meridiem.replace(/[^a-zA-Z]/g, "") 45 | // all to capital , A | AM 46 | res_meridiem = upperCase(res_meridiem) 47 | // handle single A case 48 | res_meridiem = res_meridiem.includes("A") ? "AM" : "PM" 49 | } 50 | } 51 | else { 52 | // 24 hrs format 53 | const [hour24, minute] = trim(input_time).split(":") 54 | // handle incorrect string; where hour24 is undefined | null | NaN 55 | if(hour24) { 56 | const modulo = Number(hour24) % 12 57 | res_hour = modulo === 0 ? 12 : modulo 58 | res_minute = Number(minute) 59 | res_meridiem = Number(hour24) >= 12 ? 'PM' : 'AM' 60 | } 61 | } 62 | } 63 | 64 | return { 65 | hour : res_hour, minute : res_minute, meridiem : res_meridiem 66 | } 67 | } 68 | 69 | export const generateTimeOutput = ( 70 | {hour, minute, meridiem} : MainTime, 71 | time_format:string 72 | ):OutputTime => { 73 | 74 | let hour24 = hour 75 | // create hour24 76 | if(!hour) { 77 | return {formatted: '', time: {hour, hour24, minute, meridiem}} 78 | } 79 | if(meridiem === 'PM') { 80 | // 12 PM is 12 hrs 81 | hour24 = (hour === 12) ? 12 : hour + 12 82 | } 83 | else { 84 | // 12 AM is 00 hrs 85 | if(hour === 12) hour24 = 0 86 | } 87 | let time = {hour, minute, meridiem, hour24} 88 | 89 | let formatted:string 90 | try { 91 | // create time formatted string 92 | let now = new Date() 93 | // @ts-ignore 94 | now.setHours(hour24) 95 | // @ts-ignore -- format bellow will raise err if this is wrong 96 | now.setMinutes(minute) 97 | formatted = format(now, time_format) 98 | } catch (error) { 99 | formatted = '' 100 | } 101 | 102 | return { 103 | formatted, 104 | time 105 | } 106 | } -------------------------------------------------------------------------------- /src/utils/useOutsideAlerter.hook.ts: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | 3 | 4 | export const useOutsideAlerter = (wrapperRef:React.RefObject, handlerShow: Function) => { 5 | 6 | useEffect(() => { 7 | /** 8 | * Alert if clicked on outside of element 9 | */ 10 | const handleClickOutside = (event: any) => { 11 | 12 | if (wrapperRef.current) { 13 | // check click outside of picker, and picker open 14 | if(!wrapperRef.current.contains(event.target)) { 15 | handlerShow(false) 16 | return 17 | } 18 | } 19 | } 20 | 21 | // Bind the event listener 22 | document.addEventListener("mousedown", handleClickOutside); 23 | 24 | return () => { 25 | // Unbind the event listener on clean up 26 | document.removeEventListener("mousedown", handleClickOutside); 27 | }; 28 | }, [wrapperRef]); 29 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "module": "esnext", 5 | "lib": ["dom", "esnext"], 6 | "moduleResolution": "node", 7 | "jsx": "react", 8 | "sourceMap": true, 9 | "declaration": true, 10 | "esModuleInterop": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noImplicitAny": true, 14 | "strictNullChecks": true, 15 | "suppressImplicitAnyIndexErrors": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "allowSyntheticDefaultImports": true 19 | }, 20 | "include": ["src"], 21 | "exclude": ["node_modules", "dist", "example"] 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } --------------------------------------------------------------------------------