├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── app ├── Calendar │ ├── Calendar.js │ ├── DayWrapper.js │ ├── DefaultDay.js │ ├── MonthComponent.js │ ├── PositionsDay.js │ ├── __tests__ │ │ └── DayPicker.spec.js │ ├── helpers.js │ └── styles.css ├── Example │ ├── README.md │ ├── build │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.3fbecb66.css │ │ ├── main.3fbecb66.css.map │ │ ├── main.e994af19.js │ │ └── main.e994af19.js.map │ ├── favicon.ico │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── src │ │ └── index.js │ └── yarn.lock └── index.js ├── lib └── app.min.js ├── package.json ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "browsers": ["last 2 versions", "safari >= 7"] 6 | } 7 | }], 8 | ["es2015", {"modules": false}], 9 | "stage-0", 10 | "react" 11 | ], 12 | "env": { 13 | "test" : { 14 | "plugins":[ 15 | ["module-resolver", { 16 | "root": ["./app"] 17 | }], 18 | "transform-es2015-modules-commonjs", 19 | "transform-runtime" 20 | ] 21 | }, 22 | "development": { 23 | "plugins": ["transform-runtime"] 24 | }, 25 | "production": { 26 | "plugins": [ 27 | "transform-runtime" 28 | ] 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /bin/** 2 | /build/** 3 | /coverage/** 4 | /docs/** 5 | /jsdoc/** 6 | /templates/** 7 | /tests/bench/** 8 | /tests/fixtures/** 9 | /tests/performance/** 10 | /tmp/** 11 | test.js 12 | /webpack.config.babel.js 13 | /webpack.config.prod.js 14 | /undefinedclock-action-sync 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "es6": true, 5 | "jest": true, 6 | "browser": true 7 | }, 8 | "parserOptions": { 9 | "ecmaversion": 6, 10 | "sourceType": "module", 11 | "ecmaFeatures": { 12 | "jsx": true, 13 | "experimentalObjectRestSpread": true 14 | } 15 | }, 16 | "extends": ["eslint:recommended", "plugin:react/recommended", "standard"], 17 | "plugins": [ 18 | "react" 19 | ], 20 | "rules": { 21 | "indent": [0, "tab"], 22 | "no-tabs": 0, 23 | "no-console": [2, {"allow": ["warn", "error"]}], 24 | "comma-dangle" : [2, "always-multiline"], 25 | "semi": [2, "never"], 26 | "no-extra-semi": 2, 27 | "jsx-quotes": [2, "prefer-single"], 28 | "react/jsx-boolean-value": [2, "always"], 29 | "react/jsx-closing-bracket-location": [2, {"selfClosing": "after-props", "nonEmpty": "after-props"}], 30 | "react/jsx-curly-spacing": [2, "never", {"allowMultiline": false}], 31 | "react/jsx-max-props-per-line": [2, {"maximum": 3}], 32 | "react/jsx-no-literals": 2, 33 | "react/no-is-mounted": 0, 34 | "react/self-closing-comp": ["error", { 35 | "component": true, 36 | "html": false 37 | }], 38 | "react/sort-comp": 2 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | 3 | # Loging files 4 | logs 5 | *.log* 6 | .idea 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like jest 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # node-waf configuration 23 | .lock-wscript 24 | 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | build/Release 27 | 28 | # Dependency directory 29 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 30 | node_modules 31 | 32 | # yarn files 33 | .yarn-error.log 34 | 35 | #dist folder 36 | /dist 37 | /stage 38 | 39 | # Selenium 40 | /reports 41 | 42 | # IDEA/Webstorm project files 43 | .idea 44 | *.iml 45 | 46 | #VSCode metadata 47 | .vscode 48 | 49 | # Mac files 50 | .DS_Store 51 | 52 | #andy files 53 | circle_example.yml 54 | app/gateways/pouchdb/mockData.js 55 | build-bak 56 | undefinedclock-action-sync 57 | clients_local_db 58 | 59 | # Sublime project files 60 | *.sublime-project 61 | *.sublime-workspace 62 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | after_success: 5 | - npm run test 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 0.0.1 4 | - Initial commit. (Project as it is) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 SHIFTGIG 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Multiday Calendar [![Version Badge][npm-version-svg]][package-url] 2 | 3 | [![dependency status][deps-svg]][deps-url] 4 | [![dev dependency status][dev-deps-svg]][dev-deps-url] 5 | [![License][license-image]][license-url] 6 | [![Github file size][file-size-image]]() 7 | [![Downloads][downloads-image]][downloads-url] 8 | [![Build Status](https://travis-ci.org/andresmijares/react-calendar-multiday.svg?branch=master)](https://travis-ci.org/andresmijares/react-calendar-multiday) 9 | 10 | 11 | [![npm badge][npm-badge-png]][package-url] 12 | 13 | A minimalist React Calendar used for scheduling tools. 14 | 15 | If you want to play with it: 16 | 17 | [![Edit React Calendar Multiday](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/m4j347534p) 18 | 19 | ## Install 20 | Using npm or yarn. 21 | ```bash 22 | npm install react-calendar-multiday 23 | ``` 24 | 25 | ## Run Example 26 | ```bash 27 | cd app/Example 28 | npm install 29 | npm run start 30 | ``` 31 | 32 | ## Import 33 | As a default component: 34 | 35 | ```javascript 36 | import Calendar from 'react-calendar-multiday' 37 | ``` 38 | 39 | ## Dependencies 40 | It uses **moment** and **ramda** behind the scenes, we are working to remove the ramda on the next release in order to keep the bundle size as low as we can. 41 | 42 | ## API 43 | |name|type|required|default|description| 44 | |---|---|---|---|---| 45 | |**onChange**|Function|Yes |-|Callback fired once click on a date. Expose the current selections| 46 | |**onAddChannel**|Function|No|-|Callback fired once confirm channel selection. Expose channels and currentChannel| 47 | |**onReset**|Function|No|-|Callback fired once click Reset (Reset needs to be true)| 48 | |**channels**|Dict object|No|-|Store selected dates by channels| 49 | |**selected**|Array of Moment Instances|No| [] |Pass a selection of dates| 50 | |**year**|Moment Object - Year|No| Current |Select the default year| 51 | |**month**|Moment Object - Month|No| Current |Select the default month| 52 | |**currentChannel**|Number|No|-|Key of current channel| 53 | |**reset**|Boolean|No|false|Display a clear selection button| 54 | |**isMultiple**|Boolean|No|false|Define if you need one sigle date selection or multiple| 55 | |**DayComponent**|React Node|No|Default Day Component|Renders each day into the calendar| 56 | 57 | 58 | 59 | ### OnChange 60 | Returns an object with the **current** selection and the **selected** date(s). 61 | 62 | ```javascript 63 | { 64 | current: "2017-10-16T00:00:00-03:00", 65 | selected: ["2017-10-16T00:00:00-03:00"], 66 | channels: {0: ["2017-10-16T00:00:00-03:00"]} // if channels are available 67 | } 68 | ``` 69 | 70 | ### isMultiple 71 | The calendar will allow to select one single day or multiples, the only different with be return object on the **onChange** function, it can contains one or more dates. 72 | 73 | ```javascript 74 | { 75 | current: "2017-10-16T00:00:00-03:00", 76 | selected: ["2017-10-16T00:00:00-03:00", "2017-10-27T00:00:00-03:00", "2017-11-05T00:00:00-03:00"], 77 | channels: {0: ["2017-10-16T00:00:00-03:00", "2017-10-27T00:00:00-03:00", "2017-11-05T00:00:00-03:00"]} // if channels are available 78 | } 79 | ``` 80 | 81 | ### DayComponent 82 | A component that will render on each day, it receives several props where the most importants **label** and **isSelected**. 83 | * **Label**: String; Represents the day character. 84 | * **isSelected**: Booleam; True if the day is included in the selected array. 85 | * **isCurrentChannelSelected**: Booleam; True if the day is included in the selected array for the current channel. 86 | * **isToday**: Booleam; True if the value match today;s date. 87 | * **isInThePast**: Boolem; True if the value is before than today. 88 | 89 | Some other properties are expose like **selected** which is the selection array, we expose it cause we need that rule to manage inside business cases. 90 | 91 | If you require that past days not be selected, you need to stop the propagation of the click event yourself. 92 | 93 | Common use example: 94 | 95 | ```javascript 96 | const PositionDay = props => { 97 | const onClick = (e) => { 98 | if (props.isInthePast) { 99 | e.stopPropagation() 100 | } 101 | } 102 | return ( 103 |
106 | {props.label} 107 |
) 108 | } 109 | 110 | const getStyle = function ({date, isSelected}) { 111 | return `${isSelected ? 'o_selected-day' : ''} ${date.type}-day` 112 | } 113 | 114 | const getInline = ({isToday, isInThePast}) => ({ 115 | cursor: isInThePast ? 'not-allowed' : 'inherit', 116 | background: isToday 117 | ? 'rgba(141, 224, 229, 0.5)' 118 | : isInThePast ? '#e4e4e4' : 'inherit', 119 | color: isInThePast ? '#555555' : 'inherit', 120 | }) 121 | 122 | ``` 123 | 124 | As you can see, we leave the default implementation as open as possible, this way we can support all the use cases we have into our apps. 125 | 126 | ## Styles 127 | We expose a few css clases that you can edit, otherwise, you can use our ugly css default. 128 | 129 | * o_day_picker: the calendar container 130 | * i_day-picker-header: weeks headers 131 | * i_day-picker-body: calendar body 132 | * e_day-picker-buttons: prev and next month 133 | * i_day-picker-row: weeks row 134 | * i_day-picker-reset: reset button 135 | * i_day-picker-add-channel: add channel button 136 | * o_selected-current-channel-day: date selected for current channel 137 | 138 | ## License 139 | MIT 140 | 141 | [package-url]: https://npmjs.org/package/react-calendar-multiday 142 | [npm-version-svg]: http://versionbadg.es/andresmijares/react-calendar-multiday.svg 143 | [npm-badge-png]: https://nodei.co/npm/react-calendar-multiday.png?downloads=true&stars=true 144 | [deps-svg]: https://david-dm.org/andresmijares/react-calendar-multiday.svg 145 | [deps-url]: https://david-dm.org/andresmijares/react-calendar-multiday 146 | [dev-deps-svg]: https://david-dm.org/andresmijares/react-calendar-multiday.svg 147 | [dev-deps-url]: https://david-dm.org/andresmijares/react-calendar-multiday.svg#info=devDependencies 148 | [license-image]: http://img.shields.io/npm/l/react-calendar-multiday.svg 149 | [license-url]: LICENSE 150 | [downloads-image]: http://img.shields.io/npm/dm/react-calendar-multiday.svg 151 | [downloads-url]: http://npm-stat.com/charts.html?package=react-calendar-multiday 152 | [file-size-image]: https://img.shields.io/github/size/andresmijares/react-calendar-multiday/lib/app.min.js.svg 153 | -------------------------------------------------------------------------------- /app/Calendar/Calendar.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import MonthComponent from './MonthComponent' 4 | import moment from 'moment' 5 | import {extendMoment} from 'moment-range' 6 | import {reject, or, isEmpty, values, equals, cond, T, isNil} from 'ramda' 7 | import {normalize, incMonth, decMonth, setMonthDays, TYPE, getKey, getRealMonthAndYear} from './helpers' 8 | 9 | class Calendar extends Component { 10 | static defaultProps = { 11 | year: moment().get('year'), 12 | month: moment().get('month'), 13 | isMultiple: false, 14 | selected: [], 15 | channels: null, 16 | } 17 | 18 | constructor (props) { 19 | super(props) 20 | this.moment = extendMoment(moment) 21 | const {selected, channels} = this.props 22 | const defaultDate = this.moment(getRealMonthAndYear(this.props.month, this.props.year)) 23 | 24 | this.state = { 25 | defaultDate: defaultDate, 26 | selected: normalize(selected, this.moment), 27 | monthDays: setMonthDays(defaultDate, this.moment), 28 | channels, 29 | currentChannel: props.currentChannel || 0, 30 | } 31 | 32 | this.nextMonth = this.nextMonth.bind(this) 33 | this.prevMonth = this.prevMonth.bind(this) 34 | this.onClick = this.onClick.bind(this) 35 | this.retrieveSelected = this.retrieveSelected.bind(this) 36 | this.reset = this.reset.bind(this) 37 | this.addChannel = this.addChannel.bind(this) 38 | this.addOrRemoveDateToChannel = this.addOrRemoveDateToChannel.bind(this) 39 | } 40 | 41 | componentWillReceiveProps (nextProps) { 42 | if (this.props.selected.length !== nextProps.selected.length) { 43 | this.setState({selected: normalize(nextProps.selected, this.moment)}) 44 | } 45 | if (this.state.currentChannel !== nextProps.currentChannel) { 46 | this.setState({currentChannel: nextProps.currentChannel}) 47 | } 48 | if (!equals(this.props.channels, nextProps.channels)) { 49 | this.setState({channels: nextProps.channels}) 50 | } 51 | } 52 | 53 | nextMonth () { 54 | const defaultDate = incMonth(this.state.defaultDate) 55 | this.setState({ 56 | defaultDate, 57 | monthDays: setMonthDays(defaultDate, this.moment), 58 | }) 59 | } 60 | 61 | prevMonth () { 62 | const defaultDate = decMonth(this.state.defaultDate) 63 | this.setState({ 64 | defaultDate, 65 | monthDays: setMonthDays(defaultDate, this.moment), 66 | }) 67 | } 68 | 69 | reset () { 70 | const selected = this.state.selected 71 | const empty = Object.keys(selected).length 72 | this.setState({ 73 | selected: empty ? {} : normalize(selected, this.moment), 74 | channels: !isNil(this.props.channels) ? {} : null, 75 | currentChannel: 0, 76 | }, () => this.props.onReset ? this.props.onReset() : true 77 | ) 78 | } 79 | 80 | onClick (day) { 81 | const formattedDay = day.moment.format() 82 | const calendar = getKey(day.moment) 83 | const {selected, defaultDate, monthDays} = this.state 84 | //const {selected, defaultDate, monthDays} = this.state 85 | const updatedDefaultDate = cond([ 86 | [equals(TYPE.NEXT), () => incMonth(defaultDate)], 87 | [equals(TYPE.PREV), () => decMonth(defaultDate)], 88 | [T, () => defaultDate], 89 | ])(day.type) 90 | 91 | this.setState( 92 | { 93 | channels: !isNil(this.props.channels) ? this.addOrRemoveDateToChannel(day) : this.props.channels, 94 | selected: this.props.isMultiple ? { 95 | ...selected, 96 | [calendar]: isEmpty(or(selected[calendar], {})) ? day.moment : {}, 97 | } 98 | : [day.moment], // is Single day , 99 | defaultDate: updatedDefaultDate, 100 | monthDays: cond([ 101 | [equals(TYPE.NEXT), () => setMonthDays(updatedDefaultDate, this.moment)], 102 | [equals(TYPE.PREV), () => setMonthDays(updatedDefaultDate, this.moment)], 103 | [T, () => monthDays], 104 | ])(day.type), 105 | }, () => { 106 | /* 107 | Returns information for the listener function 108 | */ 109 | this.props.onChange({ 110 | channels: this.state.channels, 111 | current: formattedDay, 112 | selected: reject(isEmpty, values(this.state.selected)) 113 | .map(d => d.format()), 114 | }) 115 | } 116 | ) 117 | } 118 | 119 | retrieveSelected () { 120 | const nextMonth = this.moment(this.state.defaultDate).add(1, 'months') 121 | const prevMonth = this.moment(this.state.defaultDate).subtract(1, 'months').subtract(7, 'days') 122 | return reject(isEmpty, values(this.state.selected)) 123 | .filter(d => d.isBetween(prevMonth, nextMonth)) 124 | } 125 | 126 | addOrRemoveDateToChannel (day) { 127 | const {channels, currentChannel} = this.state 128 | if (!channels[currentChannel]) { 129 | channels[currentChannel] = [] 130 | } 131 | 132 | if (!channels[currentChannel].some(d => d.isSame(day.moment, 'day'))) { 133 | channels[currentChannel] = channels[currentChannel].concat([day.moment]) 134 | } else { 135 | channels[currentChannel] = channels[currentChannel].filter(d => !d.isSame(day.moment, 'day')) 136 | } 137 | return channels 138 | } 139 | 140 | addChannel () { 141 | const {channels, currentChannel} = this.state 142 | const max = Object.keys(channels).reduce((a, b) => Math.max(a, b), 0) 143 | 144 | if (currentChannel === max && channels[max].length === 0) { 145 | return false 146 | } 147 | 148 | this.setState({ 149 | channels, 150 | currentChannel: !isEmpty(channels[max]) ? Number(max) + 1 : max, 151 | }, () => this.props.onAddChannel ? this.props.onAddChannel({ 152 | channels: this.state.channels, 153 | currentChannel: this.state.currentChannel, 154 | }) : true 155 | ) 156 | } 157 | 158 | render () { 159 | const {defaultDate, monthDays, currentChannel, channels} = this.state 160 | const reset = this.props.reset ? this.reset : null 161 | const addChannel = !isNil(this.props.channels) ? this.addChannel : null 162 | return ( 163 | 176 | ) 177 | } 178 | } 179 | 180 | Calendar.propTypes = { 181 | channels: PropTypes.object, 182 | DayComponent: PropTypes.node, 183 | onChange: PropTypes.func.isRequired, 184 | onReset: PropTypes.func, 185 | onAddChannel: PropTypes.func, 186 | selected: PropTypes.array, 187 | month: PropTypes.number, 188 | year: PropTypes.number, 189 | currentChannel: PropTypes.number, 190 | reset: PropTypes.bool, 191 | isMultiple: PropTypes.bool, 192 | } 193 | 194 | export default Calendar 195 | -------------------------------------------------------------------------------- /app/Calendar/DayWrapper.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import DefaultDay from './DefaultDay' 4 | import {isNil, flatten} from 'ramda' 5 | 6 | const DayWrapper = (props) => { 7 | const nextProps = { 8 | ...props, 9 | isSelected: isSelected(props), 10 | isCurrentChannelSelected: isCurrentChannelSelected(props), 11 | } 12 | 13 | return ( 14 |
props.onClick(props.date)}> 17 | {props.children && React.cloneElement(props.children, {...nextProps}) || } 18 |
) 19 | } 20 | 21 | export const isSelected = ({date, selected, channels}) => 22 | !isNil(channels) ? 23 | flatten(Object.keys(channels).map(key => channels[key])).some(each => each.isSame(date.moment, 'day')) : 24 | selected.some(each => each.isSame(date.moment, 'day')) 25 | 26 | export const isCurrentChannelSelected = ({date, selected, channels, currentChannel}) => 27 | !isNil(channels) ? 28 | !isNil(channels[currentChannel]) && 29 | channels[currentChannel].some(each => each.isSame(date.moment, 'day')) : 30 | selected.some(each => each.isSame(date.moment, 'day')) 31 | 32 | 33 | DayWrapper.propTypes = { 34 | date: PropTypes.object, 35 | children: PropTypes.node, 36 | selected: PropTypes.array, 37 | onClick: PropTypes.func, 38 | channels: PropTypes.object, 39 | currentChannel: PropTypes.number, 40 | } 41 | 42 | export default DayWrapper 43 | -------------------------------------------------------------------------------- /app/Calendar/DefaultDay.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import moment from 'moment' 4 | 5 | const getInline = (today, before) => ({ 6 | cursor: before ? 'not-allowed' : 'inherit', 7 | background: today 8 | ? 'rgba(141, 224, 229, 0.5)' 9 | : before ? 'rgba(155, 155, 155, .2)' : 'inherit', 10 | }) 11 | 12 | const DefaultDayComponent = props => { 13 | const { label, date, isToday, isInThePast, isCurrentChannelSelected, isSelected } = props 14 | const disableDate = date.moment.isBefore(moment(), 'day') 15 | const onClick = (e) => { 16 | if (disableDate || (!isCurrentChannelSelected && isSelected)) { 17 | e.stopPropagation() 18 | } 19 | } 20 | 21 | return ( 22 |
26 | {label} 27 |
) 28 | } 29 | 30 | DefaultDayComponent.propTypes = { 31 | label: PropTypes.number, 32 | date: PropTypes.object, 33 | isToday: PropTypes.bool, 34 | isInThePast: PropTypes.bool, 35 | } 36 | 37 | export const getStyle = function ({date, isSelected, isCurrentChannelSelected}) { 38 | return `${isCurrentChannelSelected 39 | ? 'o_selected-current-channel-day' : isSelected ? 'o_selected-day' : ''} ${date.type}-day` 40 | } 41 | 42 | export default DefaultDayComponent 43 | -------------------------------------------------------------------------------- /app/Calendar/MonthComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import {splitEvery, isNil, isEmpty} from 'ramda' 4 | import DayWrapper from './DayWrapper' 5 | import moment from 'moment' 6 | 7 | const MonthComponent = props => { 8 | const { days, dayNames, selected, nextMonth, prevMonth, defaultDate, onClick, 9 | reset, DayComponent, addChannel, channels, currentChannel } = props 10 | const weeks = splitEvery(7, days) 11 | return ( 12 |
13 |
14 |
15 |
16 |
17 |
18 | {defaultDate.format('MMMM YYYY')} 19 |
20 |
21 |
22 |
23 |
24 |
25 | {dayNames.map(n => 26 |
{n}
27 | )} {/* we can pass this as props as well */} 28 |
29 |
30 | {weeks.map((w, index) => 31 |
32 | {w.map((d, i) => 33 | 43 | {DayComponent} 44 | 45 | )} 46 |
47 | )} 48 |
49 |
50 | { reset && 51 | 54 | } 55 | { addChannel && 56 | 60 | } 61 |
62 |
63 | ) 64 | } 65 | 66 | MonthComponent.propTypes = { 67 | days: PropTypes.array, 68 | dayNames: PropTypes.array, 69 | selected: PropTypes.array, 70 | onClick: PropTypes.func, 71 | nextMonth: PropTypes.func, 72 | prevMonth: PropTypes.func, 73 | reset: PropTypes.func, 74 | defaultDate: PropTypes.object, 75 | DayComponent: PropTypes.node, 76 | type: PropTypes.string, 77 | channels: PropTypes.object, 78 | currentChannel: PropTypes.number, 79 | } 80 | 81 | export default MonthComponent 82 | 83 | -------------------------------------------------------------------------------- /app/Calendar/PositionsDay.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import moment from 'moment' 4 | 5 | const PositionDay = props => { 6 | const { label, selected, date } = props 7 | const disableDate = date.moment.isBefore(moment(), 'day') 8 | const dayIsSelected = isSelected(date.moment, selected) 9 | const onClick = (e) => { 10 | if (disableDate) { 11 | e.stopPropagation() 12 | } 13 | } 14 | const disabledStyles = { 15 | cursor: 'not-allowed', 16 | background: '#e4e4e4', 17 | color: '#555555', 18 | } 19 | 20 | const disabledAndSelected = { 21 | cursor: 'not-allowed', 22 | background: '#4179a3', 23 | } 24 | return ( 25 |
32 | {label} 33 |
) 34 | } 35 | 36 | PositionDay.propTypes = { 37 | label: PropTypes.number, 38 | date: PropTypes.object, 39 | selected: PropTypes.array, 40 | } 41 | 42 | export const isSelected = (momentDate, selected) => selected.some(each => each.isSame(momentDate, 'day')) 43 | 44 | export const getStyle = function (day, selected) { 45 | return `${isSelected(day.moment, selected) ? 'o_selected-day' : ''} ${day.type}-day` 46 | } 47 | 48 | export default PositionDay 49 | -------------------------------------------------------------------------------- /app/Calendar/__tests__/DayPicker.spec.js: -------------------------------------------------------------------------------- 1 | // import React from 'react' 2 | // import Enzyme, {mount} from 'enzyme' 3 | // import Adapter from 'enzyme-adapter-react-16' 4 | // import Calendar from '../Calendar' 5 | // import DayWrapper from '../DayWrapper' 6 | // import toJson from 'enzyme-to-json' 7 | // import {equals, range, inc} from 'ramda' 8 | // import jsdom from 'jsdom' 9 | 10 | // Enzyme.configure({ adapter: new Adapter() }) 11 | // var exposedProperties = ['window', 'navigator', 'document'] 12 | 13 | // global.document = jsdom.jsdom('') 14 | // global.window = document.defaultView 15 | // Object.keys(document.defaultView).forEach((property) => { 16 | // if (typeof global[property] === 'undefined') { 17 | // exposedProperties.push(property) 18 | // global[property] = document.defaultView[property] 19 | // } 20 | // }) 21 | 22 | // global.navigator = { 23 | // userAgent: 'node.js', 24 | // } 25 | 26 | // // const dayPicker = mount() 27 | 28 | -------------------------------------------------------------------------------- /app/Calendar/helpers.js: -------------------------------------------------------------------------------- 1 | import {head, last, subtract, take, takeLast, cond, lte, T} from 'ramda' 2 | 3 | export const TYPE = { 4 | MONTH: 'month', 5 | NEXT: 'next-month', 6 | PREV: 'prev-month', 7 | } 8 | 9 | export const KEY_FORMAT = 'YYYY-MM-DD' 10 | 11 | export const setMonthDays = (month, moment) => { 12 | const nextMonthD = Array.from(getMonthRange(incMonth(moment(month)), moment).by('day')) 13 | const previousMonthD = Array.from(getMonthRange(decMonth(moment(month)), moment).by('day')) 14 | const monthDays = Array.from(getMonthRange(month, moment).by('day')).map(m => ({moment: m, type: TYPE.MONTH})) 15 | const weekIndexes = monthDays.map(d => d.moment.day()) 16 | const nextMonthDays = take(subtract(6, last(weekIndexes)), nextMonthD).map(m => ({moment: m, type: TYPE.NEXT})) 17 | const prevMonthDays = takeLast(head(weekIndexes), previousMonthD).map(m => ({moment: m, type: TYPE.PREV})) 18 | return prevMonthDays.concat(monthDays, nextMonthDays) 19 | } 20 | 21 | export const normalize = (selected, momentInstance) => { 22 | const normalizedDays = Object 23 | .keys(selected) 24 | .map(date => selected[date]) 25 | .reduce((prev, d) => { 26 | const moment = momentInstance(d) 27 | prev[getKey(moment)] = moment 28 | return prev 29 | }, {}) 30 | return normalizedDays 31 | } 32 | 33 | export const getMonthRange = (month, moment) => { 34 | const start = moment(month.startOf('month')) 35 | const end = month.endOf('month') 36 | return moment.range(start, end) 37 | } 38 | 39 | export const getKey = (moment) => moment.format(KEY_FORMAT) 40 | 41 | export const incMonth = (date) => date.add(1, 'months') 42 | 43 | export const decMonth = (date) => date.subtract(1, 'months') 44 | 45 | export const getRealMonthAndYear = (month, year) => [ 46 | cond([ 47 | [lte(12), () =>year + 1], 48 | [T, () => year], 49 | ])(month), 50 | cond([ 51 | [lte(12), () => month - 12], 52 | [T, () => month], 53 | ])(month), 54 | ] 55 | -------------------------------------------------------------------------------- /app/Calendar/styles.css: -------------------------------------------------------------------------------- 1 | .o_day-picker { 2 | color: #333333; 3 | } 4 | 5 | .o_day-picker .e_day-picker-buttons { 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | position: relative; 10 | background-color: white; 11 | } 12 | 13 | .o_day-picker .i_day-picker-title { 14 | padding: 10px; 15 | text-align: center; 16 | font-size: 1.1em; 17 | } 18 | 19 | .o_day-picker .e_day-picker-arrow-container { 20 | cursor: pointer; 21 | padding: 10px; 22 | } 23 | 24 | .o_day-picker .i_day-picker-arrow-left { 25 | width: 0; 26 | height: 0; 27 | border-top: 5px solid transparent; 28 | border-bottom: 5px solid transparent; 29 | border-right:5px solid black; 30 | } 31 | 32 | .o_day-picker .i_day-picker-arrow-right { 33 | width: 0; 34 | height: 0; 35 | border-top: 5px solid transparent; 36 | border-bottom: 5px solid transparent; 37 | border-left: 5px solid black; 38 | } 39 | 40 | 41 | .o_day-picker .i_day-picker-header { 42 | display: flex; 43 | background: #cccccc; 44 | color: white; 45 | } 46 | 47 | .o_day-picker .i_day-picker-header div { 48 | padding: 5px; 49 | width: 20%; 50 | height: 20%; 51 | text-align: center; 52 | font-size: 0.8em; 53 | } 54 | 55 | .o_day-picker .i_day-picker-body { 56 | cursor: pointer; 57 | background-color: white; 58 | } 59 | 60 | .o_day-picker .i_day-picker-row { 61 | display: flex; 62 | text-align: center; 63 | border: solid #eaecec; 64 | border-width: 0 0 0 1px; 65 | } 66 | 67 | .o_day-picker .i_day-picker-row > div { 68 | border: solid #cccccc; 69 | border-width: 0 1px 1px 0; 70 | width: 20%; 71 | } 72 | 73 | .o_day-picker .i_day-picker-row [class*="-day"] { 74 | padding: 5px; 75 | text-align: left; 76 | font-size: 0.7em; 77 | min-height: 4em; 78 | transition: .1s background linear; 79 | outline: none; 80 | } 81 | 82 | .o_day-picker .i_day-picker-row [class*="-day"]:hover { 83 | background-color: rgba(184, 233, 134, .4) !important; 84 | } 85 | 86 | .o_day-picker .i_day-picker-row .selected-day { 87 | color: white; 88 | background-color: #70bde2; 89 | } 90 | 91 | .o_day-picker .i_day-picker-row .prev-month-day { 92 | color: #b3b3b3; 93 | } 94 | 95 | .o_day-picker .i_day-picker-row .next-month-day { 96 | color: #b3b3b3; 97 | } 98 | 99 | .i_day-picker-actions button{ 100 | cursor: pointer; 101 | border-radius: 3px; 102 | margin-top: 25px; 103 | text-align: center; 104 | padding: .4em 1.5em; 105 | font-size: 1em; 106 | text-transform: uppercase; 107 | margin-right: 20px; 108 | min-width: 150px; 109 | outline: none; 110 | } 111 | .o_day-picker .i_day-picker-reset { 112 | border: 2px solid #8f8f8f; 113 | color: #8f8f8f; 114 | } 115 | 116 | .o_day-picker .i_day-picker-reset:hover { 117 | background: #8f8f8f; 118 | color: #fff; 119 | } 120 | 121 | .o_day-picker .i_day-picker-add-channel { 122 | border: 2px solid #38b0ed; 123 | color: #38b0ed; 124 | } 125 | 126 | .o_day-picker .i_day-picker-add-channel:disabled { 127 | opacity: .2 !important; 128 | } 129 | 130 | .o_day-picker .i_day-picker-add-channel:hover { 131 | background: #38b0ed; 132 | color: #fff; 133 | } 134 | 135 | .o_day-picker .i_day-picker-row .o_selected-day, 136 | .o_day-picker .i_day-picker-row .o_selected-day:hover, 137 | .o_day-picker .i_day-picker-row [class*="-day"][disabled]:hover{ 138 | background: rgba(155, 155, 155, .2) !important; 139 | color: #000 !important; 140 | cursor: not-allowed !important; 141 | } 142 | 143 | .o_selected-current-channel-day { 144 | background: #b8e986 !important; 145 | color: #000 !important; 146 | } 147 | 148 | 149 | -------------------------------------------------------------------------------- /app/Example/README.md: -------------------------------------------------------------------------------- 1 | React Calendar Multiday Component Example 2 | ============================== 3 | 4 | This is a very simple — yet runnable app — showing how to use React Datepicker component. 5 | 6 | ## Running Example 7 | 8 | **In the project directory, run:** 9 | ``` 10 | $ npm install 11 | $ npm start 12 | ``` 13 | **Open [http://localhost:3000](http://localhost:3000) to view it in the browser.** 14 | -------------------------------------------------------------------------------- /app/Example/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andresmijares/react-calendar-multiday/511b101c1d5a183a20562529929654ffde0d7e55/app/Example/build/favicon.ico -------------------------------------------------------------------------------- /app/Example/build/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>React App
-------------------------------------------------------------------------------- /app/Example/build/main.3fbecb66.css: -------------------------------------------------------------------------------- 1 | .o_day-picker{color:#333}.o_day-picker .e_day-picker-buttons{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;position:relative;background-color:#fff}.o_day-picker .i_day-picker-title{-ms-flex-preferred-size:100%;flex-basis:100%;padding:10px;text-align:center}.o_day-picker .e_day-picker-arrow-container{cursor:pointer;padding:10px}.o_day-picker .i_day-picker-arrow-left{border-right:5px solid #000}.o_day-picker .i_day-picker-arrow-left,.o_day-picker .i_day-picker-arrow-right{width:0;height:0;border-top:5px solid transparent;border-bottom:5px solid transparent}.o_day-picker .i_day-picker-arrow-right{border-left:5px solid #000}.o_day-picker .i_day-picker-header{display:-webkit-box;display:-ms-flexbox;display:flex;background:#ccc;color:#fff}.o_day-picker .i_day-picker-header div{padding:5px;width:20%;height:20%;text-align:center}.o_day-picker .i_day-picker-body{cursor:pointer;background-color:#fff}.o_day-picker .i_day-picker-row{display:-webkit-box;display:-ms-flexbox;display:flex;text-align:center;border:solid #eaecec;border-width:0 0 0 1px}.o_day-picker .i_day-picker-row>div{border:solid #eaecec;border-width:0 1px 1px 0;width:20%}.o_day-picker .i_day-picker-row [class*=-day]{padding:5px;text-align:left;font-size:.7em;min-height:4em}.o_day-picker .i_day-picker-row .selected-day{color:#fff;background-color:#70bde2}.o_day-picker .i_day-picker-row .next-month-day,.o_day-picker .i_day-picker-row .prev-month-day{color:#b3b3b3}.o_day-picker .i_day-picker-reset{width:25%;cursor:pointer;border:1px solid #d3d3d3;border-radius:3px;margin-top:10px;text-align:center}.o_selected-day{background:#1a728f!important;color:#fff!important} 2 | /*# sourceMappingURL=main.3fbecb66.css.map*/ -------------------------------------------------------------------------------- /app/Example/build/main.3fbecb66.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"main.3fbecb66.css","sourceRoot":""} -------------------------------------------------------------------------------- /app/Example/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andresmijares/react-calendar-multiday/511b101c1d5a183a20562529929654ffde0d7e55/app/Example/favicon.ico -------------------------------------------------------------------------------- /app/Example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Multiday Calendar 6 | 7 | 8 | 9 | 10 | React App 11 | 12 | 13 |
14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/Example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample", 3 | "version": "0.0.1", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "0.2.0" 7 | }, 8 | "dependencies": { 9 | "moment": "*", 10 | "moment-range": "*", 11 | "ramda": "*", 12 | "react": "*", 13 | "react-dom": "*", 14 | "react-calendar-multiday": "*" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build" 19 | }, 20 | "eslintConfig": { 21 | "extends": "./node_modules/react-scripts/config/eslint.js" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Example/src/index.js: -------------------------------------------------------------------------------- 1 | // import 'babel-polyfill' /* Support for IE11 */ 2 | import React from 'react' 3 | import ReactDOM from 'react-dom' 4 | import Calendar from 'react-calendar-multiday' 5 | import moment from 'moment' 6 | import {omit} from 'ramda' 7 | 8 | const container = { 9 | width: '375px', 10 | float: 'left', 11 | marginRight: '50px', 12 | marginBottom: '50px', 13 | fontFamily: 'system-ui', 14 | } 15 | 16 | const buttonStyle = { 17 | border: 'none', 18 | fontSize: '.75em', 19 | outline: 'none', 20 | marginLeft: '10px', 21 | cursor: 'pointer', 22 | } 23 | 24 | const selectedDays = [ 25 | moment().add(3, 'days'), 26 | moment().add(7, 'days'), 27 | moment().add(8, 'days'), 28 | moment().add(10, 'days'), 29 | moment().add(60, 'days'), 30 | ] 31 | 32 | const reactToChange = (ob) => { 33 | console.warn(ob) 34 | } 35 | 36 | class App extends React.Component { 37 | constructor (props) { 38 | super(props) 39 | this.state = { 40 | currentChannel: 0, 41 | channels: {}, 42 | } 43 | this.onReset = this.onReset.bind(this) 44 | this.addChannels = this.addChannels.bind(this) 45 | this.deleteChannel = this.deleteChannel.bind(this) 46 | this.setChannel = this.setChannel.bind(this) 47 | } 48 | 49 | addChannels ({channels, currentChannel}) { 50 | this.setState({ 51 | channels: channels, 52 | currentChannel: currentChannel, 53 | }) 54 | } 55 | 56 | setChannel (index) { 57 | this.setState({currentChannel: index}) 58 | } 59 | 60 | deleteChannel (index) { 61 | this.setState({ 62 | channels: omit([String(index)], this.state.channels) 63 | }) 64 | } 65 | 66 | onReset () { 67 | this.setState({ 68 | channels: {}, 69 | currentChannel: 0 70 | }) 71 | } 72 | 73 | render () { 74 | return ( 75 |
76 |
77 |

{`Calendar with channels`}

78 | 83 |
84 |
85 |

{`Calendar with choisable channels`}

86 |
87 | {Object.keys(this.state.channels).map((key, index) => { 88 | const channel = this.state.channels[key] 89 | return
90 |

91 | {`🗓 ${channel.map(day => day.format('MM/DD/YY')).join(' - ')}`} 92 |

93 | 95 | 97 |
98 | })} 99 |
100 | 108 |
109 |
110 |

{`Single Day Calendar`}

111 | 114 |
115 |
116 |

{`Single Day Calendar Pre-selected`}

117 | 120 |
121 |
122 |
123 |

{`Two Months from Now`}

124 | 128 |
129 |
130 |

{`Multiple Day Calendar`}

131 | 134 |
135 |
136 |
137 |

{`Multiple With Pre-selected days`}

138 | 143 |
144 |
145 | ) 146 | } 147 | } 148 | 149 | const render = () => { 150 | ReactDOM.render( 151 | , 152 | document.getElementById('root') 153 | ) 154 | } 155 | 156 | /* prevent FOUC https://stackoverflow.com/a/43902734 */ 157 | setTimeout(render, 0) 158 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | import Calendar from './Calendar/Calendar' 2 | import './Calendar/styles.css' 3 | 4 | export default Calendar 5 | -------------------------------------------------------------------------------- /lib/app.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("react"),require("ramda"),require("moment"),require("moment-range")):"function"==typeof define&&define.amd?define("app",["react","ramda","moment","moment-range"],e):"object"==typeof exports?exports.app=e(require("react"),require("ramda"),require("moment"),require("moment-range")):t.app=e(t.React,t.ramda,t.moment,t["moment-range"])}(this,function(t,e,n,r){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,e),o.l=!0,o.exports}var n={};return e.m=t,e.c=n,e.d=function(t,n,r){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:r})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=57)}([function(t,e){var n=t.exports={version:"2.5.1"};"number"==typeof __e&&(__e=n)},function(t,e,n){var r=n(28)("wks"),o=n(17),i=n(3).Symbol,a="function"==typeof i;(t.exports=function(t){return r[t]||(r[t]=a&&i[t]||(a?i:o)("Symbol."+t))}).store=r},function(t,e,n){var r=n(3),o=n(0),i=n(22),a=n(7),u=function(t,e,n){var c,s,f,l=t&u.F,d=t&u.G,p=t&u.S,y=t&u.P,h=t&u.B,v=t&u.W,m=d?o:o[e]||(o[e]={}),b=m.prototype,g=d?r:p?r[e]:(r[e]||{}).prototype;d&&(n=e);for(c in n)(s=!l&&g&&void 0!==g[c])&&c in m||(f=s?g[c]:n[c],m[c]=d&&"function"!=typeof g[c]?n[c]:h&&s?i(f,r):v&&g[c]==f?function(t){var e=function(e,n,r){if(this instanceof t){switch(arguments.length){case 0:return new t;case 1:return new t(e);case 2:return new t(e,n)}return new t(e,n,r)}return t.apply(this,arguments)};return e.prototype=t.prototype,e}(f):y&&"function"==typeof f?i(Function.call,f):f,y&&((m.virtual||(m.virtual={}))[c]=f,t&u.R&&b&&!b[c]&&a(b,c,f)))};u.F=1,u.G=2,u.S=4,u.P=8,u.B=16,u.W=32,u.U=64,u.R=128,t.exports=u},function(t,e){var n=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},function(t,e,n){var r=n(8),o=n(42),i=n(23),a=Object.defineProperty;e.f=n(5)?Object.defineProperty:function(t,e,n){if(r(t),e=i(e,!0),r(n),o)try{return a(t,e,n)}catch(t){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(t[e]=n.value),t}},function(t,e,n){t.exports=!n(9)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,e){var n={}.hasOwnProperty;t.exports=function(t,e){return n.call(t,e)}},function(t,e,n){var r=n(4),o=n(12);t.exports=n(5)?function(t,e,n){return r.f(t,e,o(1,n))}:function(t,e,n){return t[e]=n,t}},function(t,e,n){var r=n(11);t.exports=function(t){if(!r(t))throw TypeError(t+" is not an object!");return t}},function(t,e){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,e,n){var r=n(46),o=n(25);t.exports=function(t){return r(o(t))}},function(t,e){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,e){t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},function(t,e,n){var r=n(45),o=n(29);t.exports=Object.keys||function(t){return r(t,o)}},function(t,e,n){var r=n(25);t.exports=function(t){return Object(r(t))}},function(t,e){t.exports={}},function(t,e){function n(){throw new Error("setTimeout has not been defined")}function r(){throw new Error("clearTimeout has not been defined")}function o(t){if(f===setTimeout)return setTimeout(t,0);if((f===n||!f)&&setTimeout)return f=setTimeout,setTimeout(t,0);try{return f(t,0)}catch(e){try{return f.call(null,t,0)}catch(e){return f.call(this,t,0)}}}function i(t){if(l===clearTimeout)return clearTimeout(t);if((l===r||!l)&&clearTimeout)return l=clearTimeout,clearTimeout(t);try{return l(t)}catch(e){try{return l.call(null,t)}catch(e){return l.call(this,t)}}}function a(){h&&p&&(h=!1,p.length?y=p.concat(y):v=-1,y.length&&u())}function u(){if(!h){var t=o(a);h=!0;for(var e=y.length;e;){for(p=y,y=[];++v1)for(var n=1;n0?r:n)(t)}},function(t,e,n){var r=n(28)("keys"),o=n(17);t.exports=function(t){return r[t]||(r[t]=o(t))}},function(t,e,n){var r=n(3),o=r["__core-js_shared__"]||(r["__core-js_shared__"]={});t.exports=function(t){return o[t]||(o[t]={})}},function(t,e){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,e){e.f=Object.getOwnPropertySymbols},function(t,e,n){t.exports={default:n(69),__esModule:!0}},function(t,e){t.exports=!0},function(t,e,n){var r=n(8),o=n(81),i=n(29),a=n(27)("IE_PROTO"),u=function(){},c=function(){var t,e=n(43)("iframe"),r=i.length;for(e.style.display="none",n(82).appendChild(e),e.src="javascript:",t=e.contentWindow.document,t.open(),t.write("