├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .travis.yml ├── CHANGELOG.MD ├── README.MD ├── __tests__ ├── Calendar.react-test.js ├── Day.react-test.js ├── Month.react-test.js ├── Timeslot.react-test.js ├── Week.react-test.js └── __snapshots__ │ ├── Calendar.react-test.js.snap │ ├── Day.react-test.js.snap │ ├── Month.react-test.js.snap │ ├── Timeslot.react-test.js.snap │ └── Week.react-test.js.snap ├── build └── build.min.js ├── index.html ├── package.json ├── public ├── atom-one-dark.css └── build.js ├── src ├── js │ ├── components │ │ ├── .gitkeep │ │ ├── calendar.jsx │ │ ├── day.jsx │ │ ├── month.jsx │ │ ├── timeslot.jsx │ │ └── week.jsx │ ├── constants │ │ ├── day.js │ │ ├── timeslot.js │ │ └── week.js │ ├── demo │ │ ├── .gitkeep │ │ ├── app.jsx │ │ └── snippets │ │ │ └── custom-timeslot.md │ ├── react-timeslot-calendar.jsx │ └── util │ │ ├── helpers.js │ │ └── markdown-snippet.jsx └── styles │ ├── .gitkeep │ ├── abstract │ └── _variables.scss │ ├── components │ ├── _calendar.scss │ ├── _day.scss │ ├── _month.scss │ ├── _timeslot.scss │ └── _week.scss │ ├── demo │ └── main.scss │ └── main.scss ├── webpack.config.js └── webpack.demo.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | root = true 5 | 6 | 7 | [*] 8 | # Change these settings to your own preference 9 | indent_style = space 10 | indent_size = 2 11 | 12 | # We recommend you to keep these unchanged 13 | end_of_line = lf 14 | charset = utf-8 15 | trim_trailing_whitespace = true 16 | insert_final_newline = true 17 | 18 | [*.md] 19 | trim_trailing_whitespace = false 20 | indent_size = 4 21 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:jest/recommended"], 3 | "parserOptions": { 4 | "ecmaVersion": 6, 5 | "sourceType": "module", 6 | "ecmaFeatures": { 7 | "jsx": true 8 | } 9 | }, 10 | "plugins": ["react", "jest"], 11 | "rules": { 12 | "indent": ["error", 2], 13 | "linebreak-style": ["error", "unix"], 14 | "quotes": ["error", "single"], 15 | "semi": ["error", "always"], 16 | "no-cond-assign": ["error", "always"], 17 | "no-unused-vars": ["error", {"argsIgnorePattern": "state|action|dispatch"}], 18 | "no-console": "off", 19 | "no-extra-boolean-cast": "off", 20 | "camelcase": 2, 21 | "comma-dangle": ["error", "always-multiline"], 22 | "keyword-spacing": ["error", { "after": true, "before": true }], 23 | "react/jsx-curly-spacing": ["error", "always"], 24 | "react/jsx-equals-spacing": ["error", "always"] 25 | }, 26 | "globals": { 27 | "document": true, 28 | "console": true, 29 | "__dirname": true, 30 | "module": true, 31 | }, 32 | "env": { 33 | "jest/globals": true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | -------------------------------------------------------------------------------- /CHANGELOG.MD: -------------------------------------------------------------------------------- 1 | ## Version 0.1.3: 2 | * **Bug Fix**: Improve date handling for disabled dates. Before it was only considering startDate, now, it takes into account startDate && endDate. 3 | 4 | ## Version 0.1.2: 5 | * **Bug Fix**: Selecting a Timeslot made calendar return to initial date. 6 | 7 | ## Version 0.1.1: 8 | * Improved build size. 9 | * Input values now return as Moment's Default Format (YYYY-MM-DDThh:mm:ssTZD) instead of a custom format (MMMM Do YYYY, h:mm:ss A). 10 | 11 | ## Version 0.1.0: 12 | * Initial Release. 13 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # React Timeslot Calendar 2 | [![Build Status](https://travis-ci.org/lrojas94/react-timeslot-calendar.svg?branch=master)](https://travis-ci.org/lrojas94/react-timeslot-calendar) 3 | 4 | A calendar component which allows users to select and reserve avaiblable timeslots so that they can setup a meeting and/or event. 5 | 6 | ## Dependencies 7 | * React 15.4.4 and up 8 | * MomentJS 9 | * CalendarJS 10 | 11 | ## Installation 12 | ``` 13 | npm install --save react-timeslot-calendar 14 | ``` 15 | 16 | ## Usage: 17 | ```js 18 | import moment from 'moment'; 19 | import ReactTimeslotCalendar from 'react-timeslot-calendar'; 20 | 21 | ... 22 | render() { 23 | return ( 24 | 27 | ); 28 | } 29 | ... 30 | ``` 31 | 32 | ## Props 33 | ### Required Props: 34 | *Note*: All format strings are read with `moment`, so any format supported by `moment` is available here. 35 | 36 | 37 | * **initialDate** (`String`): An ISO formatted date compatible with `momentJS`. A `moment` instance is also a valid input. 38 | 39 | ### Optional 40 | * **timeslots** (`Object`): An array of available timeslots in the format: 41 | ```js 42 | let timeslots = [ 43 | ['1', '2'], // 1:00 AM - 2:00 AM 44 | ['2', '3'], // 2:00 AM - 3:00 AM 45 | ['4', '6'], // 4:00 AM - 6:00 AM 46 | '5', // 5:00 AM 47 | ['4', '6', '7', '8'], // 4:00 AM - 6:00 AM - 7:00AM - 8:00AM 48 | ]; 49 | ``` 50 | By default, one hour timeslots from 12:00AM to 11:00PM are provided. 51 | 52 | * **timeslotProps** (`Object`): How the timeslots will be interpreted by the calendar. By default, the format goes as follows: 53 | ```js 54 | let timeslotProps = { 55 | format: 'h', // Each element in the timeslot array is an Hour 56 | showFormat: 'h:mm A', // They will be displayed as Hour:Minutes AM/PM 57 | } 58 | ``` 59 | 60 | * **disabledTimeslots** (`Object`): Timeslots disabled by default. a `startDate` and `format` should be provided. The resulting date **must** match with one of the timeslots. 61 | ```js 62 | // sample: 63 | let disabledTimeslots = {[ 64 | { 65 | startDate: 'April 30th 2017, 12:00:00 AM', 66 | format: 'MMMM Do YYYY, h:mm:ss A', 67 | }, 68 | { 69 | startDate: 'May 1st 2017, 3:00:00 PM', 70 | format: 'MMMM Do YYYY, h:mm:ss A', 71 | }, 72 | { 73 | startDate: 'May 5th 2017, 6:00:00 PM', 74 | format: 'MMMM Do YYYY, h:mm:ss A', 75 | }, 76 | ]} 77 | ``` 78 | * **maxTimeslots** (`Number`): The maximum number of timeslots a user can select. Default value is **1**. 79 | 80 | * **renderDays** (`Object`): Days of the week to be rendered in the calendar. As an example, if we wanted to remove weekends, all we have to do is pass: 81 | ```js 82 | let ignoreWeekends = { 83 | 'saturdays': false, 84 | 'sundays': false, 85 | }; 86 | ``` 87 | By default, all week days are displayed. 88 | 89 | * **startDateInputProps** (`Object`): Few props to modify how the input for `startDate` will behave. Only allows `name` and `class`. 90 | ```js 91 | //sample 92 | let startDateInputProps = { 93 | class: 'some-random-class', 94 | name: 'my-start-date-input-name', 95 | }; 96 | ``` 97 | * **endDateInputProps** (`Object`): Same idea as **startDateInputProps** but instead takes effect in the `endDate` input. 98 | 99 | * **onSelectTimeslot** (`Function`): A callback which takes as parameters `selectedTimeslots (array)` and `lastSelectedTimeslot (object)`. Every timeslot object contains `startDate` and `endDate`, both of which are MomentJS objects. 100 | ```js 101 | let onSelectTimeslot = (allTimeslots, lastSelectedTimeslot) => { 102 | /** 103 | * All timeslot objects include `startDate` and `endDate`. 104 | 105 | * It is important to note that if timelots provided contain a single 106 | * value (e.g: timeslots = [['8'], ['9', '10']) then only `startDate` is filled up with 107 | * the desired information. 108 | */ 109 | console.log(lastSelectedTimeslot.startDate); // MomentJS object. 110 | 111 | } 112 | ``` 113 | ### Development 114 | We're open to any help and support, so feel free to open up a PR if you happen to have a great idea! Also, feel free to take up any issues (if available). 115 | 116 | Feel free to download the project and make up some changes! Dev. environment is run with `npm run dev`. 117 | -------------------------------------------------------------------------------- /__tests__/Calendar.react-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import sinon from 'sinon'; 4 | import moment from 'moment'; 5 | import { 6 | mount, 7 | shallow, 8 | } from 'enzyme'; 9 | import Calendar from '../src/js/components/calendar'; 10 | import Timeslot from '../src/js/components/timeslot'; 11 | import { DEFAULT_TIMESLOTS } from '../src/js/constants/day'; 12 | 13 | describe('Render tests', () => { 14 | test('Renders Correctly.', () => { 15 | const tree = renderer.create( 16 | 20 | ) 21 | .toJSON(); 22 | 23 | expect(tree).toMatchSnapshot(); 24 | }); 25 | 26 | test('Expect timeslot selection to function with min props', () => { 27 | const component = mount( 28 | 32 | ); 33 | 34 | const timeslot = component.find('.tsc-timeslot').not('.tsc-timeslot--disabled').first(); 35 | timeslot.simulate('click'); 36 | 37 | expect(component.state().selectedTimeslots).toHaveLength(1); 38 | }); 39 | 40 | test('Expects a maximum of 2 selected timeslots', () => { 41 | const component = mount( 42 | 47 | ); 48 | 49 | const timeslots = component.find('.tsc-timeslot').not('.tsc-timeslot--disabled').slice(0,5); 50 | timeslots.forEach((timeslot) => { 51 | timeslot.simulate('click'); 52 | }); 53 | 54 | expect(component.state().selectedTimeslots).toHaveLength(2); 55 | }); 56 | 57 | test('Expects onSelectTimeslot to be called as many times as timeslots are clicked', () => { 58 | const onSelectTimeslot = sinon.spy(); 59 | const component = mount( 60 | 65 | ); 66 | 67 | const timeslots = component.find('.tsc-timeslot').not('.tsc-timeslot--disabled').slice(0,5); 68 | let clickCount = 0; 69 | timeslots.forEach((timeslot) => { 70 | timeslot.simulate('click'); 71 | clickCount++; 72 | }); 73 | 74 | expect(onSelectTimeslot).toHaveProperty('callCount', clickCount); 75 | }); 76 | 77 | test('Expects 2 input elements after clicking a timeslot with min props.', () => { 78 | const component = mount( 79 | 83 | ); 84 | 85 | const timeslot = component.find('.tsc-timeslot').not('.tsc-timeslot--disabled').first(); 86 | timeslot.simulate('click'); 87 | 88 | const inputs = component.findWhere(n => n.prop('name') == 'tsc-startDate' || n.prop('name') == 'tsc-endDate'); 89 | 90 | expect(inputs).toHaveLength(2); 91 | }); 92 | 93 | test('Expects 2 input elements with 1 custom name after clicking a timeslot', () => { 94 | const component = mount( 95 | 102 | ); 103 | 104 | const timeslot = component.find('.tsc-timeslot').not('.tsc-timeslot--disabled').first(); 105 | timeslot.simulate('click'); 106 | 107 | const inputs = component.findWhere(n => n.prop('name') == 'custom-startDate' || n.prop('name') == 'tsc-endDate'); 108 | 109 | expect(inputs).toHaveLength(2); 110 | }); 111 | 112 | test('Expects 4 input elements after clicking multiple timeslots', () => { 113 | const component = mount( 114 | 119 | ); 120 | 121 | const timeslots = component.find('.tsc-timeslot').not('.tsc-timeslot--disabled').slice(0,5); 122 | timeslots.forEach((timeslot) => { 123 | timeslot.simulate('click'); 124 | }); 125 | 126 | const inputs = component.findWhere(n => n.prop('name') == 'tsc-startDate[]' || n.prop('name') == 'tsc-endDate[]'); 127 | 128 | expect(inputs).toHaveLength(4); 129 | }); 130 | 131 | test('Expects 3 disabled timeslots based on props sent.', () => { 132 | const component = mount( 133 | 154 | ); 155 | 156 | const disabledTimeslots = component.findWhere(timeslot => timeslot.prop('status') == 'DISABLED'); 157 | 158 | expect(disabledTimeslots).toHaveLength(3); 159 | }); 160 | }); 161 | -------------------------------------------------------------------------------- /__tests__/Day.react-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import sinon from 'sinon'; 4 | import moment from 'moment'; 5 | import { 6 | shallow, 7 | mount, 8 | } from 'enzyme'; 9 | import Day from '../src/js/components/day'; 10 | import Timeslot from '../src/js/components/timeslot'; 11 | import { 12 | DEFAULT_TIMESLOTS, 13 | DEFAULT_TIMESLOT_FORMAT, 14 | DEFAULT_TIMESLOT_SHOW_FORMAT, 15 | } from '../src/js/constants/day'; 16 | 17 | describe('Render tests', () => { 18 | test('Renders Correctly with min props.', () => { 19 | const onClickSpy = sinon.spy(); 20 | const tree = renderer.create( 21 | 33 | ) 34 | .toJSON(); 35 | 36 | expect(tree).toMatchSnapshot(); 37 | }); 38 | 39 | test('Renders Correctly if timeslots is an array of strings.', () => { 40 | const onClickSpy = sinon.spy(); 41 | const timeslots = [ 42 | '0', 43 | '1', 44 | '2', 45 | ]; 46 | 47 | const tree = renderer.create( 48 | 60 | ) 61 | .toJSON(); 62 | 63 | expect(tree).toMatchSnapshot(); 64 | }); 65 | 66 | test('Renders Correctly if timeslots is an array of array.', () => { 67 | const onClickSpy = sinon.spy(); 68 | const timeslots = [ 69 | ['0', '1'], 70 | ['1', '2'], 71 | '3', 72 | ]; 73 | 74 | const tree = renderer.create( 75 | 87 | ) 88 | .toJSON(); 89 | 90 | expect(tree).toMatchSnapshot(); 91 | }); 92 | }); 93 | 94 | describe('Functionality tests', () => { 95 | test('Uses renderTitle function when provided', () => { 96 | const onClickSpy = sinon.spy(); 97 | const renderTitleSpy = sinon.spy(); 98 | const component = mount( 99 | 112 | ); 113 | 114 | expect(renderTitleSpy).toHaveProperty('callCount', 1); 115 | }); 116 | 117 | test('Timeslot click triggers onTimeslotClick', () => { 118 | const onClickSpy = sinon.spy(); 119 | const component = mount( 120 | 132 | ); 133 | 134 | const timeslot = component.find('.tsc-timeslot').not('.tsc-timeslot--disabled').first(); 135 | timeslot.simulate('click'); 136 | 137 | expect(onClickSpy).toHaveProperty('callCount', 1); 138 | }); 139 | 140 | test('Renders 24 timeslots by default', () => { 141 | const onClickSpy = sinon.spy(); 142 | const component = shallow( 143 | 155 | ); 156 | 157 | const timeslot = component.find(Timeslot); 158 | 159 | expect(timeslot).toHaveLength(24); 160 | }); 161 | 162 | test('Renders only the amount of timeslots provided', () => { 163 | const onClickSpy = sinon.spy(); 164 | const timeslots = [ 165 | ['0', '1'], 166 | ['1', '2'], 167 | ]; 168 | const component = shallow( 169 | 181 | ); 182 | 183 | const timeslot = component.find(Timeslot); 184 | 185 | expect(timeslot).toHaveLength(2); 186 | }); 187 | 188 | test('Expect 12 disabled timeslots', () => { 189 | const onClickSpy = sinon.spy(); 190 | const component = mount( 191 | 203 | ); 204 | 205 | const timeslot = component.find('.tsc-timeslot--disabled'); 206 | 207 | expect(timeslot).toHaveLength(12); 208 | }); 209 | 210 | }); 211 | -------------------------------------------------------------------------------- /__tests__/Month.react-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import sinon from 'sinon'; 4 | import moment from 'moment'; 5 | import Calendar from 'calendarjs'; 6 | import { 7 | mount, 8 | } from 'enzyme'; 9 | import Month from '../src/js/components/month'; 10 | import helpers from '../src/js/util/helpers'; 11 | 12 | import { RENDER_DAYS } from '../src/js/constants/week'; 13 | import { 14 | DEFAULT_TIMESLOTS, 15 | DEFAULT_TIMESLOT_FORMAT, 16 | DEFAULT_TIMESLOT_SHOW_FORMAT, 17 | } from '../src/js/constants/day'; 18 | 19 | const cal = new Calendar(2017, 4); 20 | 21 | describe('Render tests', () => { 22 | test('Renders Correctly with min props.', () => { 23 | const weeks = cal.generate(); 24 | const tree = renderer.create( 25 | 38 | ) 39 | .toJSON(); 40 | 41 | expect(tree).toMatchSnapshot(); 42 | }); 43 | 44 | test('Renders Correctly with all props.', () => { 45 | const weeks = cal.generate(); 46 | const onWeekOutOfMonth = sinon.spy(); 47 | const tree = renderer.create( 48 | 62 | ) 63 | .toJSON(); 64 | 65 | expect(tree).toMatchSnapshot(); 66 | }); 67 | }); 68 | 69 | describe('Functionality tests', () => { 70 | test('Current week contains currentDate', () => { 71 | const weeks = cal.generate(); 72 | const currentDate = moment([2017, 3, 1]); 73 | const component = mount( 74 | 87 | ); 88 | 89 | let weekIndex = null; 90 | weeks.some((week, index) => { 91 | let currentDateFound = week.some((day) => { 92 | return helpers.getMomentFromCalendarJSDateElement(day).format() === currentDate.format(); 93 | }); 94 | 95 | if (currentDateFound) { 96 | weekIndex = index; 97 | return true; 98 | } 99 | }); 100 | 101 | 102 | expect(component.state().currentWeekIndex).toEqual(weekIndex); 103 | }); 104 | 105 | test('onWeekOutOfMonth callback called if currentDate is start of the month and user tries to go back', () => { 106 | const weeks = cal.generate(); 107 | const currentDate = moment([2017, 3, 1]).startOf('month'); 108 | const onWeekOutOfMonth = sinon.spy(); 109 | const component = mount( 110 | 124 | ); 125 | 126 | component.find('.tsc-month__action-element--left').simulate('click'); 127 | expect(onWeekOutOfMonth).toHaveProperty('callCount', 1); 128 | }); 129 | 130 | test('onWeekOutOfMonth callback called if currentDate is end of the month and user tries to go next', () => { 131 | const weeks = cal.generate(); 132 | const currentDate = moment([2017, 3, 1]).endOf('month'); 133 | const onWeekOutOfMonth = sinon.spy(); 134 | const component = mount( 135 | 149 | ); 150 | 151 | component.find('.tsc-month__action-element--right').simulate('click'); 152 | expect(onWeekOutOfMonth).toHaveProperty('callCount', 1); 153 | }); 154 | 155 | test('Users can go to next week if available', () => { 156 | const weeks = cal.generate(); 157 | const currentDate = moment([2017, 3, 1]).startOf('month'); 158 | const component = mount( 159 | 172 | ); 173 | const weekIndexBeforeClick = component.state().currentWeekIndex; 174 | component.find('.tsc-month__action-element--right').simulate('click'); 175 | const weekIndexAfterClick = component.state().currentWeekIndex; 176 | expect(weekIndexAfterClick).toEqual(weekIndexBeforeClick + 1); 177 | }); 178 | 179 | test('Users can go to prev week if available', () => { 180 | const weeks = cal.generate(); 181 | const currentDate = moment([2017, 3, 1]).endOf('month'); 182 | const component = mount( 183 | 196 | ); 197 | 198 | const weekIndexBeforeClick = component.state().currentWeekIndex; 199 | component.find('.tsc-month__action-element--left').simulate('click'); 200 | const weekIndexAfterClick = component.state().currentWeekIndex; 201 | expect(weekIndexAfterClick).toEqual(weekIndexBeforeClick - 1); 202 | }); 203 | 204 | }); 205 | -------------------------------------------------------------------------------- /__tests__/Timeslot.react-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import sinon from 'sinon'; 4 | import Timeslot from '../src/js/components/timeslot'; 5 | import { 6 | shallow, 7 | mount, 8 | } from 'enzyme'; 9 | 10 | import { 11 | DEFAULT, 12 | SELECTED, 13 | DISABLED, 14 | } from '../src/js/constants/timeslot'; 15 | 16 | import { DEFAULT_TIMESLOTS } from '../src/js/constants/day'; 17 | 18 | 19 | it('Renders Correctly with All props.', () => { 20 | const onClickSpy = sinon.spy(); 21 | const tree = renderer.create( 22 | 29 | ) 30 | .toJSON(); 31 | 32 | expect(tree).toMatchSnapshot(); 33 | }); 34 | 35 | 36 | it('Renders when customClassNames prop is not provided', () => { 37 | const onClickSpy = sinon.spy(); 38 | const tree = renderer.create( 39 | 45 | ) 46 | .toJSON(); 47 | 48 | expect(tree).toMatchSnapshot(); 49 | }); 50 | 51 | it('Renders when customClassNames is null', () => { 52 | const onClickSpy = sinon.spy(); 53 | const tree = renderer.create( 54 | 61 | ) 62 | .toJSON(); 63 | 64 | expect(tree).toMatchSnapshot(); 65 | }); 66 | 67 | it('Assumes default status if not provided', () => { 68 | const onClickSpy = sinon.spy(); 69 | const timeslot = mount( 70 | 75 | ); 76 | 77 | expect(timeslot.props().status).toEqual(DEFAULT); 78 | }); 79 | 80 | it('Does not call parent onClick prop if status is Disabled', () => { 81 | const onClickSpy = sinon.spy(); 82 | const timeslot = mount( 83 | 89 | ); 90 | 91 | timeslot.simulate('click'); 92 | expect(onClickSpy).toHaveProperty('callCount', 0); 93 | }); 94 | 95 | it('Does call parent onClick prop if status is Default', () => { 96 | const onClickSpy = sinon.spy(); 97 | const timeslot = mount( 98 | 104 | ); 105 | 106 | timeslot.simulate('click'); 107 | expect(onClickSpy).toHaveProperty('callCount', 1); 108 | }); 109 | 110 | it('Does call parent onClick prop if status is Selected', () => { 111 | const onClickSpy = sinon.spy(); 112 | const timeslot = mount( 113 | 119 | ); 120 | 121 | timeslot.simulate('click'); 122 | expect(onClickSpy).toHaveProperty('callCount', 1); 123 | }); 124 | 125 | it('Has default tsc-timeslot class.', () => { 126 | const onClickSpy = sinon.spy(); 127 | const timeslot = shallow( 128 | 134 | ); 135 | 136 | expect(timeslot.hasClass('tsc-timeslot')).toEqual(true); 137 | }); 138 | 139 | it('Adds selected class when Status prop is Selected', () => { 140 | const onClickSpy = sinon.spy(); 141 | const timeslot = shallow( 142 | 148 | ); 149 | 150 | expect(timeslot.hasClass('tsc-timeslot--selected')).toEqual(true); 151 | }); 152 | 153 | it('Adds all classes in customClassNames when customClassNames is an object', () => { 154 | const onClickSpy = sinon.spy(); 155 | const customClasses = { 156 | 'added-class': true, 157 | 'added-class-two': true, 158 | }; 159 | 160 | const timeslot = shallow( 161 | 168 | ); 169 | 170 | expect(timeslot.is('.added-class.added-class-two')).toEqual(true); 171 | }); 172 | 173 | it('Does not adds classes with value = false in customClassNames', () => { 174 | const onClickSpy = sinon.spy(); 175 | const customClasses = { 176 | 'not-added-class': false, 177 | }; 178 | 179 | const timeslot = shallow( 180 | 187 | ); 188 | 189 | expect(timeslot.is('.not-added-class')).toEqual(false); 190 | }); 191 | -------------------------------------------------------------------------------- /__tests__/Week.react-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import sinon from 'sinon'; 4 | import moment from 'moment'; 5 | import Calendar from 'calendarjs'; 6 | import { 7 | shallow, 8 | mount, 9 | } from 'enzyme'; 10 | 11 | import Week from '../src/js/components/week'; 12 | import Day from '../src/js/components/day'; 13 | import Timeslot from '../src/js/components/timeslot'; 14 | 15 | import { RENDER_DAYS } from '../src/js/constants/week'; 16 | import { 17 | DEFAULT_TIMESLOTS, 18 | DEFAULT_TIMESLOT_FORMAT, 19 | DEFAULT_TIMESLOT_SHOW_FORMAT, 20 | } from '../src/js/constants/day'; 21 | 22 | const cal = new Calendar(2017, 4); 23 | 24 | describe('Render tests', () => { 25 | test('Renders Correctly with min props.', () => { 26 | const onClickSpy = sinon.spy(); 27 | const weeks = cal.generate(); 28 | 29 | const tree = renderer.create( 30 | 43 | ) 44 | .toJSON(); 45 | 46 | expect(tree).toMatchSnapshot(); 47 | }); 48 | 49 | test('Expect to Render 3 Days', () => { 50 | const onClickSpy = sinon.spy(); 51 | const weeks = cal.generate(); 52 | 53 | const component = mount( 54 | 67 | ); 68 | 69 | const day = component.find(Day); 70 | 71 | expect(day).toHaveLength(3); 72 | }); 73 | 74 | test('Timeslot click triggers onTimeslotClick', () => { 75 | const onClickSpy = sinon.spy(); 76 | const weeks = cal.generate(); 77 | 78 | const component = mount( 79 | 92 | ); 93 | 94 | const timeslot = component.find('.tsc-timeslot').not('.tsc-timeslot--disabled').first(); 95 | timeslot.simulate('click'); 96 | 97 | expect(onClickSpy).toHaveProperty('callCount', 1); 98 | }); 99 | 100 | test('Week Renders only 3 Days when specifying days to render.', () => { 101 | const onClickSpy = sinon.spy(); 102 | const weeks = cal.generate(); 103 | 104 | const component = mount( 105 | 126 | ); 127 | 128 | const days = component.find(Day); 129 | expect(days).toHaveLength(3); 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/Calendar.react-test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Render tests Renders Correctly. 1`] = ` 4 |
7 |
10 |
14 | ‹ 15 |
16 |
19 | April - 2017 20 |
21 |
25 | › 26 |
27 |
28 |
31 |
34 |
38 | ‹ 39 |
40 |
43 | Apr 23rd - Apr 29th 44 |
45 |
49 | › 50 |
51 |
52 |
55 |
58 |
61 | 62 | Sunday (23) 63 | 64 |
65 |
69 | 12:00 AM - 1:00 AM 70 |
71 |
75 | 1:00 AM - 2:00 AM 76 |
77 |
81 | 2:00 AM - 3:00 AM 82 |
83 |
87 | 3:00 AM - 4:00 AM 88 |
89 |
93 | 4:00 AM - 5:00 AM 94 |
95 |
99 | 5:00 AM - 6:00 AM 100 |
101 |
105 | 6:00 AM - 7:00 AM 106 |
107 |
111 | 7:00 AM - 8:00 AM 112 |
113 |
117 | 8:00 AM - 9:00 AM 118 |
119 |
123 | 9:00 AM - 10:00 AM 124 |
125 |
129 | 10:00 AM - 11:00 AM 130 |
131 |
135 | 11:00 AM - 12:00 PM 136 |
137 |
141 | 12:00 PM - 1:00 PM 142 |
143 |
147 | 1:00 PM - 2:00 PM 148 |
149 |
153 | 2:00 PM - 3:00 PM 154 |
155 |
159 | 3:00 PM - 4:00 PM 160 |
161 |
165 | 4:00 PM - 5:00 PM 166 |
167 |
171 | 5:00 PM - 6:00 PM 172 |
173 |
177 | 6:00 PM - 7:00 PM 178 |
179 |
183 | 7:00 PM - 8:00 PM 184 |
185 |
189 | 8:00 PM - 9:00 PM 190 |
191 |
195 | 9:00 PM - 10:00 PM 196 |
197 |
201 | 10:00 PM - 11:00 PM 202 |
203 |
207 | 11:00 PM - 12:00 AM 208 |
209 |
210 |
213 |
216 | 217 | Monday (24) 218 | 219 |
220 |
224 | 12:00 AM - 1:00 AM 225 |
226 |
230 | 1:00 AM - 2:00 AM 231 |
232 |
236 | 2:00 AM - 3:00 AM 237 |
238 |
242 | 3:00 AM - 4:00 AM 243 |
244 |
248 | 4:00 AM - 5:00 AM 249 |
250 |
254 | 5:00 AM - 6:00 AM 255 |
256 |
260 | 6:00 AM - 7:00 AM 261 |
262 |
266 | 7:00 AM - 8:00 AM 267 |
268 |
272 | 8:00 AM - 9:00 AM 273 |
274 |
278 | 9:00 AM - 10:00 AM 279 |
280 |
284 | 10:00 AM - 11:00 AM 285 |
286 |
290 | 11:00 AM - 12:00 PM 291 |
292 |
296 | 12:00 PM - 1:00 PM 297 |
298 |
302 | 1:00 PM - 2:00 PM 303 |
304 |
308 | 2:00 PM - 3:00 PM 309 |
310 |
314 | 3:00 PM - 4:00 PM 315 |
316 |
320 | 4:00 PM - 5:00 PM 321 |
322 |
326 | 5:00 PM - 6:00 PM 327 |
328 |
332 | 6:00 PM - 7:00 PM 333 |
334 |
338 | 7:00 PM - 8:00 PM 339 |
340 |
344 | 8:00 PM - 9:00 PM 345 |
346 |
350 | 9:00 PM - 10:00 PM 351 |
352 |
356 | 10:00 PM - 11:00 PM 357 |
358 |
362 | 11:00 PM - 12:00 AM 363 |
364 |
365 |
368 |
371 | 372 | Tuesday (25) 373 | 374 |
375 |
379 | 12:00 AM - 1:00 AM 380 |
381 |
385 | 1:00 AM - 2:00 AM 386 |
387 |
391 | 2:00 AM - 3:00 AM 392 |
393 |
397 | 3:00 AM - 4:00 AM 398 |
399 |
403 | 4:00 AM - 5:00 AM 404 |
405 |
409 | 5:00 AM - 6:00 AM 410 |
411 |
415 | 6:00 AM - 7:00 AM 416 |
417 |
421 | 7:00 AM - 8:00 AM 422 |
423 |
427 | 8:00 AM - 9:00 AM 428 |
429 |
433 | 9:00 AM - 10:00 AM 434 |
435 |
439 | 10:00 AM - 11:00 AM 440 |
441 |
445 | 11:00 AM - 12:00 PM 446 |
447 |
451 | 12:00 PM - 1:00 PM 452 |
453 |
457 | 1:00 PM - 2:00 PM 458 |
459 |
463 | 2:00 PM - 3:00 PM 464 |
465 |
469 | 3:00 PM - 4:00 PM 470 |
471 |
475 | 4:00 PM - 5:00 PM 476 |
477 |
481 | 5:00 PM - 6:00 PM 482 |
483 |
487 | 6:00 PM - 7:00 PM 488 |
489 |
493 | 7:00 PM - 8:00 PM 494 |
495 |
499 | 8:00 PM - 9:00 PM 500 |
501 |
505 | 9:00 PM - 10:00 PM 506 |
507 |
511 | 10:00 PM - 11:00 PM 512 |
513 |
517 | 11:00 PM - 12:00 AM 518 |
519 |
520 |
523 |
526 | 527 | Wednesday (26) 528 | 529 |
530 |
534 | 12:00 AM - 1:00 AM 535 |
536 |
540 | 1:00 AM - 2:00 AM 541 |
542 |
546 | 2:00 AM - 3:00 AM 547 |
548 |
552 | 3:00 AM - 4:00 AM 553 |
554 |
558 | 4:00 AM - 5:00 AM 559 |
560 |
564 | 5:00 AM - 6:00 AM 565 |
566 |
570 | 6:00 AM - 7:00 AM 571 |
572 |
576 | 7:00 AM - 8:00 AM 577 |
578 |
582 | 8:00 AM - 9:00 AM 583 |
584 |
588 | 9:00 AM - 10:00 AM 589 |
590 |
594 | 10:00 AM - 11:00 AM 595 |
596 |
600 | 11:00 AM - 12:00 PM 601 |
602 |
606 | 12:00 PM - 1:00 PM 607 |
608 |
612 | 1:00 PM - 2:00 PM 613 |
614 |
618 | 2:00 PM - 3:00 PM 619 |
620 |
624 | 3:00 PM - 4:00 PM 625 |
626 |
630 | 4:00 PM - 5:00 PM 631 |
632 |
636 | 5:00 PM - 6:00 PM 637 |
638 |
642 | 6:00 PM - 7:00 PM 643 |
644 |
648 | 7:00 PM - 8:00 PM 649 |
650 |
654 | 8:00 PM - 9:00 PM 655 |
656 |
660 | 9:00 PM - 10:00 PM 661 |
662 |
666 | 10:00 PM - 11:00 PM 667 |
668 |
672 | 11:00 PM - 12:00 AM 673 |
674 |
675 |
678 |
681 | 682 | Thursday (27) 683 | 684 |
685 |
689 | 12:00 AM - 1:00 AM 690 |
691 |
695 | 1:00 AM - 2:00 AM 696 |
697 |
701 | 2:00 AM - 3:00 AM 702 |
703 |
707 | 3:00 AM - 4:00 AM 708 |
709 |
713 | 4:00 AM - 5:00 AM 714 |
715 |
719 | 5:00 AM - 6:00 AM 720 |
721 |
725 | 6:00 AM - 7:00 AM 726 |
727 |
731 | 7:00 AM - 8:00 AM 732 |
733 |
737 | 8:00 AM - 9:00 AM 738 |
739 |
743 | 9:00 AM - 10:00 AM 744 |
745 |
749 | 10:00 AM - 11:00 AM 750 |
751 |
755 | 11:00 AM - 12:00 PM 756 |
757 |
761 | 12:00 PM - 1:00 PM 762 |
763 |
767 | 1:00 PM - 2:00 PM 768 |
769 |
773 | 2:00 PM - 3:00 PM 774 |
775 |
779 | 3:00 PM - 4:00 PM 780 |
781 |
785 | 4:00 PM - 5:00 PM 786 |
787 |
791 | 5:00 PM - 6:00 PM 792 |
793 |
797 | 6:00 PM - 7:00 PM 798 |
799 |
803 | 7:00 PM - 8:00 PM 804 |
805 |
809 | 8:00 PM - 9:00 PM 810 |
811 |
815 | 9:00 PM - 10:00 PM 816 |
817 |
821 | 10:00 PM - 11:00 PM 822 |
823 |
827 | 11:00 PM - 12:00 AM 828 |
829 |
830 |
833 |
836 | 837 | Friday (28) 838 | 839 |
840 |
844 | 12:00 AM - 1:00 AM 845 |
846 |
850 | 1:00 AM - 2:00 AM 851 |
852 |
856 | 2:00 AM - 3:00 AM 857 |
858 |
862 | 3:00 AM - 4:00 AM 863 |
864 |
868 | 4:00 AM - 5:00 AM 869 |
870 |
874 | 5:00 AM - 6:00 AM 875 |
876 |
880 | 6:00 AM - 7:00 AM 881 |
882 |
886 | 7:00 AM - 8:00 AM 887 |
888 |
892 | 8:00 AM - 9:00 AM 893 |
894 |
898 | 9:00 AM - 10:00 AM 899 |
900 |
904 | 10:00 AM - 11:00 AM 905 |
906 |
910 | 11:00 AM - 12:00 PM 911 |
912 |
916 | 12:00 PM - 1:00 PM 917 |
918 |
922 | 1:00 PM - 2:00 PM 923 |
924 |
928 | 2:00 PM - 3:00 PM 929 |
930 |
934 | 3:00 PM - 4:00 PM 935 |
936 |
940 | 4:00 PM - 5:00 PM 941 |
942 |
946 | 5:00 PM - 6:00 PM 947 |
948 |
952 | 6:00 PM - 7:00 PM 953 |
954 |
958 | 7:00 PM - 8:00 PM 959 |
960 |
964 | 8:00 PM - 9:00 PM 965 |
966 |
970 | 9:00 PM - 10:00 PM 971 |
972 |
976 | 10:00 PM - 11:00 PM 977 |
978 |
982 | 11:00 PM - 12:00 AM 983 |
984 |
985 |
988 |
991 | 992 | Saturday (29) 993 | 994 |
995 |
999 | 12:00 AM - 1:00 AM 1000 |
1001 |
1005 | 1:00 AM - 2:00 AM 1006 |
1007 |
1011 | 2:00 AM - 3:00 AM 1012 |
1013 |
1017 | 3:00 AM - 4:00 AM 1018 |
1019 |
1023 | 4:00 AM - 5:00 AM 1024 |
1025 |
1029 | 5:00 AM - 6:00 AM 1030 |
1031 |
1035 | 6:00 AM - 7:00 AM 1036 |
1037 |
1041 | 7:00 AM - 8:00 AM 1042 |
1043 |
1047 | 8:00 AM - 9:00 AM 1048 |
1049 |
1053 | 9:00 AM - 10:00 AM 1054 |
1055 |
1059 | 10:00 AM - 11:00 AM 1060 |
1061 |
1065 | 11:00 AM - 12:00 PM 1066 |
1067 |
1071 | 12:00 PM - 1:00 PM 1072 |
1073 |
1077 | 1:00 PM - 2:00 PM 1078 |
1079 |
1083 | 2:00 PM - 3:00 PM 1084 |
1085 |
1089 | 3:00 PM - 4:00 PM 1090 |
1091 |
1095 | 4:00 PM - 5:00 PM 1096 |
1097 |
1101 | 5:00 PM - 6:00 PM 1102 |
1103 |
1107 | 6:00 PM - 7:00 PM 1108 |
1109 |
1113 | 7:00 PM - 8:00 PM 1114 |
1115 |
1119 | 8:00 PM - 9:00 PM 1120 |
1121 |
1125 | 9:00 PM - 10:00 PM 1126 |
1127 |
1131 | 10:00 PM - 11:00 PM 1132 |
1133 |
1137 | 11:00 PM - 12:00 AM 1138 |
1139 |
1140 |
1141 |
1142 |
1143 | `; 1144 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/Day.react-test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Render tests Renders Correctly if timeslots is an array of array. 1`] = ` 4 |
7 |
10 | 11 | Friday (28) 12 | 13 |
14 |
18 | 12:00 AM - 1:00 AM 19 |
20 |
24 | 1:00 AM - 2:00 AM 25 |
26 |
30 | 3:00 AM 31 |
32 |
33 | `; 34 | 35 | exports[`Render tests Renders Correctly if timeslots is an array of strings. 1`] = ` 36 |
39 |
42 | 43 | Friday (28) 44 | 45 |
46 |
50 | 12:00 AM 51 |
52 |
56 | 1:00 AM 57 |
58 |
62 | 2:00 AM 63 |
64 |
65 | `; 66 | 67 | exports[`Render tests Renders Correctly with min props. 1`] = ` 68 |
71 |
74 | 75 | Friday (28) 76 | 77 |
78 |
82 | 12:00 AM - 1:00 AM 83 |
84 |
88 | 1:00 AM - 2:00 AM 89 |
90 |
94 | 2:00 AM - 3:00 AM 95 |
96 |
100 | 3:00 AM - 4:00 AM 101 |
102 |
106 | 4:00 AM - 5:00 AM 107 |
108 |
112 | 5:00 AM - 6:00 AM 113 |
114 |
118 | 6:00 AM - 7:00 AM 119 |
120 |
124 | 7:00 AM - 8:00 AM 125 |
126 |
130 | 8:00 AM - 9:00 AM 131 |
132 |
136 | 9:00 AM - 10:00 AM 137 |
138 |
142 | 10:00 AM - 11:00 AM 143 |
144 |
148 | 11:00 AM - 12:00 PM 149 |
150 |
154 | 12:00 PM - 1:00 PM 155 |
156 |
160 | 1:00 PM - 2:00 PM 161 |
162 |
166 | 2:00 PM - 3:00 PM 167 |
168 |
172 | 3:00 PM - 4:00 PM 173 |
174 |
178 | 4:00 PM - 5:00 PM 179 |
180 |
184 | 5:00 PM - 6:00 PM 185 |
186 |
190 | 6:00 PM - 7:00 PM 191 |
192 |
196 | 7:00 PM - 8:00 PM 197 |
198 |
202 | 8:00 PM - 9:00 PM 203 |
204 |
208 | 9:00 PM - 10:00 PM 209 |
210 |
214 | 10:00 PM - 11:00 PM 215 |
216 |
220 | 11:00 PM - 12:00 AM 221 |
222 |
223 | `; 224 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/Month.react-test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Render tests Renders Correctly with all props. 1`] = ` 4 |
7 |
10 |
14 | ‹ 15 |
16 |
19 | Mar 26th - Apr 1st 20 |
21 |
25 | › 26 |
27 |
28 |
31 |
34 |
37 | 38 | Sunday (26) 39 | 40 |
41 |
45 | 12:00 AM - 1:00 AM 46 |
47 |
51 | 1:00 AM - 2:00 AM 52 |
53 |
57 | 2:00 AM - 3:00 AM 58 |
59 |
63 | 3:00 AM - 4:00 AM 64 |
65 |
69 | 4:00 AM - 5:00 AM 70 |
71 |
75 | 5:00 AM - 6:00 AM 76 |
77 |
81 | 6:00 AM - 7:00 AM 82 |
83 |
87 | 7:00 AM - 8:00 AM 88 |
89 |
93 | 8:00 AM - 9:00 AM 94 |
95 |
99 | 9:00 AM - 10:00 AM 100 |
101 |
105 | 10:00 AM - 11:00 AM 106 |
107 |
111 | 11:00 AM - 12:00 PM 112 |
113 |
117 | 12:00 PM - 1:00 PM 118 |
119 |
123 | 1:00 PM - 2:00 PM 124 |
125 |
129 | 2:00 PM - 3:00 PM 130 |
131 |
135 | 3:00 PM - 4:00 PM 136 |
137 |
141 | 4:00 PM - 5:00 PM 142 |
143 |
147 | 5:00 PM - 6:00 PM 148 |
149 |
153 | 6:00 PM - 7:00 PM 154 |
155 |
159 | 7:00 PM - 8:00 PM 160 |
161 |
165 | 8:00 PM - 9:00 PM 166 |
167 |
171 | 9:00 PM - 10:00 PM 172 |
173 |
177 | 10:00 PM - 11:00 PM 178 |
179 |
183 | 11:00 PM - 12:00 AM 184 |
185 |
186 |
189 |
192 | 193 | Monday (27) 194 | 195 |
196 |
200 | 12:00 AM - 1:00 AM 201 |
202 |
206 | 1:00 AM - 2:00 AM 207 |
208 |
212 | 2:00 AM - 3:00 AM 213 |
214 |
218 | 3:00 AM - 4:00 AM 219 |
220 |
224 | 4:00 AM - 5:00 AM 225 |
226 |
230 | 5:00 AM - 6:00 AM 231 |
232 |
236 | 6:00 AM - 7:00 AM 237 |
238 |
242 | 7:00 AM - 8:00 AM 243 |
244 |
248 | 8:00 AM - 9:00 AM 249 |
250 |
254 | 9:00 AM - 10:00 AM 255 |
256 |
260 | 10:00 AM - 11:00 AM 261 |
262 |
266 | 11:00 AM - 12:00 PM 267 |
268 |
272 | 12:00 PM - 1:00 PM 273 |
274 |
278 | 1:00 PM - 2:00 PM 279 |
280 |
284 | 2:00 PM - 3:00 PM 285 |
286 |
290 | 3:00 PM - 4:00 PM 291 |
292 |
296 | 4:00 PM - 5:00 PM 297 |
298 |
302 | 5:00 PM - 6:00 PM 303 |
304 |
308 | 6:00 PM - 7:00 PM 309 |
310 |
314 | 7:00 PM - 8:00 PM 315 |
316 |
320 | 8:00 PM - 9:00 PM 321 |
322 |
326 | 9:00 PM - 10:00 PM 327 |
328 |
332 | 10:00 PM - 11:00 PM 333 |
334 |
338 | 11:00 PM - 12:00 AM 339 |
340 |
341 |
344 |
347 | 348 | Tuesday (28) 349 | 350 |
351 |
355 | 12:00 AM - 1:00 AM 356 |
357 |
361 | 1:00 AM - 2:00 AM 362 |
363 |
367 | 2:00 AM - 3:00 AM 368 |
369 |
373 | 3:00 AM - 4:00 AM 374 |
375 |
379 | 4:00 AM - 5:00 AM 380 |
381 |
385 | 5:00 AM - 6:00 AM 386 |
387 |
391 | 6:00 AM - 7:00 AM 392 |
393 |
397 | 7:00 AM - 8:00 AM 398 |
399 |
403 | 8:00 AM - 9:00 AM 404 |
405 |
409 | 9:00 AM - 10:00 AM 410 |
411 |
415 | 10:00 AM - 11:00 AM 416 |
417 |
421 | 11:00 AM - 12:00 PM 422 |
423 |
427 | 12:00 PM - 1:00 PM 428 |
429 |
433 | 1:00 PM - 2:00 PM 434 |
435 |
439 | 2:00 PM - 3:00 PM 440 |
441 |
445 | 3:00 PM - 4:00 PM 446 |
447 |
451 | 4:00 PM - 5:00 PM 452 |
453 |
457 | 5:00 PM - 6:00 PM 458 |
459 |
463 | 6:00 PM - 7:00 PM 464 |
465 |
469 | 7:00 PM - 8:00 PM 470 |
471 |
475 | 8:00 PM - 9:00 PM 476 |
477 |
481 | 9:00 PM - 10:00 PM 482 |
483 |
487 | 10:00 PM - 11:00 PM 488 |
489 |
493 | 11:00 PM - 12:00 AM 494 |
495 |
496 |
499 |
502 | 503 | Wednesday (29) 504 | 505 |
506 |
510 | 12:00 AM - 1:00 AM 511 |
512 |
516 | 1:00 AM - 2:00 AM 517 |
518 |
522 | 2:00 AM - 3:00 AM 523 |
524 |
528 | 3:00 AM - 4:00 AM 529 |
530 |
534 | 4:00 AM - 5:00 AM 535 |
536 |
540 | 5:00 AM - 6:00 AM 541 |
542 |
546 | 6:00 AM - 7:00 AM 547 |
548 |
552 | 7:00 AM - 8:00 AM 553 |
554 |
558 | 8:00 AM - 9:00 AM 559 |
560 |
564 | 9:00 AM - 10:00 AM 565 |
566 |
570 | 10:00 AM - 11:00 AM 571 |
572 |
576 | 11:00 AM - 12:00 PM 577 |
578 |
582 | 12:00 PM - 1:00 PM 583 |
584 |
588 | 1:00 PM - 2:00 PM 589 |
590 |
594 | 2:00 PM - 3:00 PM 595 |
596 |
600 | 3:00 PM - 4:00 PM 601 |
602 |
606 | 4:00 PM - 5:00 PM 607 |
608 |
612 | 5:00 PM - 6:00 PM 613 |
614 |
618 | 6:00 PM - 7:00 PM 619 |
620 |
624 | 7:00 PM - 8:00 PM 625 |
626 |
630 | 8:00 PM - 9:00 PM 631 |
632 |
636 | 9:00 PM - 10:00 PM 637 |
638 |
642 | 10:00 PM - 11:00 PM 643 |
644 |
648 | 11:00 PM - 12:00 AM 649 |
650 |
651 |
654 |
657 | 658 | Thursday (30) 659 | 660 |
661 |
665 | 12:00 AM - 1:00 AM 666 |
667 |
671 | 1:00 AM - 2:00 AM 672 |
673 |
677 | 2:00 AM - 3:00 AM 678 |
679 |
683 | 3:00 AM - 4:00 AM 684 |
685 |
689 | 4:00 AM - 5:00 AM 690 |
691 |
695 | 5:00 AM - 6:00 AM 696 |
697 |
701 | 6:00 AM - 7:00 AM 702 |
703 |
707 | 7:00 AM - 8:00 AM 708 |
709 |
713 | 8:00 AM - 9:00 AM 714 |
715 |
719 | 9:00 AM - 10:00 AM 720 |
721 |
725 | 10:00 AM - 11:00 AM 726 |
727 |
731 | 11:00 AM - 12:00 PM 732 |
733 |
737 | 12:00 PM - 1:00 PM 738 |
739 |
743 | 1:00 PM - 2:00 PM 744 |
745 |
749 | 2:00 PM - 3:00 PM 750 |
751 |
755 | 3:00 PM - 4:00 PM 756 |
757 |
761 | 4:00 PM - 5:00 PM 762 |
763 |
767 | 5:00 PM - 6:00 PM 768 |
769 |
773 | 6:00 PM - 7:00 PM 774 |
775 |
779 | 7:00 PM - 8:00 PM 780 |
781 |
785 | 8:00 PM - 9:00 PM 786 |
787 |
791 | 9:00 PM - 10:00 PM 792 |
793 |
797 | 10:00 PM - 11:00 PM 798 |
799 |
803 | 11:00 PM - 12:00 AM 804 |
805 |
806 |
809 |
812 | 813 | Friday (31) 814 | 815 |
816 |
820 | 12:00 AM - 1:00 AM 821 |
822 |
826 | 1:00 AM - 2:00 AM 827 |
828 |
832 | 2:00 AM - 3:00 AM 833 |
834 |
838 | 3:00 AM - 4:00 AM 839 |
840 |
844 | 4:00 AM - 5:00 AM 845 |
846 |
850 | 5:00 AM - 6:00 AM 851 |
852 |
856 | 6:00 AM - 7:00 AM 857 |
858 |
862 | 7:00 AM - 8:00 AM 863 |
864 |
868 | 8:00 AM - 9:00 AM 869 |
870 |
874 | 9:00 AM - 10:00 AM 875 |
876 |
880 | 10:00 AM - 11:00 AM 881 |
882 |
886 | 11:00 AM - 12:00 PM 887 |
888 |
892 | 12:00 PM - 1:00 PM 893 |
894 |
898 | 1:00 PM - 2:00 PM 899 |
900 |
904 | 2:00 PM - 3:00 PM 905 |
906 |
910 | 3:00 PM - 4:00 PM 911 |
912 |
916 | 4:00 PM - 5:00 PM 917 |
918 |
922 | 5:00 PM - 6:00 PM 923 |
924 |
928 | 6:00 PM - 7:00 PM 929 |
930 |
934 | 7:00 PM - 8:00 PM 935 |
936 |
940 | 8:00 PM - 9:00 PM 941 |
942 |
946 | 9:00 PM - 10:00 PM 947 |
948 |
952 | 10:00 PM - 11:00 PM 953 |
954 |
958 | 11:00 PM - 12:00 AM 959 |
960 |
961 |
964 |
967 | 968 | Saturday (1) 969 | 970 |
971 |
975 | 12:00 AM - 1:00 AM 976 |
977 |
981 | 1:00 AM - 2:00 AM 982 |
983 |
987 | 2:00 AM - 3:00 AM 988 |
989 |
993 | 3:00 AM - 4:00 AM 994 |
995 |
999 | 4:00 AM - 5:00 AM 1000 |
1001 |
1005 | 5:00 AM - 6:00 AM 1006 |
1007 |
1011 | 6:00 AM - 7:00 AM 1012 |
1013 |
1017 | 7:00 AM - 8:00 AM 1018 |
1019 |
1023 | 8:00 AM - 9:00 AM 1024 |
1025 |
1029 | 9:00 AM - 10:00 AM 1030 |
1031 |
1035 | 10:00 AM - 11:00 AM 1036 |
1037 |
1041 | 11:00 AM - 12:00 PM 1042 |
1043 |
1047 | 12:00 PM - 1:00 PM 1048 |
1049 |
1053 | 1:00 PM - 2:00 PM 1054 |
1055 |
1059 | 2:00 PM - 3:00 PM 1060 |
1061 |
1065 | 3:00 PM - 4:00 PM 1066 |
1067 |
1071 | 4:00 PM - 5:00 PM 1072 |
1073 |
1077 | 5:00 PM - 6:00 PM 1078 |
1079 |
1083 | 6:00 PM - 7:00 PM 1084 |
1085 |
1089 | 7:00 PM - 8:00 PM 1090 |
1091 |
1095 | 8:00 PM - 9:00 PM 1096 |
1097 |
1101 | 9:00 PM - 10:00 PM 1102 |
1103 |
1107 | 10:00 PM - 11:00 PM 1108 |
1109 |
1113 | 11:00 PM - 12:00 AM 1114 |
1115 |
1116 |
1117 |
1118 | `; 1119 | 1120 | exports[`Render tests Renders Correctly with min props. 1`] = ` 1121 |
1124 |
1127 |
1131 | ‹ 1132 |
1133 |
1136 | Mar 26th - Apr 1st 1137 |
1138 |
1142 | › 1143 |
1144 |
1145 |
1148 |
1151 |
1154 | 1155 | Sunday (26) 1156 | 1157 |
1158 |
1162 | 12:00 AM - 1:00 AM 1163 |
1164 |
1168 | 1:00 AM - 2:00 AM 1169 |
1170 |
1174 | 2:00 AM - 3:00 AM 1175 |
1176 |
1180 | 3:00 AM - 4:00 AM 1181 |
1182 |
1186 | 4:00 AM - 5:00 AM 1187 |
1188 |
1192 | 5:00 AM - 6:00 AM 1193 |
1194 |
1198 | 6:00 AM - 7:00 AM 1199 |
1200 |
1204 | 7:00 AM - 8:00 AM 1205 |
1206 |
1210 | 8:00 AM - 9:00 AM 1211 |
1212 |
1216 | 9:00 AM - 10:00 AM 1217 |
1218 |
1222 | 10:00 AM - 11:00 AM 1223 |
1224 |
1228 | 11:00 AM - 12:00 PM 1229 |
1230 |
1234 | 12:00 PM - 1:00 PM 1235 |
1236 |
1240 | 1:00 PM - 2:00 PM 1241 |
1242 |
1246 | 2:00 PM - 3:00 PM 1247 |
1248 |
1252 | 3:00 PM - 4:00 PM 1253 |
1254 |
1258 | 4:00 PM - 5:00 PM 1259 |
1260 |
1264 | 5:00 PM - 6:00 PM 1265 |
1266 |
1270 | 6:00 PM - 7:00 PM 1271 |
1272 |
1276 | 7:00 PM - 8:00 PM 1277 |
1278 |
1282 | 8:00 PM - 9:00 PM 1283 |
1284 |
1288 | 9:00 PM - 10:00 PM 1289 |
1290 |
1294 | 10:00 PM - 11:00 PM 1295 |
1296 |
1300 | 11:00 PM - 12:00 AM 1301 |
1302 |
1303 |
1306 |
1309 | 1310 | Monday (27) 1311 | 1312 |
1313 |
1317 | 12:00 AM - 1:00 AM 1318 |
1319 |
1323 | 1:00 AM - 2:00 AM 1324 |
1325 |
1329 | 2:00 AM - 3:00 AM 1330 |
1331 |
1335 | 3:00 AM - 4:00 AM 1336 |
1337 |
1341 | 4:00 AM - 5:00 AM 1342 |
1343 |
1347 | 5:00 AM - 6:00 AM 1348 |
1349 |
1353 | 6:00 AM - 7:00 AM 1354 |
1355 |
1359 | 7:00 AM - 8:00 AM 1360 |
1361 |
1365 | 8:00 AM - 9:00 AM 1366 |
1367 |
1371 | 9:00 AM - 10:00 AM 1372 |
1373 |
1377 | 10:00 AM - 11:00 AM 1378 |
1379 |
1383 | 11:00 AM - 12:00 PM 1384 |
1385 |
1389 | 12:00 PM - 1:00 PM 1390 |
1391 |
1395 | 1:00 PM - 2:00 PM 1396 |
1397 |
1401 | 2:00 PM - 3:00 PM 1402 |
1403 |
1407 | 3:00 PM - 4:00 PM 1408 |
1409 |
1413 | 4:00 PM - 5:00 PM 1414 |
1415 |
1419 | 5:00 PM - 6:00 PM 1420 |
1421 |
1425 | 6:00 PM - 7:00 PM 1426 |
1427 |
1431 | 7:00 PM - 8:00 PM 1432 |
1433 |
1437 | 8:00 PM - 9:00 PM 1438 |
1439 |
1443 | 9:00 PM - 10:00 PM 1444 |
1445 |
1449 | 10:00 PM - 11:00 PM 1450 |
1451 |
1455 | 11:00 PM - 12:00 AM 1456 |
1457 |
1458 |
1461 |
1464 | 1465 | Tuesday (28) 1466 | 1467 |
1468 |
1472 | 12:00 AM - 1:00 AM 1473 |
1474 |
1478 | 1:00 AM - 2:00 AM 1479 |
1480 |
1484 | 2:00 AM - 3:00 AM 1485 |
1486 |
1490 | 3:00 AM - 4:00 AM 1491 |
1492 |
1496 | 4:00 AM - 5:00 AM 1497 |
1498 |
1502 | 5:00 AM - 6:00 AM 1503 |
1504 |
1508 | 6:00 AM - 7:00 AM 1509 |
1510 |
1514 | 7:00 AM - 8:00 AM 1515 |
1516 |
1520 | 8:00 AM - 9:00 AM 1521 |
1522 |
1526 | 9:00 AM - 10:00 AM 1527 |
1528 |
1532 | 10:00 AM - 11:00 AM 1533 |
1534 |
1538 | 11:00 AM - 12:00 PM 1539 |
1540 |
1544 | 12:00 PM - 1:00 PM 1545 |
1546 |
1550 | 1:00 PM - 2:00 PM 1551 |
1552 |
1556 | 2:00 PM - 3:00 PM 1557 |
1558 |
1562 | 3:00 PM - 4:00 PM 1563 |
1564 |
1568 | 4:00 PM - 5:00 PM 1569 |
1570 |
1574 | 5:00 PM - 6:00 PM 1575 |
1576 |
1580 | 6:00 PM - 7:00 PM 1581 |
1582 |
1586 | 7:00 PM - 8:00 PM 1587 |
1588 |
1592 | 8:00 PM - 9:00 PM 1593 |
1594 |
1598 | 9:00 PM - 10:00 PM 1599 |
1600 |
1604 | 10:00 PM - 11:00 PM 1605 |
1606 |
1610 | 11:00 PM - 12:00 AM 1611 |
1612 |
1613 |
1616 |
1619 | 1620 | Wednesday (29) 1621 | 1622 |
1623 |
1627 | 12:00 AM - 1:00 AM 1628 |
1629 |
1633 | 1:00 AM - 2:00 AM 1634 |
1635 |
1639 | 2:00 AM - 3:00 AM 1640 |
1641 |
1645 | 3:00 AM - 4:00 AM 1646 |
1647 |
1651 | 4:00 AM - 5:00 AM 1652 |
1653 |
1657 | 5:00 AM - 6:00 AM 1658 |
1659 |
1663 | 6:00 AM - 7:00 AM 1664 |
1665 |
1669 | 7:00 AM - 8:00 AM 1670 |
1671 |
1675 | 8:00 AM - 9:00 AM 1676 |
1677 |
1681 | 9:00 AM - 10:00 AM 1682 |
1683 |
1687 | 10:00 AM - 11:00 AM 1688 |
1689 |
1693 | 11:00 AM - 12:00 PM 1694 |
1695 |
1699 | 12:00 PM - 1:00 PM 1700 |
1701 |
1705 | 1:00 PM - 2:00 PM 1706 |
1707 |
1711 | 2:00 PM - 3:00 PM 1712 |
1713 |
1717 | 3:00 PM - 4:00 PM 1718 |
1719 |
1723 | 4:00 PM - 5:00 PM 1724 |
1725 |
1729 | 5:00 PM - 6:00 PM 1730 |
1731 |
1735 | 6:00 PM - 7:00 PM 1736 |
1737 |
1741 | 7:00 PM - 8:00 PM 1742 |
1743 |
1747 | 8:00 PM - 9:00 PM 1748 |
1749 |
1753 | 9:00 PM - 10:00 PM 1754 |
1755 |
1759 | 10:00 PM - 11:00 PM 1760 |
1761 |
1765 | 11:00 PM - 12:00 AM 1766 |
1767 |
1768 |
1771 |
1774 | 1775 | Thursday (30) 1776 | 1777 |
1778 |
1782 | 12:00 AM - 1:00 AM 1783 |
1784 |
1788 | 1:00 AM - 2:00 AM 1789 |
1790 |
1794 | 2:00 AM - 3:00 AM 1795 |
1796 |
1800 | 3:00 AM - 4:00 AM 1801 |
1802 |
1806 | 4:00 AM - 5:00 AM 1807 |
1808 |
1812 | 5:00 AM - 6:00 AM 1813 |
1814 |
1818 | 6:00 AM - 7:00 AM 1819 |
1820 |
1824 | 7:00 AM - 8:00 AM 1825 |
1826 |
1830 | 8:00 AM - 9:00 AM 1831 |
1832 |
1836 | 9:00 AM - 10:00 AM 1837 |
1838 |
1842 | 10:00 AM - 11:00 AM 1843 |
1844 |
1848 | 11:00 AM - 12:00 PM 1849 |
1850 |
1854 | 12:00 PM - 1:00 PM 1855 |
1856 |
1860 | 1:00 PM - 2:00 PM 1861 |
1862 |
1866 | 2:00 PM - 3:00 PM 1867 |
1868 |
1872 | 3:00 PM - 4:00 PM 1873 |
1874 |
1878 | 4:00 PM - 5:00 PM 1879 |
1880 |
1884 | 5:00 PM - 6:00 PM 1885 |
1886 |
1890 | 6:00 PM - 7:00 PM 1891 |
1892 |
1896 | 7:00 PM - 8:00 PM 1897 |
1898 |
1902 | 8:00 PM - 9:00 PM 1903 |
1904 |
1908 | 9:00 PM - 10:00 PM 1909 |
1910 |
1914 | 10:00 PM - 11:00 PM 1915 |
1916 |
1920 | 11:00 PM - 12:00 AM 1921 |
1922 |
1923 |
1926 |
1929 | 1930 | Friday (31) 1931 | 1932 |
1933 |
1937 | 12:00 AM - 1:00 AM 1938 |
1939 |
1943 | 1:00 AM - 2:00 AM 1944 |
1945 |
1949 | 2:00 AM - 3:00 AM 1950 |
1951 |
1955 | 3:00 AM - 4:00 AM 1956 |
1957 |
1961 | 4:00 AM - 5:00 AM 1962 |
1963 |
1967 | 5:00 AM - 6:00 AM 1968 |
1969 |
1973 | 6:00 AM - 7:00 AM 1974 |
1975 |
1979 | 7:00 AM - 8:00 AM 1980 |
1981 |
1985 | 8:00 AM - 9:00 AM 1986 |
1987 |
1991 | 9:00 AM - 10:00 AM 1992 |
1993 |
1997 | 10:00 AM - 11:00 AM 1998 |
1999 |
2003 | 11:00 AM - 12:00 PM 2004 |
2005 |
2009 | 12:00 PM - 1:00 PM 2010 |
2011 |
2015 | 1:00 PM - 2:00 PM 2016 |
2017 |
2021 | 2:00 PM - 3:00 PM 2022 |
2023 |
2027 | 3:00 PM - 4:00 PM 2028 |
2029 |
2033 | 4:00 PM - 5:00 PM 2034 |
2035 |
2039 | 5:00 PM - 6:00 PM 2040 |
2041 |
2045 | 6:00 PM - 7:00 PM 2046 |
2047 |
2051 | 7:00 PM - 8:00 PM 2052 |
2053 |
2057 | 8:00 PM - 9:00 PM 2058 |
2059 |
2063 | 9:00 PM - 10:00 PM 2064 |
2065 |
2069 | 10:00 PM - 11:00 PM 2070 |
2071 |
2075 | 11:00 PM - 12:00 AM 2076 |
2077 |
2078 |
2081 |
2084 | 2085 | Saturday (1) 2086 | 2087 |
2088 |
2092 | 12:00 AM - 1:00 AM 2093 |
2094 |
2098 | 1:00 AM - 2:00 AM 2099 |
2100 |
2104 | 2:00 AM - 3:00 AM 2105 |
2106 |
2110 | 3:00 AM - 4:00 AM 2111 |
2112 |
2116 | 4:00 AM - 5:00 AM 2117 |
2118 |
2122 | 5:00 AM - 6:00 AM 2123 |
2124 |
2128 | 6:00 AM - 7:00 AM 2129 |
2130 |
2134 | 7:00 AM - 8:00 AM 2135 |
2136 |
2140 | 8:00 AM - 9:00 AM 2141 |
2142 |
2146 | 9:00 AM - 10:00 AM 2147 |
2148 |
2152 | 10:00 AM - 11:00 AM 2153 |
2154 |
2158 | 11:00 AM - 12:00 PM 2159 |
2160 |
2164 | 12:00 PM - 1:00 PM 2165 |
2166 |
2170 | 1:00 PM - 2:00 PM 2171 |
2172 |
2176 | 2:00 PM - 3:00 PM 2177 |
2178 |
2182 | 3:00 PM - 4:00 PM 2183 |
2184 |
2188 | 4:00 PM - 5:00 PM 2189 |
2190 |
2194 | 5:00 PM - 6:00 PM 2195 |
2196 |
2200 | 6:00 PM - 7:00 PM 2201 |
2202 |
2206 | 7:00 PM - 8:00 PM 2207 |
2208 |
2212 | 8:00 PM - 9:00 PM 2213 |
2214 |
2218 | 9:00 PM - 10:00 PM 2219 |
2220 |
2224 | 10:00 PM - 11:00 PM 2225 |
2226 |
2230 | 11:00 PM - 12:00 AM 2231 |
2232 |
2233 |
2234 |
2235 | `; 2236 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/Timeslot.react-test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Renders Correctly with All props. 1`] = ` 4 |
8 | 1:00 PM - 2:00 AM 9 |
10 | `; 11 | 12 | exports[`Renders when customClassNames is null 1`] = ` 13 |
17 | 1:00 PM - 2:00 AM 18 |
19 | `; 20 | 21 | exports[`Renders when customClassNames prop is not provided 1`] = ` 22 |
26 | 1:00 PM - 2:00 AM 27 |
28 | `; 29 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/Week.react-test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Render tests Renders Correctly with min props. 1`] = ` 4 |
7 |
10 |
13 | 14 | Sunday (26) 15 | 16 |
17 |
21 | 12:00 AM - 1:00 AM 22 |
23 |
27 | 1:00 AM - 2:00 AM 28 |
29 |
33 | 2:00 AM - 3:00 AM 34 |
35 |
39 | 3:00 AM - 4:00 AM 40 |
41 |
45 | 4:00 AM - 5:00 AM 46 |
47 |
51 | 5:00 AM - 6:00 AM 52 |
53 |
57 | 6:00 AM - 7:00 AM 58 |
59 |
63 | 7:00 AM - 8:00 AM 64 |
65 |
69 | 8:00 AM - 9:00 AM 70 |
71 |
75 | 9:00 AM - 10:00 AM 76 |
77 |
81 | 10:00 AM - 11:00 AM 82 |
83 |
87 | 11:00 AM - 12:00 PM 88 |
89 |
93 | 12:00 PM - 1:00 PM 94 |
95 |
99 | 1:00 PM - 2:00 PM 100 |
101 |
105 | 2:00 PM - 3:00 PM 106 |
107 |
111 | 3:00 PM - 4:00 PM 112 |
113 |
117 | 4:00 PM - 5:00 PM 118 |
119 |
123 | 5:00 PM - 6:00 PM 124 |
125 |
129 | 6:00 PM - 7:00 PM 130 |
131 |
135 | 7:00 PM - 8:00 PM 136 |
137 |
141 | 8:00 PM - 9:00 PM 142 |
143 |
147 | 9:00 PM - 10:00 PM 148 |
149 |
153 | 10:00 PM - 11:00 PM 154 |
155 |
159 | 11:00 PM - 12:00 AM 160 |
161 |
162 |
165 |
168 | 169 | Monday (27) 170 | 171 |
172 |
176 | 12:00 AM - 1:00 AM 177 |
178 |
182 | 1:00 AM - 2:00 AM 183 |
184 |
188 | 2:00 AM - 3:00 AM 189 |
190 |
194 | 3:00 AM - 4:00 AM 195 |
196 |
200 | 4:00 AM - 5:00 AM 201 |
202 |
206 | 5:00 AM - 6:00 AM 207 |
208 |
212 | 6:00 AM - 7:00 AM 213 |
214 |
218 | 7:00 AM - 8:00 AM 219 |
220 |
224 | 8:00 AM - 9:00 AM 225 |
226 |
230 | 9:00 AM - 10:00 AM 231 |
232 |
236 | 10:00 AM - 11:00 AM 237 |
238 |
242 | 11:00 AM - 12:00 PM 243 |
244 |
248 | 12:00 PM - 1:00 PM 249 |
250 |
254 | 1:00 PM - 2:00 PM 255 |
256 |
260 | 2:00 PM - 3:00 PM 261 |
262 |
266 | 3:00 PM - 4:00 PM 267 |
268 |
272 | 4:00 PM - 5:00 PM 273 |
274 |
278 | 5:00 PM - 6:00 PM 279 |
280 |
284 | 6:00 PM - 7:00 PM 285 |
286 |
290 | 7:00 PM - 8:00 PM 291 |
292 |
296 | 8:00 PM - 9:00 PM 297 |
298 |
302 | 9:00 PM - 10:00 PM 303 |
304 |
308 | 10:00 PM - 11:00 PM 309 |
310 |
314 | 11:00 PM - 12:00 AM 315 |
316 |
317 |
320 |
323 | 324 | Tuesday (28) 325 | 326 |
327 |
331 | 12:00 AM - 1:00 AM 332 |
333 |
337 | 1:00 AM - 2:00 AM 338 |
339 |
343 | 2:00 AM - 3:00 AM 344 |
345 |
349 | 3:00 AM - 4:00 AM 350 |
351 |
355 | 4:00 AM - 5:00 AM 356 |
357 |
361 | 5:00 AM - 6:00 AM 362 |
363 |
367 | 6:00 AM - 7:00 AM 368 |
369 |
373 | 7:00 AM - 8:00 AM 374 |
375 |
379 | 8:00 AM - 9:00 AM 380 |
381 |
385 | 9:00 AM - 10:00 AM 386 |
387 |
391 | 10:00 AM - 11:00 AM 392 |
393 |
397 | 11:00 AM - 12:00 PM 398 |
399 |
403 | 12:00 PM - 1:00 PM 404 |
405 |
409 | 1:00 PM - 2:00 PM 410 |
411 |
415 | 2:00 PM - 3:00 PM 416 |
417 |
421 | 3:00 PM - 4:00 PM 422 |
423 |
427 | 4:00 PM - 5:00 PM 428 |
429 |
433 | 5:00 PM - 6:00 PM 434 |
435 |
439 | 6:00 PM - 7:00 PM 440 |
441 |
445 | 7:00 PM - 8:00 PM 446 |
447 |
451 | 8:00 PM - 9:00 PM 452 |
453 |
457 | 9:00 PM - 10:00 PM 458 |
459 |
463 | 10:00 PM - 11:00 PM 464 |
465 |
469 | 11:00 PM - 12:00 AM 470 |
471 |
472 |
475 |
478 | 479 | Wednesday (29) 480 | 481 |
482 |
486 | 12:00 AM - 1:00 AM 487 |
488 |
492 | 1:00 AM - 2:00 AM 493 |
494 |
498 | 2:00 AM - 3:00 AM 499 |
500 |
504 | 3:00 AM - 4:00 AM 505 |
506 |
510 | 4:00 AM - 5:00 AM 511 |
512 |
516 | 5:00 AM - 6:00 AM 517 |
518 |
522 | 6:00 AM - 7:00 AM 523 |
524 |
528 | 7:00 AM - 8:00 AM 529 |
530 |
534 | 8:00 AM - 9:00 AM 535 |
536 |
540 | 9:00 AM - 10:00 AM 541 |
542 |
546 | 10:00 AM - 11:00 AM 547 |
548 |
552 | 11:00 AM - 12:00 PM 553 |
554 |
558 | 12:00 PM - 1:00 PM 559 |
560 |
564 | 1:00 PM - 2:00 PM 565 |
566 |
570 | 2:00 PM - 3:00 PM 571 |
572 |
576 | 3:00 PM - 4:00 PM 577 |
578 |
582 | 4:00 PM - 5:00 PM 583 |
584 |
588 | 5:00 PM - 6:00 PM 589 |
590 |
594 | 6:00 PM - 7:00 PM 595 |
596 |
600 | 7:00 PM - 8:00 PM 601 |
602 |
606 | 8:00 PM - 9:00 PM 607 |
608 |
612 | 9:00 PM - 10:00 PM 613 |
614 |
618 | 10:00 PM - 11:00 PM 619 |
620 |
624 | 11:00 PM - 12:00 AM 625 |
626 |
627 |
630 |
633 | 634 | Thursday (30) 635 | 636 |
637 |
641 | 12:00 AM - 1:00 AM 642 |
643 |
647 | 1:00 AM - 2:00 AM 648 |
649 |
653 | 2:00 AM - 3:00 AM 654 |
655 |
659 | 3:00 AM - 4:00 AM 660 |
661 |
665 | 4:00 AM - 5:00 AM 666 |
667 |
671 | 5:00 AM - 6:00 AM 672 |
673 |
677 | 6:00 AM - 7:00 AM 678 |
679 |
683 | 7:00 AM - 8:00 AM 684 |
685 |
689 | 8:00 AM - 9:00 AM 690 |
691 |
695 | 9:00 AM - 10:00 AM 696 |
697 |
701 | 10:00 AM - 11:00 AM 702 |
703 |
707 | 11:00 AM - 12:00 PM 708 |
709 |
713 | 12:00 PM - 1:00 PM 714 |
715 |
719 | 1:00 PM - 2:00 PM 720 |
721 |
725 | 2:00 PM - 3:00 PM 726 |
727 |
731 | 3:00 PM - 4:00 PM 732 |
733 |
737 | 4:00 PM - 5:00 PM 738 |
739 |
743 | 5:00 PM - 6:00 PM 744 |
745 |
749 | 6:00 PM - 7:00 PM 750 |
751 |
755 | 7:00 PM - 8:00 PM 756 |
757 |
761 | 8:00 PM - 9:00 PM 762 |
763 |
767 | 9:00 PM - 10:00 PM 768 |
769 |
773 | 10:00 PM - 11:00 PM 774 |
775 |
779 | 11:00 PM - 12:00 AM 780 |
781 |
782 |
785 |
788 | 789 | Friday (31) 790 | 791 |
792 |
796 | 12:00 AM - 1:00 AM 797 |
798 |
802 | 1:00 AM - 2:00 AM 803 |
804 |
808 | 2:00 AM - 3:00 AM 809 |
810 |
814 | 3:00 AM - 4:00 AM 815 |
816 |
820 | 4:00 AM - 5:00 AM 821 |
822 |
826 | 5:00 AM - 6:00 AM 827 |
828 |
832 | 6:00 AM - 7:00 AM 833 |
834 |
838 | 7:00 AM - 8:00 AM 839 |
840 |
844 | 8:00 AM - 9:00 AM 845 |
846 |
850 | 9:00 AM - 10:00 AM 851 |
852 |
856 | 10:00 AM - 11:00 AM 857 |
858 |
862 | 11:00 AM - 12:00 PM 863 |
864 |
868 | 12:00 PM - 1:00 PM 869 |
870 |
874 | 1:00 PM - 2:00 PM 875 |
876 |
880 | 2:00 PM - 3:00 PM 881 |
882 |
886 | 3:00 PM - 4:00 PM 887 |
888 |
892 | 4:00 PM - 5:00 PM 893 |
894 |
898 | 5:00 PM - 6:00 PM 899 |
900 |
904 | 6:00 PM - 7:00 PM 905 |
906 |
910 | 7:00 PM - 8:00 PM 911 |
912 |
916 | 8:00 PM - 9:00 PM 917 |
918 |
922 | 9:00 PM - 10:00 PM 923 |
924 |
928 | 10:00 PM - 11:00 PM 929 |
930 |
934 | 11:00 PM - 12:00 AM 935 |
936 |
937 |
940 |
943 | 944 | Saturday (1) 945 | 946 |
947 |
951 | 12:00 AM - 1:00 AM 952 |
953 |
957 | 1:00 AM - 2:00 AM 958 |
959 |
963 | 2:00 AM - 3:00 AM 964 |
965 |
969 | 3:00 AM - 4:00 AM 970 |
971 |
975 | 4:00 AM - 5:00 AM 976 |
977 |
981 | 5:00 AM - 6:00 AM 982 |
983 |
987 | 6:00 AM - 7:00 AM 988 |
989 |
993 | 7:00 AM - 8:00 AM 994 |
995 |
999 | 8:00 AM - 9:00 AM 1000 |
1001 |
1005 | 9:00 AM - 10:00 AM 1006 |
1007 |
1011 | 10:00 AM - 11:00 AM 1012 |
1013 |
1017 | 11:00 AM - 12:00 PM 1018 |
1019 |
1023 | 12:00 PM - 1:00 PM 1024 |
1025 |
1029 | 1:00 PM - 2:00 PM 1030 |
1031 |
1035 | 2:00 PM - 3:00 PM 1036 |
1037 |
1041 | 3:00 PM - 4:00 PM 1042 |
1043 |
1047 | 4:00 PM - 5:00 PM 1048 |
1049 |
1053 | 5:00 PM - 6:00 PM 1054 |
1055 |
1059 | 6:00 PM - 7:00 PM 1060 |
1061 |
1065 | 7:00 PM - 8:00 PM 1066 |
1067 |
1071 | 8:00 PM - 9:00 PM 1072 |
1073 |
1077 | 9:00 PM - 10:00 PM 1078 |
1079 |
1083 | 10:00 PM - 11:00 PM 1084 |
1085 |
1089 | 11:00 PM - 12:00 AM 1090 |
1091 |
1092 |
1093 | `; 1094 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React Timeslot Calendar Demo App 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-timeslot-calendar", 3 | "version": "0.2.0", 4 | "description": "A calendar based on timeslots which can be set as available, occupied and so on.", 5 | "main": "build/build.min.js", 6 | "scripts": { 7 | "build": "npm run eslint && npm run prod", 8 | "eslint": "./node_modules/.bin/eslint src/js/**/*.jsx", 9 | "prod": "./node_modules/.bin/webpack --config webpack.config.js -p", 10 | "analyze": "./node_modules/.bin/webpack --config webpack.config.js --json -p | ./node_modules/.bin/webpack-bundle-size-analyzer", 11 | "dev": "./node_modules/.bin/webpack-dev-server --config webpack.demo.config.js --hot", 12 | "demo": "./node_modules/.bin/webpack --config webpack.demo.config.js", 13 | "test": "npm run eslint && jest --verbose -e", 14 | "prepublish": "npm run build" 15 | }, 16 | "keywords": [ 17 | "react", 18 | "calendar", 19 | "timeslot", 20 | "scheduling", 21 | "schedule" 22 | ], 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/lrojas94/react-timeslot-calendar" 26 | }, 27 | "author": "Luis Rojas & Jaime Rojas", 28 | "license": "MIT", 29 | "devDependencies": { 30 | "babel-core": "^6.24.1", 31 | "babel-jest": "^20.0.3", 32 | "babel-loader": "^7.0.0", 33 | "babel-preset-es2015": "^6.24.1", 34 | "babel-preset-react": "^6.24.1", 35 | "classnames": "^2.2.5", 36 | "css-loader": "^0.28.4", 37 | "enzyme": "^2.8.2", 38 | "eslint": "^3.19.0", 39 | "eslint-plugin-jest": "^20.0.3", 40 | "eslint-plugin-react": "^7.0.1", 41 | "highlight.js": "^9.12.0", 42 | "html-loader": "^0.4.5", 43 | "jest": "^20.0.4", 44 | "markdown-loader": "^2.0.0", 45 | "marked": "^0.3.6", 46 | "node-sass": "^4.5.3", 47 | "prop-types": "^15.5.10", 48 | "react": "^15.5.4", 49 | "react-dom": "^15.5.4", 50 | "react-test-renderer": "^15.5.4", 51 | "sass-loader": "^6.0.5", 52 | "sinon": "^2.3.2", 53 | "style-loader": "^0.18.1", 54 | "webpack": "^2.6.1", 55 | "webpack-bundle-size-analyzer": "^2.7.0", 56 | "webpack-dev-server": "^2.4.5" 57 | }, 58 | "dependencies": { 59 | "calendarjs": "^0.1.0", 60 | "moment": "^2.18.1" 61 | }, 62 | "peerDependencies": { 63 | "react": "^15.5.4" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /public/atom-one-dark.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Atom One Dark by Daniel Gamage 4 | Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax 5 | 6 | base: #282c34 7 | mono-1: #abb2bf 8 | mono-2: #818896 9 | mono-3: #5c6370 10 | hue-1: #56b6c2 11 | hue-2: #61aeee 12 | hue-3: #c678dd 13 | hue-4: #98c379 14 | hue-5: #e06c75 15 | hue-5-2: #be5046 16 | hue-6: #d19a66 17 | hue-6-2: #e6c07b 18 | 19 | */ 20 | 21 | .hljs { 22 | display: block; 23 | overflow-x: auto; 24 | padding: 0.5em; 25 | color: #abb2bf; 26 | background: #282c34; 27 | } 28 | 29 | .hljs-comment, 30 | .hljs-quote { 31 | color: #5c6370; 32 | font-style: italic; 33 | } 34 | 35 | .hljs-doctag, 36 | .hljs-keyword, 37 | .hljs-formula { 38 | color: #c678dd; 39 | } 40 | 41 | .hljs-section, 42 | .hljs-name, 43 | .hljs-selector-tag, 44 | .hljs-deletion, 45 | .hljs-subst { 46 | color: #e06c75; 47 | } 48 | 49 | .hljs-literal { 50 | color: #56b6c2; 51 | } 52 | 53 | .hljs-string, 54 | .hljs-regexp, 55 | .hljs-addition, 56 | .hljs-attribute, 57 | .hljs-meta-string { 58 | color: #98c379; 59 | } 60 | 61 | .hljs-built_in, 62 | .hljs-class .hljs-title { 63 | color: #e6c07b; 64 | } 65 | 66 | .hljs-attr, 67 | .hljs-variable, 68 | .hljs-template-variable, 69 | .hljs-type, 70 | .hljs-selector-class, 71 | .hljs-selector-attr, 72 | .hljs-selector-pseudo, 73 | .hljs-number { 74 | color: #d19a66; 75 | } 76 | 77 | .hljs-symbol, 78 | .hljs-bullet, 79 | .hljs-link, 80 | .hljs-meta, 81 | .hljs-selector-id, 82 | .hljs-title { 83 | color: #61aeee; 84 | } 85 | 86 | .hljs-emphasis { 87 | font-style: italic; 88 | } 89 | 90 | .hljs-strong { 91 | font-weight: bold; 92 | } 93 | 94 | .hljs-link { 95 | text-decoration: underline; 96 | } 97 | -------------------------------------------------------------------------------- /src/js/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lrojas94/react-timeslot-calendar/04d98812448b8e3f93400c59e05707feb3ba72b2/src/js/components/.gitkeep -------------------------------------------------------------------------------- /src/js/components/calendar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import moment from 'moment'; 4 | import CalendarJS from 'calendarjs'; 5 | import Month from './month.jsx'; 6 | 7 | export default class Calendar extends React.Component { 8 | constructor(props) { 9 | 10 | super(props); 11 | 12 | this._updateInputProps(this.props.startDateInputProps, this.props.endDateInputProps); 13 | this._updateTimeslotProps(this.props.timeslotProps); 14 | this._updateRenderDays(this.props.renderDays); 15 | 16 | this.state = { 17 | currentDate: moment(props.initialDate), 18 | selectedTimeslots: [], 19 | }; 20 | } 21 | 22 | render() { 23 | return ( 24 |
25 | { this._renderActions() } 26 | { this._renderMonth() } 27 | { this._renderInputs() } 28 |
29 | ); 30 | } 31 | 32 | _renderActions() { 33 | const { 34 | currentDate, 35 | } = this.state; 36 | 37 | const actionTitle = `${currentDate.format('MMMM - YYYY')}`; 38 | 39 | return ( 40 |
41 |
42 | ‹ 43 |
44 |
45 | { actionTitle } 46 |
47 |
48 | › 49 |
50 |
51 | ); 52 | } 53 | 54 | _renderMonth() { 55 | const { 56 | currentDate, 57 | selectedTimeslots, 58 | } = this.state; 59 | 60 | const { 61 | timeslots, 62 | initialDate, 63 | } = this.props; 64 | 65 | const cal = new CalendarJS(currentDate.year(), currentDate.month() + 1); 66 | const weeks = cal.generate(); 67 | 68 | return ( 69 | 81 | ); 82 | } 83 | 84 | _renderInputs() { 85 | const { 86 | selectedTimeslots, 87 | } = this.state; 88 | 89 | const { 90 | startDate, 91 | endDate, 92 | } = this.inputProps; 93 | 94 | //Determines if multiple input or single one. 95 | const inputPrefix = selectedTimeslots.length > 1 ? '[]' : ''; 96 | 97 | return selectedTimeslots.map((timeslot, index) => { 98 | return ( 99 |
100 | 106 | 112 |
113 | ); 114 | }); 115 | } 116 | 117 | _onWeekOutOfMonth(updateDate) { 118 | this.setState({ 119 | currentDate: updateDate, 120 | }); 121 | 122 | return; 123 | } 124 | 125 | _onGoToNextMonth() { 126 | const { 127 | currentDate, 128 | } = this.state; 129 | 130 | let nextDate = currentDate.clone() 131 | .startOf('month') 132 | .add(1, 'months') 133 | .startOf('month'); 134 | 135 | this.setState({ 136 | currentDate: nextDate, 137 | }); 138 | } 139 | 140 | _onGoToPrevMonth() { 141 | const { 142 | currentDate, 143 | } = this.state; 144 | 145 | let nextDate = currentDate.clone() 146 | .startOf('month') 147 | .subtract(1, 'months') 148 | .startOf('month'); 149 | 150 | this.setState({ 151 | currentDate: nextDate, 152 | }); 153 | } 154 | 155 | _formatDisabledTimeslots() { 156 | const { 157 | disabledTimeslots, 158 | } = this.props; 159 | 160 | return disabledTimeslots.map((timeslot) => { 161 | let timeslotMoment = Object.assign({}, timeslot); 162 | timeslotMoment.startDate = moment(timeslotMoment.startDate, timeslotMoment.format); 163 | timeslotMoment.endDate = moment(timeslotMoment.endDate, timeslotMoment.format); 164 | 165 | return timeslotMoment; 166 | }); 167 | } 168 | 169 | _onTimeslotClick(newTimeslot) { 170 | const { 171 | selectedTimeslots, 172 | } = this.state; 173 | 174 | const { 175 | maxTimeslots, 176 | onSelectTimeslot, 177 | } = this.props; 178 | 179 | const newSelectedTimeslots = selectedTimeslots.slice(); 180 | 181 | let existentTimeslotIndex = -1; 182 | const timeslotExists = newSelectedTimeslots.some((timeslot, index) => { 183 | existentTimeslotIndex = index; 184 | return newTimeslot.startDate.format() === timeslot.startDate.format(); 185 | }); 186 | 187 | if (timeslotExists) { 188 | newSelectedTimeslots.splice(existentTimeslotIndex, 1); 189 | } 190 | else { 191 | newSelectedTimeslots.push(newTimeslot); 192 | } 193 | 194 | if (newSelectedTimeslots.length > maxTimeslots) { 195 | newSelectedTimeslots.splice(0, 1); 196 | } 197 | 198 | this.setState({ 199 | selectedTimeslots: newSelectedTimeslots, 200 | currentDate: moment(newTimeslot.startDate), 201 | }, () => { 202 | // State was set: 203 | onSelectTimeslot && onSelectTimeslot(newSelectedTimeslots, newTimeslot); 204 | }); 205 | } 206 | 207 | _updateInputProps(startDateInputProps, endDateInputProps) { 208 | const defaultStartDateProps = { 209 | name: 'tsc-startDate', 210 | classes: 'tsc-hidden-input', 211 | type: 'hidden', 212 | }; 213 | 214 | const defaultEndDateProps = { 215 | name: 'tsc-endDate', 216 | classes: 'tsc-hidden-input', 217 | type: 'hidden', 218 | }; 219 | 220 | this.inputProps = { 221 | startDate: Object.assign({}, defaultStartDateProps, startDateInputProps), 222 | endDate: Object.assign({}, defaultEndDateProps, endDateInputProps), 223 | }; 224 | } 225 | 226 | _updateTimeslotProps(timeslotProps) { 227 | const defaultProps = { 228 | format: 'h', 229 | showFormat: 'h:mm A', 230 | }; 231 | 232 | this.timeslotProps = Object.assign({}, defaultProps, timeslotProps); 233 | } 234 | 235 | _updateRenderDays(renderDays) { 236 | const defaultRenderDays = { 237 | sunday: true, 238 | monday: true, 239 | tuesday: true, 240 | wednesday: true, 241 | thursday: true, 242 | friday: true, 243 | saturday: true, 244 | }; 245 | 246 | this.renderDays = Object.assign({}, defaultRenderDays, renderDays); 247 | } 248 | 249 | componentWillReceiveProps(nextProps) { 250 | this._updateInputProps(nextProps.startDateInputProps, nextProps.endDateInputProps); 251 | this._updateTimeslotProps(nextProps.timeslotProps); 252 | this._updateRenderDays(nextProps.renderDays); 253 | } 254 | 255 | } 256 | 257 | Calendar.defaultProps = { 258 | disabledTimeslots: [], 259 | maxTimeslots: 1, 260 | inputProps: { 261 | names: {}, 262 | }, 263 | startDateInputProps: {}, 264 | endDateInputProps: {}, 265 | }; 266 | 267 | /** 268 | * @type {String} initialDate: The initial date in which to place the calendar. Must be MomentJS parseable. 269 | * @type {Array} timeslots: An array of timeslots to be displayed in each day. 270 | * @type {Object} timeslotProps: An object with keys and values for timeslot props (format, viewFormat) 271 | * @type {Array} selectedTimeslots: Initial value for selected timeslot inputs. Expects Dates formatted as Strings. 272 | * @type {Array} disabledTimeslots: Initial value for selected timeslot inputs. Expects Dates formatted as Strings. 273 | * @type {Integer} maxTimexlots: maximum ammount of timeslots to select. 274 | * @type {Object} renderDays: An array of days which states which days of the week to render. By default renders all days. 275 | * @type {Object} startDateInputProps: properties for the startDate Inputs. Includes name, class, type (hidden, text...) 276 | * @type {Object} endDateInputProps: properties for the endDate Inputs. Includes name, class, type (hidden, text...) 277 | */ 278 | Calendar.propTypes = { 279 | initialDate: PropTypes.string.isRequired, 280 | timeslots: PropTypes.array.isRequired, 281 | timeslotProps: PropTypes.object, 282 | selectedTimeslots: PropTypes.array, 283 | disabledTimeslots: PropTypes.array, 284 | maxTimeslots: PropTypes.number, 285 | renderDays: PropTypes.object, 286 | startDateInputProps: PropTypes.object, 287 | endDateInputProps: PropTypes.object, 288 | onSelectTimeslot: PropTypes.func, 289 | }; 290 | -------------------------------------------------------------------------------- /src/js/components/day.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classnames from 'classnames'; 4 | import moment from 'moment'; 5 | import Timeslot from './timeslot.jsx'; 6 | 7 | import { 8 | DEFAULT_TIMESLOT_FORMAT, 9 | DEFAULT_TIMESLOT_SHOW_FORMAT, 10 | } from '../constants/day.js'; 11 | 12 | import { 13 | DEFAULT, 14 | DISABLED, 15 | SELECTED, 16 | } from '../constants/timeslot.js'; 17 | 18 | export default class Day extends React.Component { 19 | 20 | render() { 21 | const dayClassNames = classnames({ 22 | 'tsc-day': true, 23 | }); 24 | 25 | return ( 26 |
27 | { this._renderTitle() } 28 | { this._renderTimeSlots() } 29 |
30 | ); 31 | } 32 | 33 | _renderTitle() { 34 | const { 35 | renderTitle, 36 | momentTime, 37 | } = this.props; 38 | 39 | return ( 40 |
41 | {renderTitle(momentTime)} 42 |
43 | ); 44 | } 45 | 46 | _renderTimeSlots() { 47 | const { 48 | timeslots, 49 | timeslotProps, 50 | selectedTimeslots, 51 | disabledTimeslots, 52 | momentTime, 53 | initialDate, 54 | } = this.props; 55 | 56 | return timeslots.map((slot, index) => { 57 | let description = ''; 58 | for (let i = 0; i < slot.length; i ++){ 59 | description += moment(slot[i], timeslotProps.format).format(timeslotProps.showFormat); 60 | if (i < (slot.length - 1)){ 61 | description += ' - '; 62 | } 63 | } 64 | let timeslotDates = { 65 | startDate: momentTime.clone().add(slot[0], timeslotProps.format), 66 | endDate: momentTime.clone().add(slot[slot.length - 1], timeslotProps.format), 67 | }; 68 | 69 | let status = DEFAULT; 70 | if (timeslotDates.startDate.isBefore(initialDate) || timeslotDates.startDate.isSame(initialDate)) { 71 | status = DISABLED; 72 | } 73 | 74 | const isSelected = selectedTimeslots.some((selectedTimeslot) => { 75 | return timeslotDates.startDate.format() === selectedTimeslot.startDate.format(); 76 | }); 77 | 78 | const isDisabled = disabledTimeslots.some((disabledTimeslot) => { 79 | return disabledTimeslot.startDate.isBetween(timeslotDates.startDate, timeslotDates.endDate, null, '[)') || 80 | disabledTimeslot.endDate.isBetween(timeslotDates.startDate, timeslotDates.endDate, null, '(]'); 81 | }); 82 | 83 | if (isDisabled) { 84 | status = DISABLED; 85 | } 86 | else if (isSelected) { 87 | status = SELECTED; 88 | } 89 | 90 | 91 | return ( 92 | 98 | ); 99 | }); 100 | } 101 | 102 | _onTimeslotClick(index) { 103 | const { 104 | timeslots, 105 | timeslotFormat, 106 | momentTime, 107 | onTimeslotClick, 108 | } = this.props; 109 | 110 | const timeslot = { 111 | startDate: momentTime.clone().add(timeslots[index][0], timeslotFormat), 112 | endDate: momentTime.clone().add(timeslots[index][1], timeslotFormat), 113 | }; 114 | 115 | onTimeslotClick(timeslot); 116 | } 117 | } 118 | 119 | Day.defaultProps = { 120 | timeslotFormat: DEFAULT_TIMESLOT_FORMAT, 121 | timeslotShowFormat: DEFAULT_TIMESLOT_SHOW_FORMAT, 122 | renderTitle: (momentTime) => { 123 | return momentTime.format('dddd (D)'); 124 | }, 125 | }; 126 | 127 | /** 128 | * @type {Array} timeslots: Array of timeslots. 129 | * @type {Object} timeslotProps: An object with keys and values for timeslot props (format, viewFormat) 130 | * @type {Array} selectedTimeslots: Selected Timeslots Set used to add the SELECTED status if needed when renderizing timeslots. 131 | * @type {Array} disabledTimeslots: Disabled Timeslots Set used to add the DISABLED status if needed when renderizing timeslots. 132 | * @type {String} timeslotFormat: format used by moment when identifying the timeslot 133 | * @type {String} timslotShowFormat: format to show used by moment when formating timeslot hours for final view. 134 | * @type {Function} onTimeslotClick: Function to be excecuted when clicked. 135 | * @type {Function} renderTitle: Function to be used when rendering the title. 136 | * @type {Object} momentTime: MomentJS datetime object. 137 | * @type {Ojbect} initialDate: Moment JS Date used to initialize the Calendar and which progresses further into the tree. 138 | */ 139 | Day.propTypes = { 140 | timeslots: PropTypes.array.isRequired, 141 | timeslotProps: PropTypes.object, 142 | selectedTimeslots: PropTypes.array, 143 | disabledTimeslots: PropTypes.array, 144 | timeslotFormat: PropTypes.string.isRequired, 145 | timeslotShowFormat: PropTypes.string.isRequired, 146 | onTimeslotClick: PropTypes.func.isRequired, 147 | renderTitle: PropTypes.func.isRequired, 148 | momentTime: PropTypes.object.isRequired, 149 | initialDate: PropTypes.object.isRequired, 150 | }; 151 | -------------------------------------------------------------------------------- /src/js/components/month.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import helpers from './../util/helpers'; 4 | import Week from './week.jsx'; 5 | 6 | export default class Month extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | 10 | this.state = { 11 | currentWeekIndex: this._getStartingWeek(props.currentDate, props.weeks), 12 | }; 13 | } 14 | 15 | _getStartingWeek(currentDate, weeks) { 16 | // find out staring week: 17 | const currentDateWithoutTime = currentDate.startOf('day'); 18 | let startingWeek = 0; 19 | weeks.some((week, index) => { 20 | let weekContainsDate = week.some((day) => { 21 | const momentDay = helpers.getMomentFromCalendarJSDateElement(day); 22 | return momentDay.format() === currentDateWithoutTime.format(); 23 | }); 24 | 25 | if (weekContainsDate) { 26 | startingWeek = index; 27 | return weekContainsDate; 28 | } 29 | }); 30 | 31 | return startingWeek; 32 | } 33 | 34 | render() { 35 | 36 | return ( 37 |
38 | { this._renderActions() } 39 | { this._renderWeek() } 40 |
41 | ); 42 | } 43 | 44 | _renderActions() { 45 | const { 46 | weeks, 47 | } = this.props; 48 | 49 | const { 50 | currentWeekIndex, 51 | } = this.state; 52 | 53 | const currentWeek = weeks[currentWeekIndex]; 54 | const startDate = helpers.getMomentFromCalendarJSDateElement(currentWeek[0]); 55 | const endDate = helpers.getMomentFromCalendarJSDateElement(currentWeek[currentWeek.length - 1]); 56 | const actionTitle = `${startDate.format('MMM Do')} - ${endDate.format('MMM Do')}`; 57 | 58 | return ( 59 |
60 |
61 | ‹ 62 |
63 |
64 | { actionTitle } 65 |
66 |
67 | › 68 |
69 |
70 | ); 71 | } 72 | 73 | _renderWeek() { 74 | const { 75 | currentWeekIndex, 76 | } = this.state; 77 | 78 | const { 79 | weeks, 80 | initialDate, 81 | timeslots, 82 | timeslotProps, 83 | selectedTimeslots, 84 | disabledTimeslots, 85 | renderDays, 86 | } = this.props; 87 | 88 | return ( 89 | 99 | ); 100 | } 101 | 102 | _onTimeslotClick(timeslot) { 103 | const { 104 | onTimeslotClick, 105 | } = this.props; 106 | 107 | onTimeslotClick(timeslot); 108 | } 109 | 110 | /** 111 | * Handles prev week button click. 112 | */ 113 | _onPrevWeekClicked() { 114 | const { 115 | currentWeekIndex, 116 | } = this.state; 117 | 118 | const { 119 | onWeekOutOfMonth, 120 | weeks, 121 | } = this.props; 122 | 123 | if (currentWeekIndex - 1 >= 0) { 124 | this.setState({ 125 | currentWeekIndex: currentWeekIndex - 1, 126 | }); 127 | } 128 | else if (onWeekOutOfMonth) { 129 | const firstDayOfPrevWeek = helpers.getMomentFromCalendarJSDateElement(weeks[0][0]).clone().subtract(1, 'days'); 130 | onWeekOutOfMonth(firstDayOfPrevWeek); 131 | } 132 | } 133 | 134 | /** 135 | * Handles next week button click. 136 | */ 137 | _onNextWeekClicked() { 138 | const { 139 | currentWeekIndex, 140 | } = this.state; 141 | 142 | const { 143 | weeks, 144 | onWeekOutOfMonth, 145 | } = this.props; 146 | 147 | if (currentWeekIndex + 1 < weeks.length) { 148 | this.setState({ 149 | currentWeekIndex: currentWeekIndex + 1, 150 | }); 151 | } 152 | else if (onWeekOutOfMonth) { 153 | const lastDay = weeks[currentWeekIndex].length - 1; 154 | const firstDayOfNextWeek = helpers.getMomentFromCalendarJSDateElement(weeks[currentWeekIndex][lastDay]).clone().add(1, 'days'); 155 | onWeekOutOfMonth(firstDayOfNextWeek); 156 | } 157 | } 158 | 159 | componentWillReceiveProps(nextProps) { 160 | this.setState({ 161 | currentWeekIndex: this._getStartingWeek(nextProps.currentDate, nextProps.weeks), 162 | }); 163 | } 164 | } 165 | 166 | /** 167 | * @type {Object} currentDate: Base currentDate to get the month from - Usually first day of the month 168 | * @type {Array} weeks: A list of weeks based on calendarJS 169 | * @type {Function} onWeekOutOfMonth: A callback to call when user goes out of the month 170 | * @type {Function} onTimeslotClick: Function to be excecuted when clicked. 171 | * @type {Object} initialDate: Moment JS Date used to initialize the Calendar and which progresses further into the tree. 172 | * @type {Array} timeslots: An array of timeslots to be displayed in each day. 173 | * @type {Object} timeslotProps: An object with keys and values for timeslot props (format, viewFormat) 174 | * @type {Array} selectedTimeslots: Selected Timeslots Set used further into the tree to add the classes needed to when renderizing timeslots. 175 | * @type {Array} DisabledTimeslots: Disabled Timeslots Set used further into the tree to add the classes needed to when renderizing timeslots. 176 | * @type {Object} renderDays: An array of days which states which days of the week to render. By default renders all days. 177 | */ 178 | Month.propTypes = { 179 | currentDate: PropTypes.object.isRequired, 180 | weeks: PropTypes.array.isRequired, 181 | onWeekOutOfMonth: PropTypes.func, 182 | onTimeslotClick: PropTypes.func, 183 | initialDate: PropTypes.object.isRequired, 184 | timeslots: PropTypes.array.isRequired, 185 | timeslotProps: PropTypes.object, 186 | selectedTimeslots: PropTypes.array, 187 | disabledTimeslots: PropTypes.array, 188 | renderDays: PropTypes.object, 189 | }; 190 | -------------------------------------------------------------------------------- /src/js/components/timeslot.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classnames from 'classnames'; 4 | 5 | import { 6 | DEFAULT, 7 | SELECTED, 8 | DISABLED, 9 | } from '../constants/timeslot.js'; 10 | 11 | export default class Timeslot extends React.Component { 12 | render() { 13 | const { 14 | description, 15 | status, 16 | customClassNames, 17 | } = this.props; 18 | 19 | const timeslotClassNames = classnames({ 20 | 'tsc-timeslot': true, 21 | 'tsc-timeslot--selected': status == SELECTED, 22 | 'tsc-timeslot--disabled': status == DISABLED, 23 | }, customClassNames); 24 | 25 | return ( 26 |
27 | { description } 28 |
29 | ); 30 | } 31 | 32 | _onTimeslotClick(event) { 33 | event.preventDefault(); 34 | const { 35 | status, 36 | onClick, 37 | } = this.props; 38 | 39 | if (status !== DISABLED) { 40 | onClick(); 41 | } 42 | } 43 | } 44 | 45 | Timeslot.defaultProps = { 46 | status: DEFAULT, 47 | }; 48 | 49 | /** 50 | * @type {String} description: The contents to be displayed by the timeslot. Default format will resume to something similar to "7:00 PM - 8:00 PM" 51 | * @type {String} status: allows the div to change format based on the current status of the element (disabled, selected, default) 52 | * @type (Function) onClick: Function to be excecuted when clicked. 53 | */ 54 | Timeslot.propTypes = { 55 | description: PropTypes.string.isRequired, 56 | status: PropTypes.oneOf([ 57 | DEFAULT, 58 | SELECTED, 59 | DISABLED, 60 | ]), 61 | onClick: PropTypes.func.isRequired, 62 | customClassNames: PropTypes.oneOfType([ 63 | PropTypes.string, 64 | PropTypes.object, 65 | ]), 66 | }; 67 | -------------------------------------------------------------------------------- /src/js/components/week.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import helpers from './../util/helpers'; 4 | import Day from './day.jsx'; 5 | 6 | export default class Week extends React.Component { 7 | render() { 8 | 9 | return ( 10 |
11 | { this._renderWeekDays() } 12 |
13 | ); 14 | } 15 | 16 | _renderWeekDays() { 17 | const { 18 | weekToRender, 19 | initialDate, 20 | timeslots, 21 | timeslotProps, 22 | selectedTimeslots, 23 | disabledTimeslots, 24 | renderDays, 25 | } = this.props; 26 | 27 | return weekToRender.map((day, index) => { 28 | let formattedDate = helpers.getMomentFromCalendarJSDateElement(day); 29 | const weekDay = formattedDate.format('dddd').toLowerCase(); 30 | if (renderDays[weekDay]){ 31 | return ( 32 | 42 | ); 43 | } 44 | }); 45 | } 46 | 47 | _onTimeslotClick(timeslot) { 48 | const { 49 | onTimeslotClick, 50 | } = this.props; 51 | 52 | onTimeslotClick(timeslot); 53 | } 54 | } 55 | 56 | /** 57 | * @type {Array} weekToRender: Week to render. Each day should also have the requested timeslots, unless default configuration is desired. 58 | * @type {Function} onTimeslotClick: Function to be excecuted when clicked. 59 | * @type {Object} initialDate: Moment JS Date used to initialize the Calendar and which progresses further into the tree. 60 | * @type {Array} timeslots: Timeslots Set of Timeslot elements to render. Progresses further into the tree. 61 | * @type {Object} timeslotProps: An object with keys and values for timeslot props (format, viewFormat) 62 | * @type {Array} selectedTimeslots: Selected Timeslots Set used further into the tree to add the classes needed to when renderizing timeslots. 63 | * @type {Array} disabledTimeslots: Disabled Timeslots Set used further into the tree to add the classes needed to when renderizing timeslots. 64 | * @type {Object} renderDays: An array of days which states which days of the week to render. By default renders all days. 65 | */ 66 | Week.propTypes = { 67 | weekToRender: PropTypes.array.isRequired, 68 | onTimeslotClick: PropTypes.func.isRequired, 69 | initialDate: PropTypes.object.isRequired, 70 | timeslots : PropTypes.array.isRequired, 71 | timeslotProps: PropTypes.object, 72 | selectedTimeslots: PropTypes.array, 73 | disabledTimeslots: PropTypes.array, 74 | renderDays: PropTypes.object, 75 | }; 76 | -------------------------------------------------------------------------------- /src/js/constants/day.js: -------------------------------------------------------------------------------- 1 | export const DEFAULT_TIMESLOT_FORMAT = 'h'; 2 | export const DEFAULT_TIMESLOT_SHOW_FORMAT = 'h:mm A'; 3 | export const DEFAULT_TIMESLOTS = Array(24).fill(null).map((n, i) => [`${ i }`, `${ i + 1 }`] ); 4 | -------------------------------------------------------------------------------- /src/js/constants/timeslot.js: -------------------------------------------------------------------------------- 1 | export const DEFAULT = 'DEFAULT'; 2 | export const SELECTED = 'SELECTED'; 3 | export const DISABLED = 'DISABLED'; 4 | -------------------------------------------------------------------------------- /src/js/constants/week.js: -------------------------------------------------------------------------------- 1 | export const RENDER_DAYS = { 2 | sunday: true, 3 | monday: true, 4 | tuesday: true, 5 | wednesday: true, 6 | thursday: true, 7 | friday: true, 8 | saturday: true, 9 | }; 10 | -------------------------------------------------------------------------------- /src/js/demo/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lrojas94/react-timeslot-calendar/04d98812448b8e3f93400c59e05707feb3ba72b2/src/js/demo/.gitkeep -------------------------------------------------------------------------------- /src/js/demo/app.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import moment from 'moment'; 4 | 5 | import '../../styles/demo/main.scss'; 6 | 7 | import ReactTimeslotCalendar from './../react-timeslot-calendar.jsx'; 8 | import MarkdownSnippet from './../util/markdown-snippet.jsx'; 9 | /** Code snippets **/ 10 | import customTimeslotSnippet from './snippets/custom-timeslot.md'; 11 | 12 | export default class App extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | 16 | this.initialDate = moment([2017, 3, 24]); 17 | 18 | } 19 | render() { 20 | return ( 21 |
22 |

React Timeslot Calendar

23 | { this._customTimeslotSnippetRender() } 24 |
25 | ); 26 | } 27 | 28 | _customTimeslotSnippetRender() { 29 | return ( 30 |
31 |

Using Custom Timeslots and Callback

32 | 33 | { 42 | console.log('All Timeslots:'); 43 | console.log(timeslots); 44 | 45 | console.log('Last selected timeslot:'); 46 | console.log(lastSelected); 47 | } } 48 | /> 49 |
50 | ); 51 | } 52 | 53 | 54 | } 55 | 56 | ReactDOM.render(, document.getElementById('react-timeslot-calendar')); 57 | -------------------------------------------------------------------------------- /src/js/demo/snippets/custom-timeslot.md: -------------------------------------------------------------------------------- 1 | ```js 2 | { 11 | // Do stuff with timeslots. 12 | console.log(lastSelected.startDate); 13 | }} 14 | /> 15 | ``` 16 | -------------------------------------------------------------------------------- /src/js/react-timeslot-calendar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import '../styles/main.scss'; 5 | import Calendar from './components/calendar.jsx'; 6 | import { 7 | DEFAULT_TIMESLOTS, 8 | } from './constants/day.js'; 9 | 10 | 11 | export default class ReactTimeslotCalendar extends React.Component { 12 | 13 | render() { 14 | return ( 15 | 18 | ); 19 | } 20 | } 21 | 22 | ReactTimeslotCalendar.defaultProps = { 23 | timeslots: DEFAULT_TIMESLOTS, 24 | }; 25 | 26 | 27 | /** 28 | * @type {String} initialDate: The initial date in which to place the calendar. Must be MomentJS parseable. 29 | * @type {Array} timeslots: An array of timeslots to be displayed in each day. 30 | * @type {Object} timeslotProps: An object with keys and values for timeslot props (format, viewFormat) 31 | * @type {Array} selectedTimeslots: Initial value for selected timeslot inputs. Expects Dates formatted as Strings. 32 | * @type {Array} disabledTimeslots: Initial value for selected timeslot inputs. Expects: StartDate, EndDate, Format. 33 | * @type {Integer} maxTimexlots: maximum ammount of timeslots to select. 34 | * @type {Object} renderDays: An array of days which states which days of the week to render. By default renders all days. 35 | * @type {Object} startDateInputProps: properties for the startDate Inputs. Includes name, class, type (hidden, text...) 36 | * @type {Object} endDateInputProps: properties for the endDate Inputs. Includes name, class, type (hidden, text...) 37 | * @type {Object} onSelectTimeslot: Function which takes as parameters 1) The array of selected timeslots and 2) The latest selected timeslot. 38 | */ 39 | ReactTimeslotCalendar.propTypes = { 40 | initialDate: PropTypes.string.isRequired, 41 | timeslots: PropTypes.array.isRequired, 42 | timeslotProps: PropTypes.object, 43 | selectedTimeslots: PropTypes.array, 44 | disabledTimeslots: PropTypes.array, 45 | maxTimeslots: PropTypes.number, 46 | renderDays: PropTypes.object, 47 | startDateInputProps: PropTypes.object, 48 | endDateInputProps: PropTypes.object, 49 | onSelectTimeslot: PropTypes.func, 50 | }; 51 | -------------------------------------------------------------------------------- /src/js/util/helpers.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | let helpers = {}; 4 | export default helpers; 5 | 6 | helpers.getMomentFromCalendarJSDateElement = (dayElement) => { 7 | return moment([ 8 | dayElement.year, 9 | dayElement.month - 1, 10 | dayElement.date, 11 | ]); 12 | }; 13 | -------------------------------------------------------------------------------- /src/js/util/markdown-snippet.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default class MarkdownSnippet extends React.Component { 5 | render() { 6 | const { 7 | snippet, 8 | } = this.props; 9 | 10 | return ( 11 |
12 | ); 13 | } 14 | } 15 | 16 | MarkdownSnippet.propTypes = { 17 | snippet: PropTypes.string, 18 | }; 19 | -------------------------------------------------------------------------------- /src/styles/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lrojas94/react-timeslot-calendar/04d98812448b8e3f93400c59e05707feb3ba72b2/src/styles/.gitkeep -------------------------------------------------------------------------------- /src/styles/abstract/_variables.scss: -------------------------------------------------------------------------------- 1 | $color-clouds: #ecf0f1; 2 | $color-main-orange: #FF6235; 3 | $color-main-black: #313232; 4 | $color-white: #FFFFFF; 5 | $default-fonts: 'Open Sans', sans-serif; 6 | /** 7 | * Timeslot Component variables: 8 | */ 9 | $timeslot-default-bg-color: $color-white; 10 | $timeslot-selected-bg-color: #FF3E00; 11 | $timeslot-disabled-bg-color: #EEEDEB; 12 | $timeslot-default-color: $color-main-black; 13 | $timeslot-selected-color: $color-white; 14 | $timeslot-disabled-color: #C6C3BD; 15 | $timeslot-border-radius: 0.25em; 16 | $timeslot-margin: 0.8em; 17 | $timeslot-fonts: 'Open Sans', sans-serif; 18 | 19 | /** 20 | * Day Component variables 21 | */ 22 | $day-default-color: $color-main-orange; 23 | $day-fonts: 'Open Sans', sans-serif; 24 | 25 | /** 26 | * Month Variables 27 | */ 28 | $month-bg-color: #F2F1EF; 29 | /** 30 | * Calendar variables 31 | */ 32 | $calendar-bg-color: #F2F1EF; 33 | $color-clouds: #ecf0f1; 34 | -------------------------------------------------------------------------------- /src/styles/components/_calendar.scss: -------------------------------------------------------------------------------- 1 | .tsc-calendar { 2 | display: flex; 3 | flex: 1; 4 | flex-direction: column; 5 | color: $color-main-black; 6 | background: $calendar-bg-color; 7 | padding: 0.8em; 8 | } 9 | 10 | .tsc-calendar__actions { 11 | display: flex; 12 | flex: 1; 13 | border-bottom: 0.075em solid darken($color-clouds, 5%); 14 | } 15 | 16 | .tsc-calendar__action { 17 | display: flex; 18 | justify-content: center; 19 | align-items: center; 20 | font-weight: 700; 21 | color: $color-main-black; 22 | font-family: $default-fonts; 23 | text-align: center; 24 | } 25 | 26 | .tsc-calendar__action-element { 27 | font-size: 1.5em; 28 | padding: 0 1em; 29 | cursor: pointer; 30 | } 31 | 32 | .tsc-calendar__action-title { 33 | flex-grow: 2; 34 | padding: 1em; 35 | text-transform: uppercase; 36 | } 37 | -------------------------------------------------------------------------------- /src/styles/components/_day.scss: -------------------------------------------------------------------------------- 1 | .tsc-day { 2 | display: flex; 3 | flex: 1; 4 | flex-direction: column; 5 | } 6 | 7 | .tsc-day__title { 8 | color: $day-default-color; 9 | font-family: $day-fonts; 10 | text-align: center; 11 | font-weight: 700; 12 | padding: 1em; 13 | text-transform: uppercase; 14 | } 15 | -------------------------------------------------------------------------------- /src/styles/components/_month.scss: -------------------------------------------------------------------------------- 1 | .tsc-month { 2 | display: flex; 3 | flex-direction: column; 4 | background: $month-bg-color; 5 | } 6 | 7 | .tsc-month__actions { 8 | display: flex 9 | } 10 | 11 | .tsc-month__action { 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | font-weight: 700; 16 | color: $day-default-color; 17 | font-family: $day-fonts; 18 | text-align: center; 19 | } 20 | 21 | .tsc-month__action-element { 22 | font-size: 1.5em; 23 | padding: 0 1em; 24 | cursor: pointer; 25 | } 26 | 27 | .tsc-month__action-title { 28 | flex-grow: 2; 29 | padding: 1em; 30 | } 31 | -------------------------------------------------------------------------------- /src/styles/components/_timeslot.scss: -------------------------------------------------------------------------------- 1 | @mixin timeslotSelected { 2 | background-color: $timeslot-selected-bg-color; 3 | color: $timeslot-selected-color; 4 | } 5 | 6 | .tsc-timeslot { 7 | display: flex; 8 | flex: 1; 9 | justify-content: center; 10 | padding: 1em; 11 | font-size: 0.9em; 12 | background-color: $timeslot-default-bg-color; 13 | transition: background-color 0.5s, color 0.5s, box-shadow cubic-bezier(.25,.8,.25,1) 0.3s; 14 | border-radius: $timeslot-border-radius; 15 | text-align: center; 16 | margin: $timeslot-margin; 17 | font-family: $timeslot-fonts; 18 | color: $timeslot-default-color; 19 | box-shadow: 0 0.0625em 0.188em rgba(0,0,0,0.12), 0 0.0625em 0.125em rgba(0,0,0,0.24); 20 | 21 | &:not(.tsc-timeslot--disabled) { 22 | cursor: pointer; 23 | &:hover{ 24 | @include timeslotSelected; 25 | box-shadow: 0 0.188em 0.563em rgba(0,0,0,0.25), 0 0.125em 0.125em rgba(0,0,0,0.22); 26 | } 27 | } 28 | } 29 | 30 | .tsc-timeslot--selected { 31 | @include timeslotSelected; 32 | } 33 | 34 | .tsc-timeslot--disabled { 35 | background-color: $timeslot-disabled-bg-color; 36 | color: $timeslot-disabled-color; 37 | } 38 | -------------------------------------------------------------------------------- /src/styles/components/_week.scss: -------------------------------------------------------------------------------- 1 | .tsc-week { 2 | display: flex; 3 | flex: 1; 4 | flex-direction: row; 5 | align-items: stretch; 6 | } 7 | 8 | @media only screen and (max-width: 767px) { 9 | .tsc-week { 10 | flex-direction: column; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/styles/demo/main.scss: -------------------------------------------------------------------------------- 1 | @import "../abstract/variables"; 2 | body, { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | body, 8 | html, 9 | #react-timeslot-calendar, { 10 | min-height: 100%; 11 | } 12 | 13 | .app { 14 | background: $month-bg-color; 15 | font-family: $default-fonts; 16 | min-height: 100%; 17 | display: flex; 18 | flex-direction: column; 19 | 20 | h1, h3 { 21 | text-align: center; 22 | font-weight: 100; 23 | } 24 | 25 | h1 { 26 | color: $timeslot-selected-bg-color; 27 | padding-bottom: 0.5em; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import "abstract/variables"; 2 | /** 3 | * Components 4 | */ 5 | @import "components/calendar"; 6 | @import "components/month"; 7 | @import "components/week"; 8 | @import "components/day"; 9 | @import "components/timeslot"; 10 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackBundleSizeAnalyzerPlugin = require('webpack-bundle-size-analyzer').WebpackBundleSizeAnalyzerPlugin; 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | entry: './src/js/react-timeslot-calendar.jsx', 7 | 8 | output: { 9 | path: path.join(__dirname, './build'), 10 | filename: 'build.min.js', 11 | libraryTarget: 'umd', 12 | }, 13 | plugins: [ 14 | new WebpackBundleSizeAnalyzerPlugin('./reports/plain-report.txt'), 15 | new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), 16 | new webpack.DefinePlugin({ 17 | 'process.env': { 18 | 'NODE_ENV': JSON.stringify('production'), 19 | }, 20 | }), 21 | ], 22 | module: { 23 | loaders: [ 24 | { 25 | test: /\.jsx?$/, 26 | exclude: /node_modules/, 27 | loader: 'babel-loader', 28 | query: { 29 | presets: ['react', 'es2015'], 30 | }, 31 | }, 32 | { 33 | test: /\.scss$/, 34 | use: [{ 35 | loader: 'style-loader', // creates style nodes from JS strings 36 | }, { 37 | loader: 'css-loader', // translates CSS into CommonJS 38 | }, { 39 | loader: 'sass-loader', // compiles Sass to CSS 40 | }], 41 | }, 42 | ], 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /webpack.demo.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | const path = require('path'); 3 | const hljs = require('highlight.js'); 4 | const marked = require('marked'); 5 | 6 | const renderer = new marked.Renderer(); 7 | renderer.code = function(code, language){ 8 | return '
' +
 9 |     hljs.highlight(language, code).value +
10 |     '
'; 11 | }; 12 | 13 | 14 | module.exports = { 15 | 16 | entry: './src/js/demo/app.jsx', 17 | 18 | output: { 19 | filename: './public/build.js', 20 | libraryTarget: 'umd', 21 | }, 22 | plugins: [ 23 | new webpack.DefinePlugin({ 24 | 'process.env': { 25 | 'NODE_ENV': JSON.stringify('production'), 26 | }, 27 | }), 28 | ], 29 | module: { 30 | loaders: [ 31 | { 32 | test: /\.jsx$/, 33 | exclude: /node_modules/, 34 | loader: 'babel-loader', 35 | query: { 36 | presets: ['react', 'es2015'], 37 | }, 38 | }, 39 | { 40 | test: /\.scss$/, 41 | use: [{ 42 | loader: 'style-loader', // creates style nodes from JS strings 43 | }, { 44 | loader: 'css-loader', // translates CSS into CommonJS 45 | }, { 46 | loader: 'sass-loader', // compiles Sass to CSS 47 | }], 48 | }, 49 | { 50 | test: /\.md$/, 51 | use: [{ 52 | loader: 'html-loader', 53 | }, { 54 | loader: 'markdown-loader', 55 | options: { 56 | renderer, 57 | }, 58 | }], 59 | }, 60 | ], 61 | }, 62 | }; 63 | --------------------------------------------------------------------------------