├── .gitignore ├── .idea ├── $CACHE_FILE$ ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml ├── modules.xml ├── react-horizontal-datepicker.iml └── vcs.xml ├── LICENSE ├── README.md ├── dist ├── components │ ├── DatePicker.js │ ├── DatePicker.module.css │ ├── DateView.js │ └── MonthView.js └── global │ └── helpers │ └── hexToRgb.js ├── package-lock.json ├── package.json ├── public └── index.html ├── src ├── App.css ├── App.js ├── components │ ├── DatePicker.js │ ├── DatePicker.module.css │ ├── DateView.js │ └── MonthView.js ├── global │ └── helpers │ │ └── hexToRgb.js ├── index.css └── index.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | package-lock.json 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | /.idea/ 25 | /.idea/workspace.xml 26 | -------------------------------------------------------------------------------- /.idea/$CACHE_FILE$: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | CoffeeScript 12 | 13 | 14 | GeneralCoffeeScript 15 | 16 | 17 | GeneralJavaScript 18 | 19 | 20 | HTML 21 | 22 | 23 | JavaScript 24 | 25 | 26 | TypeScript 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/react-horizontal-datepicker.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Kushagra Agrawal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## react-horizontal-datepicker 2 | #### V2 with new logic and completely removing dependency on react-waypoint as well as leaner code which now uses css-modules 3 | 4 | A simple and lightweight easily style-able Side scrolling datepicker, built with ❤️ 5 | 6 | Bundle size of 469 Bytes Minified + Gzipped 7 | 8 | ![](https://user-images.githubusercontent.com/8018852/78461316-7faf3a80-76e5-11ea-919d-cbf600f29092.png) 9 | 10 | ### Installation 11 | 12 | Run `yarn add react-horizontal-datepicker` 13 | or 14 | Run `npm i react-horizontal-datepicker` 15 | 16 | ### Usage 17 | 18 | Import: 19 | 20 | `import DatePicker from "react-horizontal-datepicker";` 21 | 22 | and simply use the component as: 23 | 24 | ```javascript 25 | 26 | ``` 27 | 28 | example at the end 29 | 30 | #### Available Props are 31 | 32 | | Prop | Type | Default | Description | 33 | | ------------- |:-------:| :-------:| ----------- | 34 | | getSelectedDay | Function | | Function to get the selected Day | 35 | | endDate | Number| 90 | Number of days to render from current date | 36 | | selectDate | Date | | prop to send selected date manually or from another calendar component | 37 | | color | String | 'rgb(54, 105, 238)' | Set the primary color can be any color format in string | 38 | | labelFormat | String | 'MMMM yyyy' | Month label format - uses [date-fns format](https://date-fns.org/v1.30.1/docs/format) types | 39 | | marked | Object | | Marking targeted date with text below (Optional) | 40 | 41 | ### Example: 42 | 43 | https://codesandbox.io/s/vigilant-newton-gn0g7 44 | 45 | ```javascript 46 | function App() { 47 | 48 | const selectedDay = (val) =>{ 49 | console.log(val) 50 | }; 51 | 52 | return ( 53 | 59 | ); 60 | } 61 | ``` 62 | 63 | ### Using marked dates 64 | 65 | Example: 66 | 67 | ```javascript 68 | function App() { 69 | 70 | const selectedDay = (val) =>{ 71 | console.log(val) 72 | }; 73 | 74 | return ( 75 | 98 | ); 99 | } 100 | ``` 101 | 102 | 103 | ### Todo 104 | use react window for efficienc -------------------------------------------------------------------------------- /dist/components/DatePicker.js: -------------------------------------------------------------------------------- 1 | function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } 2 | 3 | /* eslint-disable react-hooks/exhaustive-deps */ 4 | import { addDays } from "date-fns"; 5 | import React from "react"; 6 | import hexToRgb from "../global/helpers/hexToRgb"; 7 | import styles from "./DatePicker.module.css"; 8 | import { DateView } from "./DateView"; 9 | import { MonthView } from './MonthView'; 10 | 11 | const DatePicker = props => { 12 | const next = event => { 13 | event.preventDefault(); 14 | const e = document.getElementById('container'); 15 | const width = e ? e.getBoundingClientRect().width : null; 16 | e.scrollLeft += width - 60; 17 | }; 18 | 19 | const prev = event => { 20 | event.preventDefault(); 21 | const e = document.getElementById('container'); 22 | const width = e ? e.getBoundingClientRect().width : null; 23 | e.scrollLeft -= width - 60; 24 | }; 25 | 26 | const primaryColor = props.color ? props.color.indexOf("rgb") > 0 ? props.color : hexToRgb(props.color) : 'rgb(54, 105, 238)'; 27 | const startDate = props.startDate || new Date(); 28 | const lastDate = addDays(startDate, props.days || 90); 29 | let buttonzIndex = { 30 | zIndex: 2 31 | }; 32 | let buttonStyle = { 33 | background: primaryColor 34 | }; 35 | let Component = DateView; 36 | 37 | if (props.type === "month") { 38 | buttonzIndex = { 39 | zIndex: 5 40 | }; 41 | Component = MonthView; 42 | buttonStyle = { 43 | background: primaryColor, 44 | marginBottom: "5px" 45 | }; 46 | } 47 | 48 | return /*#__PURE__*/React.createElement("div", { 49 | className: styles.container 50 | }, /*#__PURE__*/React.createElement("div", { 51 | className: styles.buttonWrapper, 52 | style: buttonzIndex 53 | }, /*#__PURE__*/React.createElement("button", { 54 | className: styles.button, 55 | style: buttonStyle, 56 | onClick: prev 57 | }, "<")), /*#__PURE__*/React.createElement(Component, _extends({}, props, { 58 | primaryColor: primaryColor, 59 | startDate: startDate, 60 | lastDate: lastDate 61 | })), /*#__PURE__*/React.createElement("div", { 62 | className: styles.buttonWrapper, 63 | style: buttonzIndex 64 | }, /*#__PURE__*/React.createElement("button", { 65 | className: styles.button, 66 | style: buttonStyle, 67 | onClick: next 68 | }, ">"))); 69 | }; 70 | 71 | export { DatePicker }; -------------------------------------------------------------------------------- /dist/components/DatePicker.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | width: 100%; 4 | background: inherit; 5 | } 6 | 7 | .buttonWrapper { 8 | display: flex; 9 | align-items: flex-end; 10 | background: inherit; 11 | } 12 | 13 | .button { 14 | border: none; 15 | text-decoration: none; 16 | cursor: pointer; 17 | border-radius: 50%; 18 | width: 40px; 19 | height: 40px; 20 | color: white; 21 | font-size: 20px; 22 | font-weight: bold; 23 | flex-shrink: 0; 24 | display: flex; 25 | align-items: center; 26 | justify-content: center; 27 | padding: 0; 28 | margin-bottom: 13px; 29 | } 30 | .dateListScrollable { 31 | display: flex; 32 | overflow-x: scroll; 33 | scrollbar-width: none; 34 | margin: 2px 0 2px -40px; 35 | -webkit-overflow-scrolling: touch; 36 | } 37 | 38 | .dateListScrollable::-webkit-scrollbar { 39 | -webkit-appearance: none; 40 | display: none; 41 | } 42 | 43 | .monthContainer { 44 | display: flex; 45 | flex-direction: column; 46 | cursor: pointer; 47 | padding: 2px; 48 | margin: 2px; 49 | } 50 | 51 | .monthYearLabel { 52 | align-self: flex-start; 53 | z-index: 3; 54 | font-size: 15px; 55 | font-weight: bold; 56 | position: sticky; 57 | top: 10px; 58 | left: 0; 59 | width: max-content; 60 | margin: 0 10px; 61 | } 62 | 63 | .dateDayItem{ 64 | display: flex; 65 | flex-direction: column; 66 | align-items: center; 67 | cursor: pointer; 68 | margin: 0 0 0 5px; 69 | width: 45px; 70 | height: 49px; 71 | flex-shrink: 0; 72 | } 73 | 74 | .daysContainer { 75 | display: flex; 76 | z-index: 1; 77 | margin-top: 10px; 78 | } 79 | 80 | .dayLabel { 81 | font-size: 12px; 82 | margin: 4px 0 0 0; 83 | } 84 | 85 | .dateLabel { 86 | font-size: 18px; 87 | } -------------------------------------------------------------------------------- /dist/components/DateView.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import React, { useEffect, useState } from "react"; 3 | import styles from "./DatePicker.module.css"; 4 | import { addDays, addMonths, differenceInMonths, format, isSameDay, lastDayOfMonth, startOfMonth } from "date-fns"; 5 | 6 | const DateView = ({ 7 | startDate, 8 | lastDate, 9 | selectDate, 10 | getSelectedDay, 11 | primaryColor, 12 | labelFormat 13 | }) => { 14 | const [selectedDate, setSelectedDate] = useState(null); 15 | const firstSection = { 16 | marginLeft: '40px' 17 | }; 18 | const selectedStyle = { 19 | fontWeight: "bold", 20 | width: "45px", 21 | height: "45px", 22 | borderRadius: "50%", 23 | border: `2px solid ${primaryColor}`, 24 | color: primaryColor 25 | }; 26 | const labelColor = { 27 | color: primaryColor 28 | }; 29 | 30 | const getStyles = day => { 31 | return isSameDay(day, selectedDate) ? selectedStyle : null; 32 | }; 33 | 34 | const getId = day => { 35 | return isSameDay(day, selectedDate) ? 'selected' : ""; 36 | }; 37 | 38 | const renderDays = () => { 39 | const dayFormat = "E"; 40 | const dateFormat = "d"; 41 | const months = []; 42 | let days = []; 43 | 44 | for (let i = 0; i <= differenceInMonths(lastDate, startDate); i++) { 45 | let start, end; 46 | const month = startOfMonth(addMonths(startDate, i)); 47 | start = i === 0 ? Number(format(startDate, dateFormat)) - 1 : 0; 48 | end = i === differenceInMonths(lastDate, startDate) ? Number(format(lastDate, "d")) : Number(format(lastDayOfMonth(month), "d")); 49 | 50 | for (let j = start; j < end; j++) { 51 | let currentDay = addDays(month, j); 52 | days.push( /*#__PURE__*/React.createElement("div", { 53 | id: `${getId(currentDay)}`, 54 | className: styles.dateDayItem, 55 | style: getStyles(currentDay), 56 | key: currentDay, 57 | onClick: () => onDateClick(currentDay) 58 | }, /*#__PURE__*/React.createElement("div", { 59 | className: styles.dayLabel 60 | }, format(currentDay, dayFormat)), /*#__PURE__*/React.createElement("div", { 61 | className: styles.dateLabel 62 | }, format(currentDay, dateFormat)))); 63 | } 64 | 65 | months.push( /*#__PURE__*/React.createElement("div", { 66 | className: styles.monthContainer, 67 | key: month 68 | }, /*#__PURE__*/React.createElement("span", { 69 | className: styles.monthYearLabel, 70 | style: labelColor 71 | }, format(month, labelFormat || "MMMM yyyy")), /*#__PURE__*/React.createElement("div", { 72 | className: styles.daysContainer, 73 | style: i === 0 ? firstSection : null 74 | }, days))); 75 | days = []; 76 | } 77 | 78 | return /*#__PURE__*/React.createElement("div", { 79 | id: "container", 80 | className: styles.dateListScrollable 81 | }, months); 82 | }; 83 | 84 | const onDateClick = day => { 85 | setSelectedDate(day); 86 | 87 | if (getSelectedDay) { 88 | getSelectedDay(day); 89 | } 90 | }; 91 | 92 | useEffect(() => { 93 | if (getSelectedDay) { 94 | if (selectDate) { 95 | getSelectedDay(selectDate); 96 | } else { 97 | getSelectedDay(startDate); 98 | } 99 | } 100 | }, []); 101 | useEffect(() => { 102 | if (selectDate) { 103 | if (!isSameDay(selectedDate, selectDate)) { 104 | setSelectedDate(selectDate); 105 | setTimeout(() => { 106 | let view = document.getElementById('selected'); 107 | 108 | if (view) { 109 | view.scrollIntoView({ 110 | behavior: "smooth", 111 | inline: "center", 112 | block: "nearest" 113 | }); 114 | } 115 | }, 20); 116 | } 117 | } 118 | }, [selectDate]); 119 | return /*#__PURE__*/React.createElement(React.Fragment, null, renderDays()); 120 | }; 121 | 122 | export { DateView }; -------------------------------------------------------------------------------- /dist/components/MonthView.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import React, { useEffect, useState } from "react"; 3 | import styles from "./DatePicker.module.css"; 4 | import { addMonths, differenceInMonths, format, isSameDay, startOfMonth } from "date-fns"; 5 | 6 | const MonthView = ({ 7 | startDate, 8 | lastDate, 9 | selectDate, 10 | getSelectedDay, 11 | primaryColor, 12 | labelFormat 13 | }) => { 14 | const [selectedDate, setSelectedDate] = useState(null); 15 | const rgb = primaryColor.replace(/[^\d,]/g, '').split(','); 16 | const brightness = Math.round((parseInt(rgb[0]) * 299 + parseInt(rgb[1]) * 587 + parseInt(rgb[2]) * 114) / 1000); 17 | const textColour = brightness > 125 ? 'black' : 'white'; 18 | const selectedStyle = { 19 | borderRadius: "0.7rem", 20 | background: `${primaryColor}`, 21 | color: textColour 22 | }; 23 | 24 | const getStyles = day => { 25 | return isSameDay(day, selectedDate) ? selectedStyle : null; 26 | }; 27 | 28 | const getId = day => { 29 | return isSameDay(day, selectedDate) ? 'selected' : ""; 30 | }; 31 | 32 | const renderDays = () => { 33 | const months = []; 34 | 35 | for (let i = 0; i <= differenceInMonths(lastDate, startDate); i++) { 36 | const month = startOfMonth(addMonths(startDate, i)); 37 | months.push( /*#__PURE__*/React.createElement("div", { 38 | id: `${getId(month)}`, 39 | className: styles.monthContainer, 40 | key: month, 41 | style: getStyles(month), 42 | onClick: () => onDateClick(month) 43 | }, /*#__PURE__*/React.createElement("span", { 44 | className: styles.monthYearLabel 45 | }, format(month, labelFormat || "MMMM yyyy")))); 46 | } 47 | 48 | return /*#__PURE__*/React.createElement("div", { 49 | id: "container", 50 | className: styles.dateListScrollable 51 | }, months); 52 | }; 53 | 54 | const onDateClick = day => { 55 | setSelectedDate(day); 56 | 57 | if (getSelectedDay) { 58 | getSelectedDay(day); 59 | } 60 | }; 61 | 62 | useEffect(() => { 63 | if (getSelectedDay) { 64 | if (selectDate) { 65 | getSelectedDay(selectDate); 66 | } else { 67 | getSelectedDay(startDate); 68 | } 69 | } 70 | }, []); 71 | useEffect(() => { 72 | if (selectDate) { 73 | if (!isSameDay(selectedDate, selectDate)) { 74 | setSelectedDate(selectDate); 75 | setTimeout(() => { 76 | let view = document.getElementById('selected'); 77 | 78 | if (view) { 79 | view.scrollIntoView({ 80 | behavior: "smooth", 81 | inline: "center", 82 | block: "nearest" 83 | }); 84 | } 85 | }, 20); 86 | } 87 | } 88 | }, [selectDate]); 89 | return /*#__PURE__*/React.createElement(React.Fragment, null, renderDays()); 90 | }; 91 | 92 | export { MonthView }; -------------------------------------------------------------------------------- /dist/global/helpers/hexToRgb.js: -------------------------------------------------------------------------------- 1 | export default function hexToRgb(hex) { 2 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 3 | return result ? "rgb(" + parseInt(result[1], 16) + ',' + parseInt(result[2], 16) + ',' + parseInt(result[3], 16) + ")" : null; 4 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-horizontal-datepicker", 3 | "version": "2.0.3", 4 | "homepage": "https://github.com/kush-agra/react-horizontal-datepicker", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/kush-agra/react-horizontal-datepicker.git" 8 | }, 9 | "keywords": [ 10 | "date-picker", 11 | "horizontal-calendar", 12 | "horizontal-datepicker", 13 | "swipeable", 14 | "side-scrolling", 15 | "calendar-strip", 16 | "web" 17 | ], 18 | "private": false, 19 | "main": "./dist/components/DatePicker.js", 20 | "license": "MIT", 21 | "author": "Kushagra Agrawal", 22 | "babel": { 23 | "presets": [ 24 | "@babel/preset-react" 25 | ] 26 | }, 27 | "dependencies": { 28 | "date-fns": "^2.14.0" 29 | }, 30 | "peerDependencies": { 31 | "react": "^17.0.13", 32 | "react-dom": "^17.0.2", 33 | "react-scripts": "^5.0.1" 34 | }, 35 | "scripts": { 36 | "publish:npm": "rm -rf dist && mkdir dist && mkdir dist/components && mkdir dist/global && babel src/components -d dist/components --copy-files && babel src/global -d dist/global --copy-files", 37 | "start": "react-scripts start", 38 | "build": "react-scripts build", 39 | "test": "react-scripts test", 40 | "eject": "react-scripts eject" 41 | }, 42 | "eslintConfig": { 43 | "extends": "react-app" 44 | }, 45 | "browserslist": { 46 | "production": [ 47 | ">0.2%", 48 | "not dead", 49 | "not op_mini all" 50 | ], 51 | "development": [ 52 | "last 1 chrome version", 53 | "last 1 firefox version", 54 | "last 1 safari version" 55 | ] 56 | }, 57 | "devDependencies": { 58 | "@babel/cli": "^7.10.1", 59 | "@babel/preset-react": "^7.10.1", 60 | "react": "^16.13.1", 61 | "react-dom": "^16.13.1", 62 | "react-scripts": "^3.4.1" 63 | }, 64 | "bit": { 65 | "env": { 66 | "compiler": "bit.envs/compilers/react@1.0.13" 67 | }, 68 | "componentsDefaultDirectory": "components/{name}", 69 | "packageManager": "npm" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Strip 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | padding: 15px; 3 | background: white; 4 | } -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './App.css'; 3 | import { DatePicker } from "./components/DatePicker"; 4 | 5 | function App() { 6 | const selectedDay = (val) =>{ 7 | console.log(val) 8 | }; 9 | 10 | const startDate = new Date(2010, 0, 1); 11 | 12 | return ( 13 |
14 | 21 |
22 | ); 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /src/components/DatePicker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import { addDays } from "date-fns"; 3 | import React from "react"; 4 | import hexToRgb from "../global/helpers/hexToRgb"; 5 | import styles from "./DatePicker.module.css" 6 | import { DateView } from "./DateView"; 7 | import { MonthView } from './MonthView'; 8 | 9 | const DatePicker = (props) => { 10 | const next = (event) => { 11 | event.preventDefault(); 12 | const e = document.getElementById('container'); 13 | const width = e ? e.getBoundingClientRect().width : null; 14 | e.scrollLeft += width - 60; 15 | }; 16 | 17 | const prev = (event) => { 18 | event.preventDefault(); 19 | const e = document.getElementById('container'); 20 | const width = e ? e.getBoundingClientRect().width : null; 21 | e.scrollLeft -= width - 60; 22 | }; 23 | 24 | const primaryColor = props.color? (props.color.indexOf("rgb") > 0?props.color:hexToRgb(props.color)):'rgb(54, 105, 238)'; 25 | 26 | const startDate = props.startDate || new Date(); 27 | const lastDate = addDays(startDate, props.days || 90); 28 | 29 | let buttonzIndex = {zIndex: 2}; 30 | let buttonStyle = {background: primaryColor}; 31 | let Component = DateView; 32 | 33 | if (props.type === "month") { 34 | buttonzIndex = {zIndex: 5}; 35 | Component = MonthView; 36 | buttonStyle = {background: primaryColor, marginBottom: "5px"}; 37 | } 38 | 39 | return ( 40 |
41 |
42 | 43 |
44 | 45 |
46 | 47 |
48 |
49 | ) 50 | } 51 | 52 | export { DatePicker } -------------------------------------------------------------------------------- /src/components/DatePicker.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | width: 100%; 4 | background: inherit; 5 | } 6 | 7 | .buttonWrapper { 8 | display: flex; 9 | align-items: flex-end; 10 | background: inherit; 11 | } 12 | 13 | .button { 14 | border: none; 15 | text-decoration: none; 16 | cursor: pointer; 17 | border-radius: 50%; 18 | width: 40px; 19 | height: 40px; 20 | color: white; 21 | font-size: 20px; 22 | font-weight: bold; 23 | flex-shrink: 0; 24 | display: flex; 25 | align-items: center; 26 | justify-content: center; 27 | padding: 0; 28 | margin-bottom: 13px; 29 | } 30 | .dateListScrollable { 31 | display: flex; 32 | overflow-x: scroll; 33 | scrollbar-width: none; 34 | margin: 2px 0 2px -40px; 35 | -webkit-overflow-scrolling: touch; 36 | } 37 | 38 | .dateListScrollable::-webkit-scrollbar { 39 | -webkit-appearance: none; 40 | display: none; 41 | } 42 | 43 | .monthContainer { 44 | display: flex; 45 | flex-direction: column; 46 | cursor: pointer; 47 | padding: 2px; 48 | margin: 2px; 49 | } 50 | 51 | .monthYearLabel { 52 | align-self: flex-start; 53 | z-index: 3; 54 | font-size: 15px; 55 | font-weight: bold; 56 | position: sticky; 57 | top: 10px; 58 | left: 0; 59 | width: max-content; 60 | margin: 0 10px; 61 | } 62 | 63 | .dateDayItem { 64 | display: flex; 65 | flex-direction: column; 66 | align-items: center; 67 | cursor: pointer; 68 | margin: 0 0 0 5px; 69 | width: 45px; 70 | height: 49px; 71 | flex-shrink: 0; 72 | } 73 | 74 | .dateDayItemMarked { 75 | display: flex; 76 | flex-direction: column; 77 | align-items: center; 78 | cursor: pointer; 79 | margin: 0 0 0 5px; 80 | width: 45px; 81 | height: 70px; 82 | flex-shrink: 0; 83 | } 84 | 85 | .daysContainer { 86 | display: flex; 87 | z-index: 1; 88 | margin-top: 10px; 89 | } 90 | 91 | .dayLabel { 92 | font-size: 12px; 93 | margin: 4px 0 0 0; 94 | } 95 | 96 | .dateLabel { 97 | font-size: 18px; 98 | } 99 | 100 | .markedLabel { 101 | margin-top: 10px; 102 | } -------------------------------------------------------------------------------- /src/components/DateView.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import React, {useEffect, useState} from "react"; 3 | import styles from "./DatePicker.module.css" 4 | import { 5 | addDays, 6 | addMonths, 7 | differenceInMonths, 8 | format, 9 | isSameDay, 10 | lastDayOfMonth, 11 | startOfMonth 12 | } from "date-fns"; 13 | 14 | 15 | const DateView = ({startDate, lastDate, selectDate, getSelectedDay, primaryColor, labelFormat, marked}) => { 16 | const [selectedDate, setSelectedDate] = useState(null); 17 | const firstSection = {marginLeft: '40px'}; 18 | const selectedStyle = {fontWeight:"bold",width:"45px",height:"45px",borderRadius:"50%",border:`2px solid ${primaryColor}`,color:primaryColor}; 19 | const labelColor = {color: primaryColor}; 20 | const markedStyle = {color: "#8c3737", padding: "2px", fontSize: 12}; 21 | 22 | const getStyles = (day) => { 23 | return isSameDay(day, selectedDate)?selectedStyle:null; 24 | }; 25 | 26 | const getId = (day) => { 27 | return isSameDay(day, selectedDate)?'selected':""; 28 | }; 29 | 30 | const getMarked = (day) => { 31 | let markedRes = marked?.find(i => isSameDay(i.date, day)); 32 | if (markedRes) { 33 | if (!markedRes?.marked) { 34 | return; 35 | } 36 | 37 | return
38 | {markedRes.text} 39 |
; 40 | } 41 | 42 | return ""; 43 | }; 44 | 45 | const renderDays = () => { 46 | const dayFormat = "E"; 47 | const dateFormat = "d"; 48 | 49 | const months = []; 50 | let days = []; 51 | 52 | // const styleItemMarked = marked ? styles.dateDayItemMarked : styles.dateDayItem; 53 | 54 | for (let i = 0; i <= differenceInMonths(lastDate, startDate); i++) { 55 | let start, end; 56 | const month = startOfMonth(addMonths(startDate, i)); 57 | 58 | start = i === 0 ? Number(format(startDate, dateFormat)) - 1 : 0; 59 | end = i === differenceInMonths(lastDate, startDate) ? Number(format(lastDate, "d")) : Number(format(lastDayOfMonth(month), "d")); 60 | 61 | for (let j = start; j < end; j++) { 62 | let currentDay = addDays(month, j); 63 | 64 | days.push( 65 |
onDateClick(currentDay)} 70 | > 71 |
{format(currentDay, dayFormat)}
72 |
{format(currentDay, dateFormat)}
73 | {getMarked(currentDay)} 74 |
75 | ); 76 | } 77 | months.push( 78 |
81 | 82 | {format(month, labelFormat || "MMMM yyyy")} 83 | 84 |
85 | {days} 86 |
87 |
88 | ); 89 | days = []; 90 | 91 | } 92 | 93 | return
{months}
; 94 | } 95 | 96 | const onDateClick = day => { 97 | setSelectedDate(day); 98 | if (getSelectedDay) { 99 | getSelectedDay(day); 100 | } 101 | }; 102 | 103 | useEffect(() => { 104 | if (getSelectedDay) { 105 | if (selectDate) { 106 | getSelectedDay(selectDate); 107 | } else { 108 | getSelectedDay(startDate); 109 | } 110 | } 111 | }, []); 112 | 113 | useEffect(() => { 114 | if (selectDate) { 115 | if (!isSameDay(selectedDate, selectDate)) { 116 | setSelectedDate(selectDate); 117 | setTimeout(() => { 118 | let view = document.getElementById('selected'); 119 | if (view) { 120 | view.scrollIntoView({behavior: "smooth", inline: "center", block: "nearest"}); 121 | } 122 | }, 20); 123 | } 124 | } 125 | }, [selectDate]); 126 | 127 | return {renderDays()} 128 | } 129 | 130 | 131 | 132 | 133 | export { DateView } 134 | -------------------------------------------------------------------------------- /src/components/MonthView.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import React, {useEffect, useState} from "react"; 3 | import styles from "./DatePicker.module.css" 4 | import { 5 | addMonths, 6 | differenceInMonths, 7 | format, 8 | isSameDay, 9 | startOfMonth 10 | } from "date-fns"; 11 | 12 | 13 | const MonthView = ({startDate, lastDate, selectDate, getSelectedDay, primaryColor, labelFormat}) => { 14 | const [selectedDate, setSelectedDate] = useState(null); 15 | const rgb = primaryColor.replace(/[^\d,]/g, '').split(','); 16 | const brightness = Math.round(((parseInt(rgb[0]) * 299) + 17 | (parseInt(rgb[1]) * 587) + 18 | (parseInt(rgb[2]) * 114)) / 1000); 19 | const textColour = (brightness > 125) ? 'black' : 'white'; 20 | 21 | const selectedStyle = {borderRadius:"0.7rem",background:`${primaryColor}`, color: textColour}; 22 | 23 | const getStyles = (day) => { 24 | return isSameDay(day, selectedDate)?selectedStyle:null; 25 | }; 26 | 27 | const getId = (day) => { 28 | return isSameDay(day, selectedDate)?'selected':""; 29 | }; 30 | 31 | const renderDays = () => { 32 | 33 | const months = []; 34 | 35 | for (let i = 0; i <= differenceInMonths(lastDate, startDate); i++) { 36 | const month = startOfMonth(addMonths(startDate, i)); 37 | months.push( 38 |
onDateClick(month)} 43 | > 44 | 45 | {format(month, labelFormat || "MMMM yyyy")} 46 | 47 |
48 | ); 49 | } 50 | 51 | return
{months}
; 52 | } 53 | 54 | const onDateClick = day => { 55 | setSelectedDate(day); 56 | if (getSelectedDay) { 57 | getSelectedDay(day); 58 | } 59 | }; 60 | 61 | useEffect(() => { 62 | if (getSelectedDay) { 63 | if (selectDate) { 64 | getSelectedDay(selectDate); 65 | } else { 66 | getSelectedDay(startDate); 67 | } 68 | } 69 | }, []); 70 | 71 | useEffect(() => { 72 | if (selectDate) { 73 | if (!isSameDay(selectedDate, selectDate)) { 74 | setSelectedDate(selectDate); 75 | setTimeout(() => { 76 | let view = document.getElementById('selected'); 77 | if (view) { 78 | view.scrollIntoView({behavior: "smooth", inline: "center", block: "nearest"}); 79 | } 80 | }, 20); 81 | } 82 | } 83 | }, [selectDate]); 84 | 85 | return {renderDays()} 86 | } 87 | 88 | 89 | 90 | 91 | export { MonthView } -------------------------------------------------------------------------------- /src/global/helpers/hexToRgb.js: -------------------------------------------------------------------------------- 1 | export default function hexToRgb(hex) { 2 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 3 | return result ? "rgb(" 4 | + parseInt(result[1], 16) + ',' 5 | + parseInt(result[2], 16) + ',' 6 | + parseInt(result[3], 16) + ")" : null; 7 | } -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | 8 | --------------------------------------------------------------------------------