├── .babelrc ├── .eslintrc ├── .gitignore ├── .travis.yml ├── README.md ├── __tests__ └── index.test.js ├── example ├── .babelrc ├── index.html ├── package-lock.json ├── package.json ├── src │ ├── App.js │ └── index.js └── webpack.config.js ├── package-lock.json ├── package.json ├── prettier.config.js ├── rollup.config.js └── src ├── date.js ├── index.js └── types.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "presets": [["@babel/preset-env"]] 5 | } 6 | }, 7 | "presets": [ 8 | [ 9 | "@babel/preset-env", 10 | { 11 | "debug": false, 12 | "modules": false, 13 | "loose": true, 14 | "targets": { 15 | "browsers": "defaults" 16 | } 17 | } 18 | ], 19 | "@babel/preset-react" 20 | ], 21 | plugins: [ 22 | "@babel/plugin-transform-destructuring", 23 | "@babel/plugin-proposal-object-rest-spread" 24 | ] 25 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "jest": true, 5 | "browser": true 6 | }, 7 | "settings": { 8 | "import/resolver": { 9 | "node": { 10 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 11 | } 12 | }, 13 | "react": { 14 | "version": "detect" 15 | } 16 | }, 17 | "parserOptions": { 18 | "ecmaVersion": 6, 19 | "sourceType": "module", 20 | "allowImportExportEverywhere": true, 21 | "ecmaFeatures": { 22 | "jsx": true, 23 | "experimentalObjectRestSpread": true 24 | } 25 | }, 26 | "rules": { 27 | "no-console": "off", 28 | "semi": 2, 29 | "no-undef": 2, 30 | "no-undef-init": 2, 31 | "no-tabs": 2, 32 | "react/self-closing-comp": 2, 33 | "react/jsx-uses-react": 2, 34 | "react/jsx-uses-vars": 2, 35 | "react/no-typos": 2, 36 | "no-unused-vars": 2 37 | }, 38 | "plugins": [ 39 | "import", 40 | "react" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Osx 2 | .DS_Store 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | 9 | # sublime project 10 | *.sublime-project 11 | *.sublime-workspace 12 | 13 | # vscode project 14 | .vscode 15 | 16 | # idea project 17 | .idea 18 | 19 | # Runtime data 20 | pids 21 | *.pid 22 | *.seed 23 | 24 | # Directory for instrumented libs generated by jscoverage/JSCover 25 | lib-cov 26 | 27 | # Coverage directory used by tools like istanbul 28 | coverage 29 | .nyc_output/ 30 | 31 | # Compiled binary addons (http://nodejs.org/api/addons.html) 32 | build/Release 33 | 34 | # Dependency directory 35 | node_modules 36 | 37 | # Optional npm cache directory 38 | .npm 39 | 40 | # Build Data 41 | dist/* 42 | es/* 43 | lib/* 44 | 45 | # Docs 46 | docs/.next/* 47 | docs/export/* 48 | .firebase/ 49 | 50 | # Optional REPL history 51 | .node_repl_history -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "stable" 5 | 6 | script: 7 | - npm run -s lint 8 | - npm run -s test 9 | - npm run -s coveralls 10 | - npm run -s build 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # react-use-calendar 3 | 4 | Custom React Hook for implementing a calendar with events 5 | 6 | [![Build Status](https://travis-ci.org/gregnb/react-use-calendar.svg?branch=master)](https://travis-ci.org/gregnb/react-use-calendar) 7 | [![Coverage Status](https://coveralls.io/repos/github/gregnb/react-use-calendar/badge.svg?branch=master)](https://coveralls.io/github/gregnb/react-use-calendar?branch=master) 8 | [![dependencies Status](https://david-dm.org/gregnb/react-use-calendar/status.svg)](https://david-dm.org/gregnb/react-use-calendar) 9 | [![npm version](https://badge.fury.io/js/react-use-calendar.svg)](https://badge.fury.io/js/react-use-calendar) 10 | 11 |
12 | 13 |
14 | 15 | ## Installation 16 | 17 | ``` 18 | npm install react-use-calendar --save 19 | ``` 20 | 21 | ## Demo 22 | 23 | [![Edit react-use-calendar](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/y0jonx2xvz) 24 | 25 | 26 | ## Usage 27 | 28 | ```javascript 29 | import React from 'react'; 30 | import useCalendar from 'react-use-calendar'; 31 | 32 | function App() { 33 | 34 | const [state, actions] = useCalendar(null, { 35 | events: [ 36 | { 37 | startDate: new Date(2019, 1, 27), 38 | endDate: new Date(2019, 1, 27), 39 | note: 'Meeting with clients' 40 | }, 41 | { 42 | startDate: new Date(2019, 1, 22), 43 | endDate: new Date(2019, 1, 25), 44 | note: 'Vacation' 45 | } 46 | ] 47 | }); 48 | 49 | return ( 50 | 51 | 52 | 53 | 56 | 64 | 65 | 66 | {state.days.map(day => )} 67 | 68 | 69 | 70 | {state.weeks.map((week, index) => 71 | 72 | {week.map(day => 73 | 76 | )} 77 | 78 | )} 79 | 80 |
54 | {state.month} - {state.year} 55 | 57 | 60 | 63 |
{day}
74 | {day.dayOfMonth} 75 |
81 | ); 82 | 83 | } 84 | 85 | ``` 86 | 87 | ## API 88 | 89 | ### useCalendar 90 | 91 | ```js 92 | const [state, actions] = useCalendar(date, config); 93 | ``` 94 | 95 | ### Parameters 96 | 97 | | Field | Type | Description | 98 | | ------- | :--------: | ---------------------------------------------------------------------------------------- | 99 | | date | `date` | Initialize calendar with starting date | 100 | | config | `object` | Configuration | 101 | 102 | #### config 103 | 104 | | Key | Type | Description | 105 | | ------- | :--------: | ---------------------------------------------------------------------------------------- | 106 | | events | `array` | Calendar events as an array of objects. `[{ startDate: date, endDate: date, note: string }]` | 107 | | numOfWeeks | `number` | Number of calendar weeks. `default: 6` | 108 | | numOfDays | `number` | Number of days per week. `default: 7` | 109 | | rtl | `boolean` | Enable right-to-left | 110 | | locale | `object` | [date-fns](https://date-fns.org/) locale | 111 | 112 | ### Returns 113 | 114 | #### state 115 | 116 | | Key | Type | Description | 117 | | ------- | :--------: | ---------------------------------------------------------------------------------------- | 118 | | days | `array` | Short names for days of week `['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']` | 119 | | weeks | `array` | Weeks of calendar `[{ day: object }]` | 120 | | month | `string` | Current month in full month format | 121 | | year | `number` | Current year | 122 | 123 | ``` 124 | { 125 | date: date, 126 | dayIndex: number, 127 | dayOfMonth: number, 128 | dayOfWeek: string, 129 | dayOfYear: number, 130 | events: array, 131 | isToday: boolean, 132 | isSameMonth: boolean, 133 | isWeekend: boolean, 134 | weekIndex: number 135 | } 136 | ``` 137 | 138 | #### actions 139 | 140 | | Key | Type | Description | 141 | | ------- | :--------: | ---------------------------------------------------------------------------------------- | 142 | | setDate | `function` | Set current day for Calendar `function(today: date)` | 143 | | getNextMonth | `function` | Set calendar to next month | 144 | | getPrevMonth | `function` | Set calendar to previous month | 145 | | addEvent | `function` | Add an event to calendar. `function(event: { startDate: date, endDate: date, note: string })` | 146 | | removeEvent | `function` | Remove event from calendar `function(id: number)` | 147 | 148 | ## Localization 149 | 150 | ```javascript 151 | import React from 'react'; 152 | import useCalendar from 'react-use-calendar'; 153 | 154 | import ruLocale from 'date-fns/locale/ru'; 155 | 156 | function App() { 157 | 158 | const [state, actions] = useCalendar(null, { locale: ruLocale }); 159 | 160 | return ( 161 |
162 | ... 163 |
164 | ); 165 | } 166 | 167 | ``` 168 | 169 | ## License 170 | The files included in this repository are licensed under the MIT license. 171 | -------------------------------------------------------------------------------- /__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | import { renderHook, cleanup, act } from 'react-hooks-testing-library' 2 | import useCalendar from '../src'; 3 | import { setDate } from 'date-fns'; 4 | 5 | afterEach(cleanup); 6 | 7 | describe('useCalendar initialization', () => { 8 | test('initializing with no date or config', () => { 9 | const { result } = renderHook(() => useCalendar(null)); 10 | const [state] = result.current; 11 | expect(state.weeks.length).toBe(6); 12 | }); 13 | 14 | test('initializing with date and no config', () => { 15 | const { result } = renderHook(() => useCalendar(new Date())); 16 | const [state] = result.current; 17 | expect(state.weeks.length).toBe(6); 18 | }); 19 | 20 | test('initializing with date and config { rtl: true }', () => { 21 | const { result } = renderHook(() => useCalendar(new Date(2019, 1, 15), { rtl: true })); 22 | const [state] = result.current; 23 | expect(state.weeks[0][0]['dayOfMonth']).toBe(2); 24 | }); 25 | 26 | test('initializing with date and config { rtl: false }', () => { 27 | const { result } = renderHook(() => useCalendar(new Date(2019, 1, 15), { rtl: false })); 28 | const [state] = result.current; 29 | expect(state.weeks[0][0]['dayOfMonth']).toBe(27); 30 | }); 31 | 32 | test('initializing with date and config { numOfWeeks: 5, numOfDays: 5 }', () => { 33 | const { result } = renderHook(() => useCalendar(new Date(2019, 1, 15), { numOfWeeks: 5, numOfDays: 5 })); 34 | const [state] = result.current; 35 | expect(state.weeks.length).toBe(5); 36 | expect(state.weeks[0][0]['dayOfMonth']).toBe(27); 37 | expect(state.weeks[4][0]['dayOfMonth']).toBe(16); 38 | }); 39 | 40 | }); 41 | 42 | describe('useCalendar actions', () => { 43 | 44 | test('calling setDate action', () => { 45 | const { result } = renderHook(() => useCalendar(null)); 46 | let [_, actions] = result.current; 47 | 48 | act(() => actions.setDate(new Date(2019, 3, 15))); 49 | 50 | let [state] = result.current; 51 | const { weeks } = state; 52 | expect(weeks[0][0]['dayOfWeek']).toBe('Sunday'); 53 | expect(weeks[0][0]['dayOfMonth']).toBe(31); 54 | }); 55 | 56 | test('calling getPrevMonth action', () => { 57 | const { result } = renderHook(() => useCalendar(new Date(2019, 3, 15, 0, 0, 0, 0))); 58 | let [state, actions] = result.current; 59 | let weeks = state.weeks; 60 | 61 | expect(weeks[0][0]['dayOfWeek']).toBe('Sunday'); 62 | expect(weeks[0][0]['dayOfYear']).toBe(90); 63 | 64 | act(() => actions.getPrevMonth()); 65 | let [newState] = result.current; 66 | weeks = newState.weeks; 67 | 68 | expect(weeks[0][0]['dayOfWeek']).toBe('Sunday'); 69 | expect(weeks[0][0]['dayOfYear']).toBe(55); 70 | 71 | }); 72 | 73 | test('calling getNextMonth action', () => { 74 | const { result } = renderHook(() => useCalendar(new Date(2019, 3, 15, 0, 0, 0, 0))); 75 | let [state, actions] = result.current; 76 | let weeks = state.weeks; 77 | 78 | expect(weeks[0][0]['dayOfWeek']).toBe('Sunday'); 79 | expect(weeks[0][0]['dayOfYear']).toBe(90); 80 | 81 | act(() => actions.getNextMonth()); 82 | let [newState] = result.current; 83 | weeks = newState.weeks; 84 | 85 | expect(weeks[0][0]['dayOfWeek']).toBe('Sunday'); 86 | expect(weeks[0][0]['dayOfYear']).toBe(118); 87 | 88 | }); 89 | 90 | test('calling addEvent action', () => { 91 | const { result } = renderHook(() => useCalendar(new Date(2019, 3, 15, 0, 0, 0, 0))); 92 | let [state, actions] = result.current; 93 | let weeks = state.weeks; 94 | 95 | expect(weeks[0][0]['dayOfWeek']).toBe('Sunday'); 96 | expect(weeks[0][0]['dayOfYear']).toBe(90); 97 | 98 | const event = { startDate: new Date(2019, 3, 12), endDate: new Date(2019, 3, 12), note: "test" }; 99 | act(() => actions.addEvent(event)); 100 | let [newState] = result.current; 101 | weeks = newState.weeks; 102 | 103 | expect(weeks[1][5].events).toEqual([ { ...event, id: 0, isMultiDayEvent: false }]); 104 | 105 | }); 106 | 107 | test('calling addEvent action', () => { 108 | 109 | const eventOne = { startDate: new Date(2019, 3, 12), endDate: new Date(2019, 3, 12), note: "test #1" }; 110 | const eventTwo = { startDate: new Date(2019, 3, 22), endDate: new Date(2019, 3, 25), note: "test #2" }; 111 | 112 | const { result } = renderHook(() => useCalendar(new Date(2019, 3, 15, 0, 0, 0, 0), { 113 | events: [eventOne, eventTwo] 114 | })); 115 | let [state, actions] = result.current; 116 | let weeks = state.weeks; 117 | 118 | expect(weeks[0][0]['dayOfWeek']).toBe('Sunday'); 119 | expect(weeks[0][0]['dayOfYear']).toBe(90); 120 | expect(weeks[1][5].events).toEqual([ { ...eventOne, id: 0, isMultiDayEvent: false }]); 121 | 122 | // now remove event 123 | const eventId = weeks[3][2].events[0].id; 124 | act(() => actions.removeEvent(eventId)); 125 | let [newState] = result.current; 126 | weeks = newState.weeks; 127 | 128 | expect(weeks[3][2].events.length).toBe(0); 129 | 130 | }); 131 | 132 | }); 133 | -------------------------------------------------------------------------------- /example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": {}, 3 | "presets": [ 4 | [ 5 | "@babel/preset-env", 6 | { 7 | "debug": false, 8 | "modules": false, 9 | "loose": true, 10 | "targets": { 11 | "browsers": "defaults" 12 | } 13 | } 14 | ], 15 | "@babel/preset-react" 16 | ], 17 | plugins: [ 18 | "@babel/plugin-transform-destructuring", 19 | "@babel/plugin-proposal-object-rest-spread" 20 | ] 21 | } -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | React Hooks Example 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-detect-print", 3 | "version": "0.0.1", 4 | "description": "use-detect-print example", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "dev": "cross-env NODE_ENV=development webpack-dev-server -d --progress --colors" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/gregnb/use-detect-print.git" 12 | }, 13 | "author": "", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/gregnb/use-detect-print/issues" 17 | }, 18 | "homepage": "https://github.com/gregnb/use-detect-print#readme", 19 | "dependencies": { 20 | "lodash.merge": "^4.6.1", 21 | "react": "../node_modules/react", 22 | "react-dom": "../node_modules/react-dom" 23 | }, 24 | "devDependencies": { 25 | "@babel/cli": "^7.2.3", 26 | "@babel/core": "^7.2.2", 27 | "@babel/plugin-proposal-object-rest-spread": "^7.3.2", 28 | "@babel/plugin-transform-object-assign": "^7.2.0", 29 | "@babel/preset-env": "^7.3.1", 30 | "@babel/preset-react": "^7.0.0", 31 | "babel-eslint": "^10.0.1", 32 | "babel-loader": "^8.0.5", 33 | "babel-plugin-transform-class-properties": "^6.24.1", 34 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 35 | "cross-env": "^5.2.0", 36 | "eslint": "^5.13.0", 37 | "eslint-loader": "^2.1.1", 38 | "eslint-plugin-filenames": "^1.3.2", 39 | "eslint-plugin-import": "^2.16.0", 40 | "eslint-plugin-jsx-a11y": "^6.2.1", 41 | "eslint-plugin-react": "^7.12.4", 42 | "webpack": "^4.29.1", 43 | "webpack-cli": "^3.2.1", 44 | "webpack-dev-server": "^3.1.14" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import useCalendar from '../../src'; 3 | 4 | function App() { 5 | 6 | const [state, actions] = useCalendar(null, { 7 | events: [ 8 | { startDate: new Date(2019, 1, 27), endDate: new Date(2019, 1, 27), note: "note #1" }, 9 | { startDate: new Date(2019, 1, 22), endDate: new Date(2019, 1, 25), note: "note #2" }, 10 | ] 11 | }); 12 | 13 | return ( 14 | 15 | 16 | 17 | 20 | 28 | 29 | 30 | {state.days.map(day => )} 31 | 32 | 33 | 34 | {state.weeks.map((week, index) => 35 | 36 | {week.map(day => 37 | 40 | )} 41 | 42 | )} 43 | 44 |
18 | {state.month} - {state.year} 19 | 21 | 24 | 27 |
{day}
38 | {day.dayOfMonth} 39 |
45 | ); 46 | } 47 | 48 | export default App; 49 | -------------------------------------------------------------------------------- /example/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('app-root')); -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: { 6 | app: "./src/index.js" 7 | }, 8 | stats: "verbose", 9 | mode: "development", 10 | context: __dirname, 11 | output: { 12 | filename: 'bundle.js' 13 | }, 14 | devtool: 'source-map', 15 | devServer: { 16 | disableHostCheck: true, 17 | hot: true, 18 | inline: true, 19 | host: "0.0.0.0", 20 | port: 5080 21 | }, 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.jsx?$/, 26 | exclude: /(node_modules)/, 27 | use: [ 28 | 'babel-loader', 29 | 'eslint-loader' 30 | ] 31 | } 32 | ] 33 | }, 34 | plugins: [ 35 | new webpack.HotModuleReplacementPlugin(), 36 | new webpack.NamedModulesPlugin(), 37 | new webpack.DefinePlugin({ 38 | 'process.env': { 39 | NODE_ENV: JSON.stringify("development"), 40 | } 41 | }) 42 | ] 43 | }; 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-use-calendar", 3 | "version": "1.0.0", 4 | "description": "A custom React Hook for implementing a calendar with events", 5 | "source": "src/index.js", 6 | "main": "dist/index.js", 7 | "scripts": { 8 | "build": "rollup -c", 9 | "clean": "rimraf dist", 10 | "dev": "rollup -c -w", 11 | "lint": "eslint src", 12 | "prebuild": "npm run clean", 13 | "prerelease": "npm run lint && npm test && npm run build", 14 | "coveralls": "jest --coverage && cat ./coverage/lcov.info | coveralls", 15 | "test": "jest --verbose --coverage" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/gregnb/react-use-calendar.git" 20 | }, 21 | "keywords": [ 22 | "react-hooks", 23 | "hooks", 24 | "react", 25 | "calendar", 26 | "use-calendar" 27 | ], 28 | "author": "gregnb", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/gregnb/react-use-calendar/issues" 32 | }, 33 | "homepage": "https://github.com/gregnb/react-use-calendar#readme", 34 | "devDependencies": { 35 | "@babel/core": "^7.2.2", 36 | "@babel/plugin-proposal-object-rest-spread": "^7.3.2", 37 | "@babel/plugin-transform-destructuring": "^7.3.2", 38 | "@babel/preset-env": "^7.3.1", 39 | "@babel/preset-react": "^7.0.0", 40 | "babel-core": "^6.26.3", 41 | "babel-eslint": "^10.0.1", 42 | "babel-jest": "^23.6.0", 43 | "coveralls": "^3.0.3", 44 | "date-fns": "^2.0.0-alpha.27", 45 | "eslint": "^5.12.0", 46 | "eslint-plugin-import": "^2.16.0", 47 | "eslint-plugin-react": "^7.12.4", 48 | "eslint-plugin-react-hooks": "^1.3.0", 49 | "husky": "^1.3.1", 50 | "jest": "^24.1.0", 51 | "lint-staged": "^8.1.3", 52 | "prettier": "^1.16.4", 53 | "react": "^16.8.1", 54 | "react-dom": "^16.8.1", 55 | "react-hooks-testing-library": "^0.3.4", 56 | "rimraf": "^2.6.3", 57 | "rollup": "^1.1.2", 58 | "rollup-plugin-babel": "^4.3.2", 59 | "rollup-plugin-commonjs": "^9.2.0", 60 | "rollup-plugin-copy": "^0.2.3", 61 | "rollup-plugin-replace": "^2.1.0", 62 | "rollup-plugin-terser": "^4.0.4", 63 | "rollup-plugin-uglify": "^6.0.2", 64 | "rollup-plugin-uglify-es": "0.0.1" 65 | }, 66 | "files": [ 67 | "dist" 68 | ], 69 | "peerDependencies": { 70 | "react": "^16.8.0" 71 | }, 72 | "husky": { 73 | "hooks": { 74 | "pre-commit": "lint-staged" 75 | } 76 | }, 77 | "lint-staged": { 78 | "src/**/*.{js,jsx}": [ 79 | "prettier --config prettier.config.js --single-quote --write", 80 | "git add" 81 | ] 82 | }, 83 | "dependencies": { 84 | "date-fns": "^2.0.0-alpha.27" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 120, 3 | singleQuote: true, 4 | trailingComma: 'all', 5 | bracketSpacing: true, 6 | jsxBracketSameLine: true, 7 | parser: 'babylon', 8 | semi: true, 9 | }; 10 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import replace from 'rollup-plugin-replace'; 3 | import commonjs from 'rollup-plugin-commonjs'; 4 | //import uglify from 'rollup-plugin-uglify-es'; 5 | import { uglify } from 'rollup-plugin-uglify'; 6 | //import { terser } from "rollup-plugin-terser"; 7 | import copy from 'rollup-plugin-copy'; 8 | import pkg from './package.json' 9 | 10 | const external = Object.keys(pkg.peerDependencies || {}); 11 | const allExternal = [...external, Object.keys(pkg.dependencies || {})]; 12 | 13 | export default { 14 | input: 'src/index.js', 15 | external: allExternal, 16 | plugins: [ 17 | replace({ 18 | 'process.env.NODE_ENV': JSON.stringify('production'), 19 | }), 20 | commonjs({ 21 | include: [ 22 | 'node_modules/**', 23 | ], 24 | }), 25 | babel({ 26 | exclude: 'node_modules/**', // only transpile our source code 27 | }), 28 | uglify() 29 | ], 30 | output: [ 31 | { 32 | file: pkg.main, 33 | format: 'cjs', 34 | exports: 'named' 35 | } 36 | ] 37 | }; -------------------------------------------------------------------------------- /src/date.js: -------------------------------------------------------------------------------- 1 | import { 2 | addDays, 3 | eachDayOfInterval, 4 | endOfWeek, 5 | format, 6 | getDate, 7 | getDayOfYear, 8 | getMonth, 9 | getYear, 10 | isSameDay, 11 | isSameMonth, 12 | isWeekend, 13 | startOfWeek, 14 | } from 'date-fns'; 15 | 16 | function omitKey(data, key) { 17 | const { [key]: omit, ...other } = data; // eslint-disable-line no-unused-vars 18 | return other; 19 | } 20 | 21 | function transformDate(startDate, date, locale) { 22 | return { 23 | date, 24 | dayOfWeek: format(date, 'EEEE', { locale }), 25 | dayOfYear: getDayOfYear(date), 26 | dayOfMonth: getDate(date), 27 | isToday: isSameDay(new Date(), date), 28 | isSameMonth: isSameMonth(date, startDate), 29 | isWeekend: isWeekend(date), 30 | }; 31 | } 32 | 33 | function getDateKey(date) { 34 | return format(date, 'M-dd-yy'); 35 | } 36 | 37 | function getEventsForDate(date, events, eventsIndex) { 38 | const dateKey = getDateKey(date); 39 | const dateEvents = eventsIndex[dateKey]; 40 | 41 | return dateEvents 42 | ? dateEvents.map(id => ({ id, ...omitKey(events[id], 'dates'), isMultiDayEvent: events[id].dates.length > 1 })) 43 | : []; 44 | } 45 | 46 | function addEvent(newEvent, prevEvents) { 47 | const addedEvent = createEvents({ events: [newEvent] }, prevEvents); 48 | return { ...addedEvent, ...getDays(prevEvents.startDate, { ...prevEvents, ...addedEvent }) }; 49 | } 50 | 51 | function removeEvent(id, { startDate, events, options }) { 52 | const cleanEvents = [...events]; 53 | cleanEvents.splice(id, 1); 54 | 55 | const newEvents = createEvents({ events: cleanEvents }, { events: [], eventsIndex: {} }); 56 | const days = getDays(startDate, { options, ...newEvents }); 57 | 58 | return { 59 | ...newEvents, 60 | ...days, 61 | }; 62 | } 63 | 64 | function createEvents({ events }, prevEvents) { 65 | if (!events) return; 66 | 67 | let newEvents = [...prevEvents.events]; 68 | let newEventsIndex = { ...prevEvents.eventsIndex }; 69 | 70 | events.forEach(event => { 71 | let dates = []; 72 | 73 | eachDayOfInterval({ start: event.startDate, end: event.endDate }).forEach(day => { 74 | const dateKey = getDateKey(day); 75 | const index = newEvents.length; 76 | if (!events[dateKey]) newEventsIndex[dateKey] = []; 77 | newEventsIndex[dateKey].push(index); 78 | dates.push(dateKey); 79 | }); 80 | 81 | newEvents.push({ ...event, dates }); 82 | }); 83 | 84 | return { events: newEvents, eventsIndex: newEventsIndex }; 85 | } 86 | 87 | function getDays(date, { options, events, eventsIndex }) { 88 | let currentDate = startOfWeek(new Date(getYear(date), getMonth(date))); 89 | const weeks = Array.from({ length: options.numOfWeeks }).map((_, weekIndex) => { 90 | const days = Array.from({ length: options.numOfDays }).map((_, dayIndex) => { 91 | const day = transformDate(date, currentDate, options.locale); 92 | const dayEvents = getEventsForDate(currentDate, events, eventsIndex); 93 | currentDate = addDays(currentDate, 1); 94 | return { dayIndex, weekIndex, events: dayEvents, ...day }; 95 | }); 96 | return options.rtl ? days.reverse() : days; 97 | }); 98 | 99 | const days = eachDayOfInterval({ start: startOfWeek(currentDate), end: endOfWeek(currentDate) }).map(day => 100 | format(day, 'EEE', { locale: options.locale }), 101 | ); 102 | 103 | return { 104 | startDate: date, 105 | month: format(date, 'LLLL'), 106 | year: getYear(date), 107 | weeks, 108 | days, 109 | }; 110 | } 111 | 112 | export { getDays, addEvent, removeEvent, createEvents }; 113 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useReducer } from 'react'; 2 | import { addDays, format } from 'date-fns'; 3 | import { getDays, createEvents, addEvent, removeEvent } from './date'; 4 | import * as actionTypes from './types'; 5 | 6 | const initialState = { 7 | startDate: null, 8 | month: null, 9 | days: [], 10 | weeks: [], 11 | events: [], 12 | eventsIndex: {}, 13 | options: { 14 | numOfWeeks: 6, 15 | numOfDays: 7, 16 | rtl: false, 17 | locale: undefined, 18 | }, 19 | }; 20 | 21 | function dateDep(date) { 22 | return date ? format(date, 'yyyy-MM-dd') : null; 23 | } 24 | 25 | function reducer(state, action) { 26 | switch (action.type) { 27 | case actionTypes.SET_OPTIONS: 28 | return { ...state, ...createEvents(action.options, state), options: { ...state.options, ...action.options } }; 29 | case actionTypes.SET_DATE: 30 | return { ...state, ...getDays(action.date, state) }; 31 | case actionTypes.GET_NEXT_MONTH: 32 | return { ...state, ...getDays(addDays(state.startDate, 30), state) }; 33 | case actionTypes.GET_PREV_MONTH: 34 | return { ...state, ...getDays(addDays(state.startDate, -30), state) }; 35 | case actionTypes.ADD_EVENT: 36 | return { ...state, ...addEvent(action.event, state) }; 37 | case actionTypes.REMOVE_EVENT: 38 | return { ...state, ...removeEvent(action.id, state) }; 39 | default: 40 | return state; 41 | } 42 | } 43 | 44 | function initialize(date, options) { 45 | const events = { 46 | ...initialState, 47 | ...createEvents(options, initialState), 48 | options: { ...initialState.options, ...options }, 49 | }; 50 | return { 51 | ...events, 52 | ...getDays(date, events), 53 | }; 54 | } 55 | 56 | function useCalendar(date, options = {}) { 57 | const [state, dispatch] = useReducer(reducer, initialState, () => initialize(date || new Date(), options)); 58 | const { days, weeks, month, year } = state; 59 | 60 | useEffect(() => { 61 | return () => dispatch({ type: actionTypes.SET_DATE, date }); 62 | }, [dateDep(date)]); 63 | 64 | return [ 65 | { days, weeks, month, year }, 66 | { 67 | setDate: date => dispatch({ date, type: actionTypes.SET_DATE }), 68 | getNextMonth: () => dispatch({ type: actionTypes.GET_NEXT_MONTH }), 69 | getPrevMonth: () => dispatch({ type: actionTypes.GET_PREV_MONTH }), 70 | addEvent: event => dispatch({ event, type: actionTypes.ADD_EVENT }), 71 | removeEvent: id => dispatch({ id, type: actionTypes.REMOVE_EVENT }), 72 | }, 73 | ]; 74 | } 75 | 76 | export default useCalendar; 77 | -------------------------------------------------------------------------------- /src/types.js: -------------------------------------------------------------------------------- 1 | const GET_NEXT_MONTH = 'GET_NEXT_MONTH'; 2 | const GET_PREV_MONTH = 'GET_PREV_MONTH'; 3 | const SET_DATE = 'SET_DATE'; 4 | const SET_OPTIONS = 'SET_OPTIONS'; 5 | const ADD_EVENT = 'ADD_EVENT'; 6 | const REMOVE_EVENT = 'REMOVE_EVENT'; 7 | 8 | export { GET_NEXT_MONTH, GET_PREV_MONTH, SET_DATE, ADD_EVENT, REMOVE_EVENT, SET_OPTIONS }; 9 | --------------------------------------------------------------------------------