├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .nvmrc ├── .travis.yml ├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── demo ├── agenda.gif ├── calendar-list.gif ├── calendar.gif ├── custom.png ├── horizontal-calendar-list.gif ├── loader.png ├── marking1.png ├── marking2.png ├── marking3.png ├── marking4.png ├── marking5.png └── marking6.png ├── example ├── .babelrc ├── .buckconfig ├── .flowconfig ├── .gitattributes ├── .gitignore ├── .watchmanconfig ├── __tests__ │ ├── index.android.js │ └── index.ios.js ├── android │ ├── app │ │ ├── BUCK │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── calendarsexample │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── keystores │ │ ├── BUCK │ │ └── debug.keystore.properties │ └── settings.gradle ├── app.json ├── index.android.js ├── index.ios.js ├── ios │ ├── CalendarsExample-tvOS │ │ └── Info.plist │ ├── CalendarsExample-tvOSTests │ │ └── Info.plist │ ├── CalendarsExample.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ ├── CalendarsExample-tvOS.xcscheme │ │ │ └── CalendarsExample.xcscheme │ ├── CalendarsExample │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Base.lproj │ │ │ └── LaunchScreen.xib │ │ ├── Images.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Info.plist │ │ └── main.m │ └── CalendarsExampleTests │ │ ├── CalendarsExampleTests.m │ │ └── Info.plist ├── package.json └── src │ ├── app.js │ └── screens │ ├── agenda.js │ ├── calendars.js │ ├── calendarsList.js │ ├── horizontalCalendarList.js │ ├── index.js │ └── menu.js ├── package.json ├── pom.xml ├── spec ├── runner.js └── support │ └── jasmine.json └── src ├── agenda ├── img │ └── knob@2x.png ├── index.js ├── platform-style.ios.js ├── platform-style.js ├── reservation-list │ ├── index.js │ ├── reservation.js │ └── style.js └── style.js ├── calendar-list ├── index.js ├── item.js └── style.js ├── calendar ├── day │ ├── basic │ │ ├── index.js │ │ └── style.js │ ├── custom │ │ ├── index.js │ │ └── style.js │ ├── multi-dot │ │ ├── index.js │ │ └── style.js │ ├── multi-period │ │ ├── index.js │ │ └── style.js │ └── period │ │ ├── index.js │ │ └── style.js ├── header │ ├── index.js │ └── style.js ├── img │ ├── next.png │ ├── next@1.5x.android.png │ ├── next@2x.android.png │ ├── next@2x.ios.png │ ├── next@3x.android.png │ ├── next@4x.android.png │ ├── previous.png │ ├── previous@1.5x.android.png │ ├── previous@2x.android.png │ ├── previous@2x.ios.png │ ├── previous@3x.android.png │ └── previous@4x.android.png ├── index.js ├── style.js └── updater.js ├── component-updater.js ├── component-updater.spec.js ├── dateutils.js ├── dateutils.spec.js ├── index.js ├── input.js ├── interface.js ├── interface.spec.js ├── style.js └── testIDs.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "globals": { 7 | "expect": true, 8 | "it": true, 9 | "describe": true, 10 | }, 11 | "extends": "eslint:recommended", 12 | "parser": "babel-eslint", 13 | "parserOptions": { 14 | "ecmaFeatures": { 15 | "experimentalObjectRestSpread": true, 16 | "jsx": true 17 | }, 18 | "sourceType": "module" 19 | }, 20 | "plugins": [ 21 | "react" 22 | ], 23 | "rules": { 24 | "no-unused-vars": 2, 25 | "react/jsx-uses-vars": 2, 26 | "react/jsx-uses-react": 2, 27 | "indent": [ 28 | "error", 29 | 2 30 | ], 31 | "linebreak-style": [ 32 | "error", 33 | "unix" 34 | ], 35 | "quotes": [ 36 | "error", 37 | "single" 38 | ], 39 | "semi": [ 40 | "error", 41 | "always" 42 | ] 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | .idea 5 | yarn.lock 6 | .vscode 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | src/**/*.spec.js 4 | example/ 5 | demo/ 6 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 6.9 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "7" 4 | 5 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please make our job easier by filling this template out to completion. If you're requesting a feature instead of reporting a bug, please feel free to skip the Environment and Reproducible Demo sections. 2 | 3 | ## Description 4 | 5 | 1-2 sentences describing the problem you're having or the feature you'd like to request 6 | 7 | ## Expected Behavior 8 | 9 | What action did you perform, and what did you expect to happen? 10 | 11 | ## Observed Behavior 12 | 13 | What actually happened when you performed the above actions? 14 | 15 | If there's an error message, please paste the *full terminal output and error message* in this code block: 16 | 17 | ``` 18 | Error text goes here! 19 | ``` 20 | 21 | ## Environment 22 | 23 | Please run these commands in the project folder and fill in their results: 24 | 25 | * `npm ls react-native-calendars`: 26 | * `npm ls react-native`: 27 | 28 | Also specify: 29 | 30 | 1. Phone/emulator/simulator & version: 31 | 32 | ## Reproducible Demo 33 | 34 | Please provide a minimized reproducible demonstration of the problem you're reporting. 35 | 36 | Issues that come with minimal repro's are resolved much more quickly than issues where a maintainer has to reproduce themselves. 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Wix.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native Calendars ✨ 🗓️ 📆 2 | 3 | [![Version](https://img.shields.io/npm/v/react-native-calendars.svg)](https://www.npmjs.com/package/react-native-calendars) 4 | [![Build Status](https://travis-ci.org/wix/react-native-calendars.svg?branch=master)](https://travis-ci.org/wix/react-native-calendars) 5 | 6 | This module includes various customizable react native calendar components. 7 | 8 | The package is both **Android** and **iOS** compatible. 9 | 10 | ## Try it out 11 | 12 | You can run example module by performing these steps: 13 | 14 | ``` 15 | $ git clone git@github.com:wix/react-native-calendars.git 16 | $ cd react-native-calendars/example 17 | $ npm install 18 | $ react-native run-ios 19 | ``` 20 | 21 | You can check example screens source code in [example module screens](https://github.com/wix-private/wix-react-native-calendar/tree/master/example/src/screens) 22 | 23 | This project is compatible with Expo/CRNA (without ejecting), and the examples have been [published on Expo](https://expo.io/@community/react-native-calendars-example) 24 | 25 | ## Installation 26 | 27 | ``` 28 | $ npm install --save react-native-calendars 29 | ``` 30 | 31 | The solution is implemented in JavaScript so no native module linking is required. 32 | 33 | ## Usage 34 | 35 | `import {` [Calendar](#calendar), [CalendarList](#calendarlist), [Agenda](#agenda) `} from 'react-native-calendars';` 36 | 37 | All parameters for components are optional. By default the month of current local date will be displayed. 38 | 39 | Event handler callbacks are called with `calendar objects` like this: 40 | 41 | ```javasctipt 42 | { 43 | day: 1, // day of month (1-31) 44 | month: 1, // month of year (1-12) 45 | year: 2017, // year 46 | timestamp, // UTC timestamp representing 00:00 AM of this date 47 | dateString: '2016-05-13' // date formatted as 'YYYY-MM-DD' string 48 | } 49 | ``` 50 | 51 | Parameters that require date types accept YYYY-MM-DD formated datestrings, JavaScript date objects, `calendar objects` and UTC timestamps. 52 | 53 | Calendars can be localized by adding custom locales to `LocaleConfig` object: 54 | 55 | ```javascript 56 | import {LocaleConfig} from 'react-native-calendars'; 57 | 58 | LocaleConfig.locales['fr'] = { 59 | monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'], 60 | monthNamesShort: ['Janv.','Févr.','Mars','Avril','Mai','Juin','Juil.','Août','Sept.','Oct.','Nov.','Déc.'], 61 | dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'], 62 | dayNamesShort: ['Dim.','Lun.','Mar.','Mer.','Jeu.','Ven.','Sam.'] 63 | }; 64 | 65 | LocaleConfig.defaultLocale = 'fr'; 66 | ``` 67 | 68 | ### Calendar 69 | 70 | 71 | 72 | 73 | 74 | #### Basic parameters 75 | 76 | ```javascript 77 | {console.log('selected day', day)}} 86 | // Handler which gets executed on day long press. Default = undefined 87 | onDayLongPress={(day) => {console.log('selected day', day)}} 88 | // Month format in calendar title. Formatting values: http://arshaw.com/xdate/#Formatting 89 | monthFormat={'yyyy MM'} 90 | // Handler which gets executed when visible month changes in calendar. Default = undefined 91 | onMonthChange={(month) => {console.log('month changed', month)}} 92 | // Hide month navigation arrows. Default = false 93 | hideArrows={true} 94 | // Replace default arrows with custom ones (direction can be 'left' or 'right') 95 | renderArrow={(direction) => ()} 96 | // Do not show days of other months in month page. Default = false 97 | hideExtraDays={true} 98 | // If hideArrows=false and hideExtraDays=false do not switch month when tapping on greyed out 99 | // day from another month that is visible in calendar page. Default = false 100 | disableMonthChange={true} 101 | // If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday. 102 | firstDay={1} 103 | // Hide day names. Default = false 104 | hideDayNames={true} 105 | // Show week numbers to the left. Default = false 106 | showWeekNumbers={true} 107 | // Handler which gets executed when press arrow icon left. It receive a callback can go back month 108 | onPressArrowLeft={substractMonth => substractMonth()} 109 | // Handler which gets executed when press arrow icon left. It receive a callback can go next month 110 | onPressArrowRight={addMonth => addMonth()} 111 | /> 112 | ``` 113 | 114 | #### Date marking 115 | 116 | **!Disclaimer!** Make sure that `markedDates` param is immutable. If you change `markedDates` object content but the reference to it does not change calendar update will not be triggered. 117 | 118 | Dot marking 119 | 120 | 121 | 122 | 123 | 124 | ```javascript 125 | 134 | ``` 135 | 136 | You can customise a dot color for each day independently. 137 | 138 | Multi-Dot marking 139 | 140 | 141 | 142 | 143 | 144 | Use markingType = 'multi-dot' if you want to display more than one dot. Both the Calendar and CalendarList control support multiple dots by using 'dots' array in markedDates. The property 'color' is mandatory while 'key' and 'selectedColor' are optional. If key is omitted then the array index is used as key. If selectedColor is omitted then 'color' will be used for selected dates. 145 | ```javascript 146 | const vacation = {key:'vacation', color: 'red', selectedDotColor: 'blue'}; 147 | const massage = {key:'massage', color: 'blue', selectedDotColor: 'blue'}; 148 | const workout = {key:'workout', color: 'green'}; 149 | 150 | 157 | ``` 158 | 159 | 160 | Period marking 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | ```javascript 171 | 182 | ``` 183 | 184 | Multi-period marking 185 | 186 | 187 | 188 | 189 | 190 | CAUTION: This marking is only fully supported by the `` component because it expands its height. Usage with `` might lead to overflow issues. 191 | 192 | ```javascript 193 | 213 | ``` 214 | 215 | Custom marking allows you to customize each marker with custom styles. 216 | 217 | 218 | 219 | 220 | 221 | ```javascript 222 | 249 | ``` 250 | 251 | Keep in mind that different marking types are not compatible. You can use just one marking style for calendar. 252 | 253 | #### Displaying data loading indicator 254 | 255 | 256 | 257 | 258 | 259 | The loading indicator next to month name will be displayed if `` has `displayLoadingIndicator` property and `markedDays` collection does not have a value for every day of the month in question. When you load data for days, just set `[]` or special marking value to all days in `markedDates` collection. 260 | 261 | #### Customizing look & feel 262 | 263 | ```javascript 264 | 294 | ``` 295 | 296 | #### Advanced styling 297 | 298 | If you want to have complete control over calendar styles you can do it by overriding default style.js files. For example, if you want to override calendar header style first you have to find stylesheet id for this file: 299 | 300 | https://github.com/wix/react-native-calendars/blob/master/src/calendar/header/style.js#L4 301 | 302 | In this case it is 'stylesheet.calendar.header'. Next you can add overriding stylesheet to your theme with this id. 303 | 304 | https://github.com/wix/react-native-calendars/blob/master/example/src/screens/calendars.js#L56 305 | 306 | ```javascript 307 | theme={{ 308 | arrowColor: 'white', 309 | 'stylesheet.calendar.header': { 310 | week: { 311 | marginTop: 5, 312 | flexDirection: 'row', 313 | justifyContent: 'space-between' 314 | } 315 | } 316 | }} 317 | ``` 318 | 319 | **Disclaimer**: issues that arise because something breaks after using stylesheet override will not be supported. Use this option at your own risk. 320 | 321 | #### Overriding day component 322 | 323 | If you need custom functionality not supported by current day component implementations you can pass your own custom day 324 | component to the calendar. 325 | 326 | ```javascript 327 | { 330 | return ({date.day}); 331 | }} 332 | /> 333 | ``` 334 | 335 | The dayComponent prop has to receive a RN component or function that receive props. The day component will receive such props: 336 | 337 | * state - disabled if the day should be disabled (this is decided by base calendar component) 338 | * marking - markedDates value for this day 339 | * date - the date object representing this day 340 | 341 | **Tip:** Don't forget to implement shouldComponentUpdate for your custom day component to make calendar perform better 342 | 343 | If you implement an awesome day component please make a PR so that other people could use it :) 344 | 345 | ### CalendarList 346 | 347 | 348 | 349 | 350 | 351 | `` is scrollable semi-infinite calendar composed of `` components. Currently it is possible to scroll 4 years back and 4 years to the future. All paramters that are available for `` are also available for this component. There are also some additional params that can be used: 352 | 353 | ```javascript 354 | {console.log('now these months are visible', months);}} 357 | // Max amount of months allowed to scroll to the past. Default = 50 358 | pastScrollRange={50} 359 | // Max amount of months allowed to scroll to the future. Default = 50 360 | futureScrollRange={50} 361 | // Enable or disable scrolling of calendar list 362 | scrollEnabled={true} 363 | // Enable or disable vertical scroll indicator. Default = false 364 | showScrollIndicator={true} 365 | ...calendarParams 366 | /> 367 | ``` 368 | 369 | #### Horizontal CalendarList 370 | 371 | 372 | 373 | 374 | 375 | You can also make the `CalendarList` scroll horizontally. To do that you need to pass specific props to the `CalendarList`: 376 | 377 | ```javascript 378 | 388 | ``` 389 | 390 | ### Agenda 391 | 392 | 393 | 394 | 395 | An advanced agenda component that can display interactive listings for calendar day items. 396 | 397 | ```javascript 398 | {console.log('trigger items loading')}} 410 | // callback that fires when the calendar is opened or closed 411 | onCalendarToggled={(calendarOpened) => {console.log(calendarOpened)}} 412 | // callback that gets called on day press 413 | onDayPress={(day)=>{console.log('day pressed')}} 414 | // callback that gets called when day changes while scrolling agenda list 415 | onDayChange={(day)=>{console.log('day changed')}} 416 | // initially selected day 417 | selected={'2012-05-16'} 418 | // Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined 419 | minDate={'2012-05-10'} 420 | // Maximum date that can be selected, dates after maxDate will be grayed out. Default = undefined 421 | maxDate={'2012-05-30'} 422 | // Max amount of months allowed to scroll to the past. Default = 50 423 | pastScrollRange={50} 424 | // Max amount of months allowed to scroll to the future. Default = 50 425 | futureScrollRange={50} 426 | // specify how each item should be rendered in agenda 427 | renderItem={(item, firstItemInDay) => {return ();}} 428 | // specify how each date should be rendered. day can be undefined if the item is not first in that day. 429 | renderDay={(day, item) => {return ();}} 430 | // specify how empty date content with no items should be rendered 431 | renderEmptyDate={() => {return ();}} 432 | // specify how agenda knob should look like 433 | renderKnob={() => {return ();}} 434 | // specify what should be rendered instead of ActivityIndicator 435 | renderEmptyData = {() => {return ();}} 436 | // specify your item comparison function for increased performance 437 | rowHasChanged={(r1, r2) => {return r1.text !== r2.text}} 438 | // Hide knob button. Default = false 439 | hideKnob={true} 440 | // By default, agenda dates are marked if they have at least one item, but you can override this if needed 441 | markedDates={{ 442 | '2012-05-16': {selected: true, marked: true}, 443 | '2012-05-17': {marked: true}, 444 | '2012-05-18': {disabled: true} 445 | }} 446 | // If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make sure to also set the refreshing prop correctly. 447 | onRefresh={() => console.log('refreshing...')} 448 | // Set this true while waiting for new data from a refresh 449 | refreshing={false} 450 | // Add a custom RefreshControl component, used to provide pull-to-refresh functionality for the ScrollView. 451 | refreshControl={null} 452 | // agenda theme 453 | theme={{ 454 | ...calendarTheme, 455 | agendaDayTextColor: 'yellow', 456 | agendaDayNumColor: 'green', 457 | agendaTodayColor: 'red', 458 | agendaKnobColor: 'blue' 459 | }} 460 | // agenda container style 461 | style={{}} 462 | /> 463 | ``` 464 | 465 | ## Authors 466 | 467 | * [Tautvilas Mecinskas](https://github.com/tautvilas/) - Initial code - [@tautvilas](https://twitter.com/TautviIas) 468 | * Katrin Zotchev - Initial design - [@katrin_zot](https://twitter.com/katrin_zot) 469 | 470 | See also the list of [contributors](https://github.com/wix/react-native-calendar-components/contributors) who participated in this project. 471 | 472 | ## Contributing 473 | 474 | Pull requests are welcome. `npm run test` and `npm run lint` before push. 475 | -------------------------------------------------------------------------------- /demo/agenda.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/demo/agenda.gif -------------------------------------------------------------------------------- /demo/calendar-list.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/demo/calendar-list.gif -------------------------------------------------------------------------------- /demo/calendar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/demo/calendar.gif -------------------------------------------------------------------------------- /demo/custom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/demo/custom.png -------------------------------------------------------------------------------- /demo/horizontal-calendar-list.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/demo/horizontal-calendar-list.gif -------------------------------------------------------------------------------- /demo/loader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/demo/loader.png -------------------------------------------------------------------------------- /demo/marking1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/demo/marking1.png -------------------------------------------------------------------------------- /demo/marking2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/demo/marking2.png -------------------------------------------------------------------------------- /demo/marking3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/demo/marking3.png -------------------------------------------------------------------------------- /demo/marking4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/demo/marking4.png -------------------------------------------------------------------------------- /demo/marking5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/demo/marking5.png -------------------------------------------------------------------------------- /demo/marking6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/demo/marking6.png -------------------------------------------------------------------------------- /example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react-native"] 3 | } 4 | -------------------------------------------------------------------------------- /example/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /example/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore unexpected extra "@providesModule" 9 | .*/node_modules/.*/node_modules/fbjs/.* 10 | 11 | ; Ignore duplicate module providers 12 | ; For RN Apps installed via npm, "Libraries" folder is inside 13 | ; "node_modules/react-native" but in the source repo it is in the root 14 | .*/Libraries/react-native/React.js 15 | .*/Libraries/react-native/ReactNative.js 16 | 17 | [include] 18 | 19 | [libs] 20 | node_modules/react-native/Libraries/react-native/react-native-interface.js 21 | node_modules/react-native/flow 22 | flow/ 23 | 24 | [options] 25 | emoji=true 26 | 27 | module.system=haste 28 | 29 | munge_underscores=true 30 | 31 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 32 | 33 | suppress_type=$FlowIssue 34 | suppress_type=$FlowFixMe 35 | suppress_type=$FixMe 36 | 37 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(4[0-9]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 38 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(4[0-9]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 39 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 40 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 41 | 42 | unsafe.enable_getters_and_setters=true 43 | 44 | [version] 45 | ^0.49.1 46 | -------------------------------------------------------------------------------- /example/.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | 44 | # fastlane 45 | # 46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 47 | # screenshots whenever they are needed. 48 | # For more information about the recommended setup visit: 49 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 50 | 51 | fastlane/report.xml 52 | fastlane/Preview.html 53 | fastlane/screenshots 54 | -------------------------------------------------------------------------------- /example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /example/__tests__/index.android.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Index from '../index.android.js'; 4 | 5 | // Note: test renderer must be required after react-native. 6 | import renderer from 'react-test-renderer'; 7 | 8 | it('renders correctly', () => { 9 | const tree = renderer.create( 10 | 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /example/__tests__/index.ios.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Index from '../index.ios.js'; 4 | 5 | // Note: test renderer must be required after react-native. 6 | import renderer from 'react-test-renderer'; 7 | 8 | it('renders correctly', () => { 9 | const tree = renderer.create( 10 | 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /example/android/app/BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | lib_deps = [] 12 | 13 | for jarfile in glob(['libs/*.jar']): 14 | name = 'jars__' + jarfile[jarfile.rindex('/') + 1: jarfile.rindex('.jar')] 15 | lib_deps.append(':' + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | 21 | for aarfile in glob(['libs/*.aar']): 22 | name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')] 23 | lib_deps.append(':' + name) 24 | android_prebuilt_aar( 25 | name = name, 26 | aar = aarfile, 27 | ) 28 | 29 | android_library( 30 | name = "all-libs", 31 | exported_deps = lib_deps, 32 | ) 33 | 34 | android_library( 35 | name = "app-code", 36 | srcs = glob([ 37 | "src/main/java/**/*.java", 38 | ]), 39 | deps = [ 40 | ":all-libs", 41 | ":build_config", 42 | ":res", 43 | ], 44 | ) 45 | 46 | android_build_config( 47 | name = "build_config", 48 | package = "com.calendarsexample", 49 | ) 50 | 51 | android_resource( 52 | name = "res", 53 | package = "com.calendarsexample", 54 | res = "src/main/res", 55 | ) 56 | 57 | android_binary( 58 | name = "app", 59 | keystore = "//android/keystores:debug", 60 | manifest = "src/main/AndroidManifest.xml", 61 | package_type = "debug", 62 | deps = [ 63 | ":app-code", 64 | ], 65 | ) 66 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | 3 | import com.android.build.OutputFile 4 | 5 | /** 6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 7 | * and bundleReleaseJsAndAssets). 8 | * These basically call `react-native bundle` with the correct arguments during the Android build 9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 10 | * bundle directly from the development server. Below you can see all the possible configurations 11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 12 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 13 | * 14 | * project.ext.react = [ 15 | * // the name of the generated asset file containing your JS bundle 16 | * bundleAssetName: "index.android.bundle", 17 | * 18 | * // the entry file for bundle generation 19 | * entryFile: "index.android.js", 20 | * 21 | * // whether to bundle JS and assets in debug mode 22 | * bundleInDebug: false, 23 | * 24 | * // whether to bundle JS and assets in release mode 25 | * bundleInRelease: true, 26 | * 27 | * // whether to bundle JS and assets in another build variant (if configured). 28 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 29 | * // The configuration property can be in the following formats 30 | * // 'bundleIn${productFlavor}${buildType}' 31 | * // 'bundleIn${buildType}' 32 | * // bundleInFreeDebug: true, 33 | * // bundleInPaidRelease: true, 34 | * // bundleInBeta: true, 35 | * 36 | * // whether to disable dev mode in custom build variants (by default only disabled in release) 37 | * // for example: to disable dev mode in the staging build type (if configured) 38 | * devDisabledInStaging: true, 39 | * // The configuration property can be in the following formats 40 | * // 'devDisabledIn${productFlavor}${buildType}' 41 | * // 'devDisabledIn${buildType}' 42 | * 43 | * // the root of your project, i.e. where "package.json" lives 44 | * root: "../../", 45 | * 46 | * // where to put the JS bundle asset in debug mode 47 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 48 | * 49 | * // where to put the JS bundle asset in release mode 50 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 51 | * 52 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 53 | * // require('./image.png')), in debug mode 54 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 55 | * 56 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 57 | * // require('./image.png')), in release mode 58 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 59 | * 60 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 61 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 62 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 63 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 64 | * // for example, you might want to remove it from here. 65 | * inputExcludes: ["android/**", "ios/**"], 66 | * 67 | * // override which node gets called and with what additional arguments 68 | * nodeExecutableAndArgs: ["node"], 69 | * 70 | * // supply additional arguments to the packager 71 | * extraPackagerArgs: [] 72 | * ] 73 | */ 74 | 75 | apply from: "../../node_modules/react-native/react.gradle" 76 | 77 | /** 78 | * Set this to true to create two separate APKs instead of one: 79 | * - An APK that only works on ARM devices 80 | * - An APK that only works on x86 devices 81 | * The advantage is the size of the APK is reduced by about 4MB. 82 | * Upload all the APKs to the Play Store and people will download 83 | * the correct one based on the CPU architecture of their device. 84 | */ 85 | def enableSeparateBuildPerCPUArchitecture = false 86 | 87 | /** 88 | * Run Proguard to shrink the Java bytecode in release builds. 89 | */ 90 | def enableProguardInReleaseBuilds = false 91 | 92 | android { 93 | compileSdkVersion 25 94 | buildToolsVersion "25.0.1" 95 | 96 | defaultConfig { 97 | applicationId "com.calendarsexample" 98 | minSdkVersion 16 99 | targetSdkVersion 22 100 | versionCode 1 101 | versionName "1.0" 102 | ndk { 103 | abiFilters "armeabi-v7a", "x86" 104 | } 105 | } 106 | splits { 107 | abi { 108 | reset() 109 | enable enableSeparateBuildPerCPUArchitecture 110 | universalApk false // If true, also generate a universal APK 111 | include "armeabi-v7a", "x86" 112 | } 113 | } 114 | buildTypes { 115 | release { 116 | minifyEnabled enableProguardInReleaseBuilds 117 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 118 | } 119 | } 120 | // applicationVariants are e.g. debug, release 121 | applicationVariants.all { variant -> 122 | variant.outputs.each { output -> 123 | // For each separate APK per architecture, set a unique version code as described here: 124 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits 125 | def versionCodes = ["armeabi-v7a":1, "x86":2] 126 | def abi = output.getFilter(OutputFile.ABI) 127 | if (abi != null) { // null for the universal-debug, universal-release variants 128 | output.versionCodeOverride = 129 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 130 | } 131 | } 132 | } 133 | } 134 | 135 | dependencies { 136 | compile fileTree(dir: "libs", include: ["*.jar"]) 137 | compile "com.android.support:appcompat-v7:23.0.1" 138 | compile "com.facebook.react:react-native:+" // From node_modules 139 | compile project(':react-native-navigation') 140 | } 141 | 142 | // Run this once to be able to run the application with BUCK 143 | // puts all compile dependencies into folder libs for BUCK to use 144 | task copyDownloadableDepsToLibs(type: Copy) { 145 | from configurations.compile 146 | into 'libs' 147 | } 148 | -------------------------------------------------------------------------------- /example/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Disabling obfuscation is useful if you collect stack traces from production crashes 20 | # (unless you are using a system that supports de-obfuscate the stack traces). 21 | -dontobfuscate 22 | 23 | # React Native 24 | 25 | # Keep our interfaces so they can be used by other ProGuard rules. 26 | # See http://sourceforge.net/p/proguard/bugs/466/ 27 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip 28 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters 29 | -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip 30 | 31 | # Do not strip any method/class that is annotated with @DoNotStrip 32 | -keep @com.facebook.proguard.annotations.DoNotStrip class * 33 | -keep @com.facebook.common.internal.DoNotStrip class * 34 | -keepclassmembers class * { 35 | @com.facebook.proguard.annotations.DoNotStrip *; 36 | @com.facebook.common.internal.DoNotStrip *; 37 | } 38 | 39 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { 40 | void set*(***); 41 | *** get*(); 42 | } 43 | 44 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } 45 | -keep class * extends com.facebook.react.bridge.NativeModule { *; } 46 | -keepclassmembers,includedescriptorclasses class * { native ; } 47 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } 48 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } 49 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } 50 | 51 | -dontwarn com.facebook.react.** 52 | 53 | # TextLayoutBuilder uses a non-public Android constructor within StaticLayout. 54 | # See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details. 55 | -dontwarn android.text.StaticLayout 56 | 57 | # okhttp 58 | 59 | -keepattributes Signature 60 | -keepattributes *Annotation* 61 | -keep class okhttp3.** { *; } 62 | -keep interface okhttp3.** { *; } 63 | -dontwarn okhttp3.** 64 | 65 | # okio 66 | 67 | -keep class sun.misc.Unsafe { *; } 68 | -dontwarn java.nio.file.* 69 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 70 | -dontwarn okio.** 71 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 19 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/calendarsexample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.calendarsexample; 2 | 3 | import com.reactnativenavigation.controllers.SplashActivity; 4 | 5 | public class MainActivity extends SplashActivity { 6 | 7 | /** 8 | * Returns the name of the main component registered from JavaScript. 9 | * This is used to schedule rendering of the component. 10 | */ 11 | protected String getMainComponentName() { 12 | return "CalendarsExample"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/calendarsexample/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.calendarsexample; 2 | import com.reactnativenavigation.NavigationApplication; 3 | 4 | import com.facebook.react.ReactPackage; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | public class MainApplication extends NavigationApplication { 10 | 11 | @Override 12 | public boolean isDebug() { 13 | // Make sure you are using BuildConfig from your own application 14 | return BuildConfig.DEBUG; 15 | } 16 | 17 | protected List getPackages() { 18 | // Add additional packages you require here 19 | // No need to add RnnPackage and MainReactPackage 20 | return Arrays.asList( 21 | // eg. new VectorIconsPackage() 22 | ); 23 | } 24 | 25 | @Override 26 | public List createAdditionalReactPackages() { 27 | return getPackages(); 28 | } 29 | } -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CalendarsExample 3 | 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | mavenLocal() 18 | jcenter() 19 | maven { 20 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 21 | url "$rootDir/../node_modules/react-native/android" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | android.useDeprecatedNdk=true 21 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 6 | -------------------------------------------------------------------------------- /example/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /example/android/keystores/BUCK: -------------------------------------------------------------------------------- 1 | keystore( 2 | name = "debug", 3 | properties = "debug.keystore.properties", 4 | store = "debug.keystore", 5 | visibility = [ 6 | "PUBLIC", 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /example/android/keystores/debug.keystore.properties: -------------------------------------------------------------------------------- 1 | key.store=debug.keystore 2 | key.alias=androiddebugkey 3 | key.store.password=android 4 | key.alias.password=android 5 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'CalendarsExample' 2 | 3 | include ':react-native-navigation' 4 | project(':react-native-navigation').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-navigation/android/app/') 5 | include ':app' 6 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CalendarsExample", 3 | "displayName": "CalendarsExample" 4 | } -------------------------------------------------------------------------------- /example/index.android.js: -------------------------------------------------------------------------------- 1 | import App from './src/app'; 2 | -------------------------------------------------------------------------------- /example/index.ios.js: -------------------------------------------------------------------------------- 1 | import App from './src/app'; 2 | -------------------------------------------------------------------------------- /example/ios/CalendarsExample-tvOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UIViewControllerBasedStatusBarAppearance 38 | 39 | NSLocationWhenInUseUsageDescription 40 | 41 | NSAppTransportSecurity 42 | 43 | 44 | NSExceptionDomains 45 | 46 | localhost 47 | 48 | NSExceptionAllowsInsecureHTTPLoads 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /example/ios/CalendarsExample-tvOSTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /example/ios/CalendarsExample.xcodeproj/xcshareddata/xcschemes/CalendarsExample-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 61 | 67 | 68 | 69 | 70 | 71 | 77 | 78 | 79 | 80 | 81 | 82 | 92 | 94 | 100 | 101 | 102 | 103 | 104 | 105 | 111 | 113 | 119 | 120 | 121 | 122 | 124 | 125 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /example/ios/CalendarsExample.xcodeproj/xcshareddata/xcschemes/CalendarsExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 61 | 67 | 68 | 69 | 70 | 71 | 77 | 78 | 79 | 80 | 81 | 82 | 92 | 94 | 100 | 101 | 102 | 103 | 104 | 105 | 111 | 113 | 119 | 120 | 121 | 122 | 124 | 125 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /example/ios/CalendarsExample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (nonatomic, strong) UIWindow *window; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /example/ios/CalendarsExample/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | #import 3 | 4 | // ********************************************** 5 | // *** DON'T MISS: THE NEXT LINE IS IMPORTANT *** 6 | // ********************************************** 7 | #import "RCCManager.h" 8 | 9 | // IMPORTANT: if you're getting an Xcode error that RCCManager.h isn't found, you've probably ran "npm install" 10 | // with npm ver 2. You'll need to "npm install" with npm 3 (see https://github.com/wix/react-native-navigation/issues/1) 11 | 12 | #import 13 | 14 | @implementation AppDelegate 15 | 16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 17 | { 18 | NSURL *jsCodeLocation; 19 | #ifdef DEBUG 20 | // jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"]; 21 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil]; 22 | #else 23 | jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 24 | #endif 25 | 26 | 27 | // ********************************************** 28 | // *** DON'T MISS: THIS IS HOW WE BOOTSTRAP ***** 29 | // ********************************************** 30 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 31 | self.window.backgroundColor = [UIColor whiteColor]; 32 | [[RCCManager sharedInstance] initBridgeWithBundleURL:jsCodeLocation launchOptions:launchOptions]; 33 | 34 | /* 35 | // original RN bootstrap - remove this part 36 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 37 | moduleName:@"example" 38 | initialProperties:nil 39 | launchOptions:launchOptions]; 40 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 41 | UIViewController *rootViewController = [UIViewController new]; 42 | rootViewController.view = rootView; 43 | self.window.rootViewController = rootViewController; 44 | [self.window makeKeyAndVisible]; 45 | */ 46 | 47 | 48 | return YES; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /example/ios/CalendarsExample/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /example/ios/CalendarsExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /example/ios/CalendarsExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | CalendarsExample 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSExceptionDomains 30 | 31 | localhost 32 | 33 | NSExceptionAllowsInsecureHTTPLoads 34 | 35 | 36 | 37 | 38 | NSLocationWhenInUseUsageDescription 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UIViewControllerBasedStatusBarAppearance 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /example/ios/CalendarsExample/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import "AppDelegate.h" 13 | 14 | int main(int argc, char * argv[]) { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example/ios/CalendarsExampleTests/CalendarsExampleTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | #import 12 | 13 | #import 14 | #import 15 | 16 | #define TIMEOUT_SECONDS 600 17 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" 18 | 19 | @interface CalendarsExampleTests : XCTestCase 20 | 21 | @end 22 | 23 | @implementation CalendarsExampleTests 24 | 25 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 26 | { 27 | if (test(view)) { 28 | return YES; 29 | } 30 | for (UIView *subview in [view subviews]) { 31 | if ([self findSubviewInView:subview matching:test]) { 32 | return YES; 33 | } 34 | } 35 | return NO; 36 | } 37 | 38 | - (void)testRendersWelcomeScreen 39 | { 40 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 41 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 42 | BOOL foundElement = NO; 43 | 44 | __block NSString *redboxError = nil; 45 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 46 | if (level >= RCTLogLevelError) { 47 | redboxError = message; 48 | } 49 | }); 50 | 51 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 52 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 53 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 54 | 55 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 56 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 57 | return YES; 58 | } 59 | return NO; 60 | }]; 61 | } 62 | 63 | RCTSetLogFunction(RCTDefaultLogFunction); 64 | 65 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 66 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 67 | } 68 | 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /example/ios/CalendarsExampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CalendarsExample", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node node_modules/react-native/local-cli/cli.js start", 7 | "test": "jest" 8 | }, 9 | "dependencies": { 10 | "react": "16.0.0-alpha.12", 11 | "react-native": "0.54.2", 12 | "react-native-calendars": "^1.17.5", 13 | "react-native-navigation": "^1.1.205" 14 | }, 15 | "devDependencies": { 16 | "babel-jest": "20.0.3", 17 | "babel-preset-react-native": "3.0.1", 18 | "jest": "20.0.4", 19 | "react-test-renderer": "16.0.0-alpha.12" 20 | }, 21 | "jest": { 22 | "preset": "react-native" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/src/app.js: -------------------------------------------------------------------------------- 1 | import {Navigation} from 'react-native-navigation'; 2 | import {registerScreens} from './screens'; 3 | registerScreens(); 4 | 5 | // eslint-disable-next-line no-console 6 | console.ignoredYellowBox = ['Remote debugger']; 7 | 8 | /* 9 | import {LocaleConfig} from 'react-native-calendars'; 10 | 11 | LocaleConfig.locales['fr'] = { 12 | monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'], 13 | monthNamesShort: ['Janv.','Févr.','Mars','Avril','Mai','Juin','Juil.','Août','Sept.','Oct.','Nov.','Déc.'], 14 | dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'], 15 | dayNamesShort: ['Dim.','Lun.','Mar.','Mer.','Jeu.','Ven.','Sam.'] 16 | }; 17 | 18 | LocaleConfig.defaultLocale = 'fr'; 19 | */ 20 | 21 | Navigation.startSingleScreenApp({ 22 | screen: { 23 | screen: 'Menu', 24 | title: 'WixCal', 25 | }, 26 | appStyle: { 27 | navBarBackgroundColor: '#00adf5', 28 | navBarTextColor: 'white', 29 | navBarButtonColor: '#ffffff', 30 | statusBarTextColorScheme: 'light', 31 | autoAdjustScrollViewInsets: true 32 | } 33 | }); -------------------------------------------------------------------------------- /example/src/screens/agenda.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | Text, 4 | View, 5 | StyleSheet 6 | } from 'react-native'; 7 | import {Agenda} from 'react-native-calendars'; 8 | 9 | export default class AgendaScreen extends Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | items: {} 14 | }; 15 | } 16 | 17 | render() { 18 | return ( 19 | ({day ? day.day: 'item'})} 39 | /> 40 | ); 41 | } 42 | 43 | loadItems(day) { 44 | setTimeout(() => { 45 | for (let i = -15; i < 85; i++) { 46 | const time = day.timestamp + i * 24 * 60 * 60 * 1000; 47 | const strTime = this.timeToString(time); 48 | if (!this.state.items[strTime]) { 49 | this.state.items[strTime] = []; 50 | const numItems = Math.floor(Math.random() * 5); 51 | for (let j = 0; j < numItems; j++) { 52 | this.state.items[strTime].push({ 53 | name: 'Item for ' + strTime, 54 | height: Math.max(50, Math.floor(Math.random() * 150)) 55 | }); 56 | } 57 | } 58 | } 59 | //console.log(this.state.items); 60 | const newItems = {}; 61 | Object.keys(this.state.items).forEach(key => {newItems[key] = this.state.items[key];}); 62 | this.setState({ 63 | items: newItems 64 | }); 65 | }, 1000); 66 | // console.log(`Load Items for ${day.year}-${day.month}`); 67 | } 68 | 69 | renderItem(item) { 70 | return ( 71 | {item.name} 72 | ); 73 | } 74 | 75 | renderEmptyDate() { 76 | return ( 77 | This is empty date! 78 | ); 79 | } 80 | 81 | rowHasChanged(r1, r2) { 82 | return r1.name !== r2.name; 83 | } 84 | 85 | timeToString(time) { 86 | const date = new Date(time); 87 | return date.toISOString().split('T')[0]; 88 | } 89 | } 90 | 91 | const styles = StyleSheet.create({ 92 | item: { 93 | backgroundColor: 'white', 94 | flex: 1, 95 | borderRadius: 5, 96 | padding: 10, 97 | marginRight: 10, 98 | marginTop: 17 99 | }, 100 | emptyDate: { 101 | height: 15, 102 | flex:1, 103 | paddingTop: 30 104 | } 105 | }); 106 | -------------------------------------------------------------------------------- /example/src/screens/calendars.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | Text, 4 | StyleSheet, 5 | ScrollView, 6 | View 7 | } from 'react-native'; 8 | import {Calendar} from 'react-native-calendars'; 9 | 10 | export default class CalendarsScreen extends Component { 11 | constructor(props) { 12 | super(props); 13 | this.state = {}; 14 | this.onDayPress = this.onDayPress.bind(this); 15 | } 16 | 17 | render() { 18 | return ( 19 | 20 | Calendar with selectable date and arrows 21 | 27 | Calendar with marked dates and hidden arrows 28 | 44 | Calendar with custom day component 45 | { 48 | return ({date.day}); 49 | }} 50 | /> 51 | Calendar with period marking and spinner 52 | 88 | Calendar with multi-dot marking 89 | 99 | Calendar with multi-period marking 100 | 128 | Calendar with week numbers 129 | 136 | Custom calendar with custom marking type 137 | 252 | 253 | ); 254 | } 255 | 256 | onDayPress(day) { 257 | this.setState({ 258 | selected: day.dateString 259 | }); 260 | } 261 | } 262 | 263 | const styles = StyleSheet.create({ 264 | calendar: { 265 | borderTopWidth: 1, 266 | paddingTop: 5, 267 | borderBottomWidth: 1, 268 | borderColor: '#eee', 269 | height: 350 270 | }, 271 | text: { 272 | textAlign: 'center', 273 | borderColor: '#bbb', 274 | padding: 10, 275 | backgroundColor: '#eee' 276 | }, 277 | container: { 278 | flex: 1, 279 | backgroundColor: 'gray' 280 | } 281 | }); 282 | -------------------------------------------------------------------------------- /example/src/screens/calendarsList.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | import {CalendarList} from 'react-native-calendars'; 4 | 5 | export default class CalendarsList extends Component { 6 | constructor(props) { 7 | super(props); 8 | } 9 | 10 | render() { 11 | return ( 12 | 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/src/screens/horizontalCalendarList.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | import {CalendarList} from 'react-native-calendars'; 4 | import {View} from 'react-native'; 5 | 6 | export default class HorizontalCalendarList extends Component { 7 | constructor(props) { 8 | super(props); 9 | } 10 | 11 | render() { 12 | return ( 13 | 14 | 22 | 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/src/screens/index.js: -------------------------------------------------------------------------------- 1 | import {Navigation} from 'react-native-navigation'; 2 | 3 | import MenuScreen from './menu'; 4 | import CalendarsScreen from './calendars'; 5 | import AgendaScreen from './agenda'; 6 | import CalendarsList from './calendarsList'; 7 | import HorizontalCalendarList from './horizontalCalendarList'; 8 | 9 | export function registerScreens() { 10 | Navigation.registerComponent('Menu', () => MenuScreen); 11 | Navigation.registerComponent('Calendars', () => CalendarsScreen); 12 | Navigation.registerComponent('Agenda', () => AgendaScreen); 13 | Navigation.registerComponent('CalendarsList', () => CalendarsList); 14 | Navigation.registerComponent('HorizontalCalendarList', () => HorizontalCalendarList); 15 | } 16 | -------------------------------------------------------------------------------- /example/src/screens/menu.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | Text, 4 | View, 5 | TouchableOpacity, 6 | StyleSheet, 7 | } from 'react-native'; 8 | 9 | export default class MenuScreen extends Component { 10 | 11 | render() { 12 | return ( 13 | 14 | 15 | Calendars 16 | 17 | 18 | Calendar List 19 | 20 | 21 | Horizontal Calendar List 22 | 23 | 24 | Agenda 25 | 26 | 27 | ); 28 | } 29 | 30 | onCalendarsPress() { 31 | this.props.navigator.push({ 32 | screen: 'Calendars', 33 | title: 'Calendars' 34 | }); 35 | } 36 | 37 | onCalendarListPress() { 38 | this.props.navigator.push({ 39 | screen: 'CalendarsList', 40 | title: 'Calendar List' 41 | }); 42 | } 43 | 44 | onHorizontalCalendarListPress() { 45 | this.props.navigator.push({ 46 | screen: 'HorizontalCalendarList', 47 | title: 'Horizontal Calendars List' 48 | }); 49 | } 50 | 51 | onAgendaPress() { 52 | this.props.navigator.push({ 53 | screen: 'Agenda', 54 | title: 'Agenda' 55 | }); 56 | } 57 | } 58 | 59 | const styles = StyleSheet.create({ 60 | menu: { 61 | height: 50, 62 | justifyContent: 'center', 63 | paddingLeft: 15, 64 | borderBottomWidth: 1 65 | }, 66 | menuText: { 67 | fontSize: 18 68 | } 69 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-calendars", 3 | "version": "1.20.0", 4 | "main": "src/index.js", 5 | "description": "React Native Calendar Components", 6 | "scripts": { 7 | "test": "jasmine src/*.spec.js && npm run lint", 8 | "lint": "eslint src/ example/src" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/wix/react-native-calendars" 13 | }, 14 | "publishConfig": { 15 | "registry": "https://registry.npmjs.org/" 16 | }, 17 | "author": "Wix.com", 18 | "license": "MIT", 19 | "dependencies": { 20 | "lodash.get": "^4.4.2", 21 | "lodash.isequal": "^4.5.0", 22 | "prop-types": "^15.5.10", 23 | "xdate": "^0.8.0" 24 | }, 25 | "devDependencies": { 26 | "babel-eslint": "^7.2.3", 27 | "eslint": "^3.19.0", 28 | "eslint-plugin-react": "^7.0.0", 29 | "jasmine": "^2.5.2", 30 | "react": "16.0.0-alpha.12", 31 | "react-native": "0.47.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | com.wixpress.hotels 5 | wix-react-native-calendar 6 | 1.0.0-SNAPSHOT 7 | pom 8 | Wix React Native Calendar 9 | WiX React Native Calendar 10 | 11 | 12 | com.wixpress.common 13 | wix-master-parent 14 | 100.0.0-SNAPSHOT 15 | 16 | 17 | 18 | 19 | tautvilas@wix.com 20 | Tautvilas Mecinskas 21 | tautvilas@wix.com 22 | Wix 23 | http://www.wix.com 24 | 25 | owner 26 | 27 | -2 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /spec/runner.js: -------------------------------------------------------------------------------- 1 | import Jasmine from 'jasmine'; 2 | import path from 'path'; 3 | 4 | var jasmine = new Jasmine(); 5 | jasmine.loadConfigFile(path.resolve(__dirname, 'support', 'jasmine.json')); 6 | jasmine.execute(); 7 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "src", 3 | "spec_files": [ 4 | "*.spec.js" 5 | ], 6 | "stopSpecOnExpectationFailure": false, 7 | "random": false 8 | } 9 | -------------------------------------------------------------------------------- /src/agenda/img/knob@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/src/agenda/img/knob@2x.png -------------------------------------------------------------------------------- /src/agenda/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | Text, 4 | View, 5 | Dimensions, 6 | Animated, 7 | ViewPropTypes, 8 | } from 'react-native'; 9 | import PropTypes from 'prop-types'; 10 | import XDate from 'xdate'; 11 | 12 | import {parseDate, xdateToData} from '../interface'; 13 | import dateutils from '../dateutils'; 14 | import CalendarList from '../calendar-list'; 15 | import ReservationsList from './reservation-list'; 16 | import styleConstructor from './style'; 17 | import { VelocityTracker } from '../input'; 18 | 19 | const HEADER_HEIGHT = 104; 20 | const KNOB_HEIGHT = 24; 21 | 22 | //Fallback when RN version is < 0.44 23 | const viewPropTypes = ViewPropTypes || View.propTypes; 24 | 25 | export default class AgendaView extends Component { 26 | static propTypes = { 27 | // Specify theme properties to override specific styles for calendar parts. Default = {} 28 | theme: PropTypes.object, 29 | 30 | // agenda container style 31 | style: viewPropTypes.style, 32 | 33 | // the list of items that have to be displayed in agenda. If you want to render item as empty date 34 | // the value of date key has to be an empty array []. If there exists no value for date key it is 35 | // considered that the date in question is not yet loaded 36 | items: PropTypes.object, 37 | 38 | // callback that gets called when items for a certain month should be loaded (month became visible) 39 | loadItemsForMonth: PropTypes.func, 40 | // callback that fires when the calendar is opened or closed 41 | onCalendarToggled: PropTypes.func, 42 | // callback that gets called on day press 43 | onDayPress: PropTypes.func, 44 | // callback that gets called when day changes while scrolling agenda list 45 | onDaychange: PropTypes.func, 46 | // specify how each item should be rendered in agenda 47 | renderItem: PropTypes.func, 48 | // specify how each date should be rendered. day can be undefined if the item is not first in that day. 49 | renderDay: PropTypes.func, 50 | // specify how agenda knob should look like 51 | renderKnob: PropTypes.func, 52 | // specify how empty date content with no items should be rendered 53 | renderEmptyDay: PropTypes.func, 54 | // specify what should be rendered instead of ActivityIndicator 55 | renderEmptyData: PropTypes.func, 56 | // specify your item comparison function for increased performance 57 | rowHasChanged: PropTypes.func, 58 | 59 | // Max amount of months allowed to scroll to the past. Default = 50 60 | pastScrollRange: PropTypes.number, 61 | 62 | // Max amount of months allowed to scroll to the future. Default = 50 63 | futureScrollRange: PropTypes.number, 64 | 65 | // initially selected day 66 | selected: PropTypes.any, 67 | // Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined 68 | minDate: PropTypes.any, 69 | // Maximum date that can be selected, dates after maxDate will be grayed out. Default = undefined 70 | maxDate: PropTypes.any, 71 | 72 | // If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday. 73 | firstDay: PropTypes.number, 74 | 75 | // Collection of dates that have to be marked. Default = items 76 | markedDates: PropTypes.object, 77 | // Optional marking type if custom markedDates are provided 78 | markingType: PropTypes.string, 79 | 80 | // Hide knob button. Default = false 81 | hideKnob: PropTypes.bool, 82 | // Month format in calendar title. Formatting values: http://arshaw.com/xdate/#Formatting 83 | monthFormat: PropTypes.string, 84 | // A RefreshControl component, used to provide pull-to-refresh functionality for the ScrollView. 85 | refreshControl: PropTypes.element, 86 | // If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make sure to also set the refreshing prop correctly. 87 | onRefresh: PropTypes.func, 88 | // Set this true while waiting for new data from a refresh. 89 | refreshing: PropTypes.bool, 90 | // Display loading indicador. Default = false 91 | displayLoadingIndicator: PropTypes.bool, 92 | }; 93 | 94 | constructor(props) { 95 | super(props); 96 | this.styles = styleConstructor(props.theme); 97 | const windowSize = Dimensions.get('window'); 98 | this.viewHeight = windowSize.height; 99 | this.viewWidth = windowSize.width; 100 | this.scrollTimeout = undefined; 101 | this.headerState = 'idle'; 102 | this.state = { 103 | scrollY: new Animated.Value(0), 104 | calendarIsReady: false, 105 | calendarScrollable: false, 106 | firstResevationLoad: false, 107 | selectedDay: parseDate(this.props.selected) || XDate(true), 108 | topDay: parseDate(this.props.selected) || XDate(true), 109 | }; 110 | this.currentMonth = this.state.selectedDay.clone(); 111 | this.onLayout = this.onLayout.bind(this); 112 | this.onScrollPadLayout = this.onScrollPadLayout.bind(this); 113 | this.onTouchStart = this.onTouchStart.bind(this); 114 | this.onTouchEnd = this.onTouchEnd.bind(this); 115 | this.onStartDrag = this.onStartDrag.bind(this); 116 | this.onSnapAfterDrag = this.onSnapAfterDrag.bind(this); 117 | this.generateMarkings = this.generateMarkings.bind(this); 118 | this.knobTracker = new VelocityTracker(); 119 | this.state.scrollY.addListener(({value}) => this.knobTracker.add(value)); 120 | } 121 | 122 | calendarOffset() { 123 | return 90 - (this.viewHeight / 2); 124 | } 125 | 126 | initialScrollPadPosition() { 127 | return Math.max(0, this.viewHeight - HEADER_HEIGHT); 128 | } 129 | 130 | setScrollPadPosition(y, animated) { 131 | this.scrollPad._component.scrollTo({x: 0, y, animated}); 132 | } 133 | 134 | onScrollPadLayout() { 135 | // When user touches knob, the actual component that receives touch events is a ScrollView. 136 | // It needs to be scrolled to the bottom, so that when user moves finger downwards, 137 | // scroll position actually changes (it would stay at 0, when scrolled to the top). 138 | this.setScrollPadPosition(this.initialScrollPadPosition(), false); 139 | // delay rendering calendar in full height because otherwise it still flickers sometimes 140 | setTimeout(() => this.setState({calendarIsReady: true}), 0); 141 | } 142 | 143 | onLayout(event) { 144 | this.viewHeight = event.nativeEvent.layout.height; 145 | this.viewWidth = event.nativeEvent.layout.width; 146 | this.forceUpdate(); 147 | } 148 | 149 | onTouchStart() { 150 | this.headerState = 'touched'; 151 | if (this.knob) { 152 | this.knob.setNativeProps({style: { opacity: 0.5 }}); 153 | } 154 | } 155 | 156 | onTouchEnd() { 157 | if (this.knob) { 158 | this.knob.setNativeProps({style: { opacity: 1 }}); 159 | } 160 | 161 | if (this.headerState === 'touched') { 162 | this.setScrollPadPosition(0, true); 163 | this.enableCalendarScrolling(); 164 | } 165 | this.headerState = 'idle'; 166 | } 167 | 168 | onStartDrag() { 169 | this.headerState = 'dragged'; 170 | this.knobTracker.reset(); 171 | } 172 | 173 | onSnapAfterDrag(e) { 174 | // on Android onTouchEnd is not called if dragging was started 175 | this.onTouchEnd(); 176 | const currentY = e.nativeEvent.contentOffset.y; 177 | this.knobTracker.add(currentY); 178 | const projectedY = currentY + this.knobTracker.estimateSpeed() * 250/*ms*/; 179 | const maxY = this.initialScrollPadPosition(); 180 | const snapY = (projectedY > maxY / 2) ? maxY : 0; 181 | this.setScrollPadPosition(snapY, true); 182 | if (snapY === 0) { 183 | this.enableCalendarScrolling(); 184 | } 185 | } 186 | 187 | onVisibleMonthsChange(months) { 188 | if (this.props.items && !this.state.firstResevationLoad) { 189 | clearTimeout(this.scrollTimeout); 190 | this.scrollTimeout = setTimeout(() => { 191 | if (this.props.loadItemsForMonth && this._isMounted) { 192 | this.props.loadItemsForMonth(months[0]); 193 | } 194 | }, 200); 195 | } 196 | } 197 | 198 | loadReservations(props) { 199 | if ((!props.items || !Object.keys(props.items).length) && !this.state.firstResevationLoad) { 200 | this.setState({ 201 | firstResevationLoad: true 202 | }, () => { 203 | if (this.props.loadItemsForMonth) { 204 | this.props.loadItemsForMonth(xdateToData(this.state.selectedDay)); 205 | } 206 | }); 207 | } 208 | } 209 | 210 | componentWillMount() { 211 | this._isMounted = true; 212 | this.loadReservations(this.props); 213 | } 214 | 215 | componentWillUnmount() { 216 | this._isMounted = false; 217 | } 218 | 219 | componentWillReceiveProps(props) { 220 | if (props.items) { 221 | this.setState({ 222 | firstResevationLoad: false 223 | }); 224 | } else { 225 | this.loadReservations(props); 226 | } 227 | } 228 | 229 | enableCalendarScrolling() { 230 | this.setState({ 231 | calendarScrollable: true 232 | }); 233 | if (this.props.onCalendarToggled) { 234 | this.props.onCalendarToggled(true); 235 | } 236 | // Enlarge calendarOffset here as a workaround on iOS to force repaint. 237 | // Otherwise the month after current one or before current one remains invisible. 238 | // The problem is caused by overflow: 'hidden' style, which we need for dragging 239 | // to be performant. 240 | // Another working solution for this bug would be to set removeClippedSubviews={false} 241 | // in CalendarList listView, but that might impact performance when scrolling 242 | // month list in expanded CalendarList. 243 | // Further info https://github.com/facebook/react-native/issues/1831 244 | this.calendar.scrollToDay(this.state.selectedDay, this.calendarOffset() + 1, true); 245 | } 246 | 247 | _chooseDayFromCalendar(d) { 248 | this.chooseDay(d, !this.state.calendarScrollable); 249 | } 250 | 251 | chooseDay(d, optimisticScroll) { 252 | const day = parseDate(d); 253 | this.setState({ 254 | calendarScrollable: false, 255 | selectedDay: day.clone() 256 | }); 257 | if (this.props.onCalendarToggled) { 258 | this.props.onCalendarToggled(false); 259 | } 260 | if (!optimisticScroll) { 261 | this.setState({ 262 | topDay: day.clone() 263 | }); 264 | } 265 | this.setScrollPadPosition(this.initialScrollPadPosition(), true); 266 | this.calendar.scrollToDay(day, this.calendarOffset(), true); 267 | if (this.props.loadItemsForMonth) { 268 | this.props.loadItemsForMonth(xdateToData(day)); 269 | } 270 | if (this.props.onDayPress) { 271 | this.props.onDayPress(xdateToData(day)); 272 | } 273 | } 274 | 275 | renderReservations() { 276 | return ( 277 | {}} 291 | ref={(c) => this.list = c} 292 | theme={this.props.theme} 293 | /> 294 | ); 295 | } 296 | 297 | onDayChange(day) { 298 | const newDate = parseDate(day); 299 | const withAnimation = dateutils.sameMonth(newDate, this.state.selectedDay); 300 | this.calendar.scrollToDay(day, this.calendarOffset(), withAnimation); 301 | this.setState({ 302 | selectedDay: parseDate(day) 303 | }); 304 | 305 | if (this.props.onDayChange) { 306 | this.props.onDayChange(xdateToData(newDate)); 307 | } 308 | } 309 | 310 | generateMarkings() { 311 | let markings = this.props.markedDates; 312 | if (!markings) { 313 | markings = {}; 314 | Object.keys(this.props.items || {}).forEach(key => { 315 | if (this.props.items[key] && this.props.items[key].length) { 316 | markings[key] = {marked: true}; 317 | } 318 | }); 319 | } 320 | const key = this.state.selectedDay.toString('yyyy-MM-dd'); 321 | return {...markings, [key]: {...(markings[key] || {}), ...{selected: true}}}; 322 | } 323 | 324 | render() { 325 | const agendaHeight = Math.max(0, this.viewHeight - HEADER_HEIGHT); 326 | const weekDaysNames = dateutils.weekDayNames(this.props.firstDay); 327 | const weekdaysStyle = [this.styles.weekdays, { 328 | opacity: this.state.scrollY.interpolate({ 329 | inputRange: [agendaHeight - HEADER_HEIGHT, agendaHeight], 330 | outputRange: [0, 1], 331 | extrapolate: 'clamp', 332 | }), 333 | transform: [{ translateY: this.state.scrollY.interpolate({ 334 | inputRange: [Math.max(0, agendaHeight - HEADER_HEIGHT), agendaHeight], 335 | outputRange: [-HEADER_HEIGHT, 0], 336 | extrapolate: 'clamp', 337 | })}] 338 | }]; 339 | 340 | const headerTranslate = this.state.scrollY.interpolate({ 341 | inputRange: [0, agendaHeight], 342 | outputRange: [agendaHeight, 0], 343 | extrapolate: 'clamp', 344 | }); 345 | 346 | const contentTranslate = this.state.scrollY.interpolate({ 347 | inputRange: [0, agendaHeight], 348 | outputRange: [0, agendaHeight/2], 349 | extrapolate: 'clamp', 350 | }); 351 | 352 | const headerStyle = [ 353 | this.styles.header, 354 | { bottom: agendaHeight, transform: [{ translateY: headerTranslate }] }, 355 | ]; 356 | 357 | if (!this.state.calendarIsReady) { 358 | // limit header height until everything is setup for calendar dragging 359 | headerStyle.push({height: 0}); 360 | // fill header with appStyle.calendarBackground background to reduce flickering 361 | weekdaysStyle.push({height: HEADER_HEIGHT}); 362 | } 363 | 364 | const shouldAllowDragging = !this.props.hideKnob && !this.state.calendarScrollable; 365 | const scrollPadPosition = (shouldAllowDragging ? HEADER_HEIGHT : 0) - KNOB_HEIGHT; 366 | 367 | const scrollPadStyle = { 368 | position: 'absolute', 369 | width: 80, 370 | height: KNOB_HEIGHT, 371 | top: scrollPadPosition, 372 | left: (this.viewWidth - 80) / 2, 373 | }; 374 | 375 | let knob = (); 376 | 377 | if (!this.props.hideKnob) { 378 | const knobView = this.props.renderKnob ? this.props.renderKnob() : (); 379 | knob = this.state.calendarScrollable ? null : ( 380 | 381 | this.knob = c}>{knobView} 382 | 383 | ); 384 | } 385 | 386 | return ( 387 | 388 | 389 | {this.renderReservations()} 390 | 391 | 392 | 393 | { 395 | this.calendar.scrollToDay(this.state.selectedDay.clone(), this.calendarOffset(), false); 396 | }} 397 | calendarWidth={this.viewWidth} 398 | theme={this.props.theme} 399 | onVisibleMonthsChange={this.onVisibleMonthsChange.bind(this)} 400 | ref={(c) => this.calendar = c} 401 | minDate={this.props.minDate} 402 | maxDate={this.props.maxDate} 403 | current={this.currentMonth} 404 | markedDates={this.generateMarkings()} 405 | markingType={this.props.markingType} 406 | removeClippedSubviews={this.props.removeClippedSubviews} 407 | onDayPress={this._chooseDayFromCalendar.bind(this)} 408 | scrollingEnabled={this.state.calendarScrollable} 409 | hideExtraDays={this.state.calendarScrollable} 410 | firstDay={this.props.firstDay} 411 | monthFormat={this.props.monthFormat} 412 | pastScrollRange={this.props.pastScrollRange} 413 | futureScrollRange={this.props.futureScrollRange} 414 | dayComponent={this.props.dayComponent} 415 | disabledByDefault={this.props.disabledByDefault} 416 | displayLoadingIndicator={this.props.displayLoadingIndicator} 417 | showWeekNumbers={this.props.showWeekNumbers} 418 | /> 419 | 420 | {knob} 421 | 422 | 423 | {this.props.showWeekNumbers && } 424 | {weekDaysNames.map((day) => ( 425 | {day} 426 | ))} 427 | 428 | this.scrollPad = c} 430 | overScrollMode='never' 431 | showsHorizontalScrollIndicator={false} 432 | showsVerticalScrollIndicator={false} 433 | style={scrollPadStyle} 434 | scrollEventThrottle={1} 435 | scrollsToTop={false} 436 | onTouchStart={this.onTouchStart} 437 | onTouchEnd={this.onTouchEnd} 438 | onScrollBeginDrag={this.onStartDrag} 439 | onScrollEndDrag={this.onSnapAfterDrag} 440 | onScroll={Animated.event( 441 | [{ nativeEvent: { contentOffset: { y: this.state.scrollY } } }], 442 | { useNativeDriver: true }, 443 | )} 444 | > 445 | 446 | 447 | 448 | ); 449 | } 450 | } 451 | -------------------------------------------------------------------------------- /src/agenda/platform-style.ios.js: -------------------------------------------------------------------------------- 1 | export default function platformStyles(appStyle) { 2 | return { 3 | knob: { 4 | width: 38, 5 | height: 7, 6 | marginTop: 10, 7 | borderRadius: 3, 8 | backgroundColor: appStyle.agendaKnobColor 9 | }, 10 | weekdays: { 11 | position: 'absolute', 12 | left: 0, 13 | right: 0, 14 | top: 0, 15 | flexDirection: 'row', 16 | justifyContent: 'space-around', 17 | marginLeft: 15, 18 | marginRight: 15, 19 | paddingTop: 15, 20 | paddingBottom: 7, 21 | backgroundColor: appStyle.calendarBackground 22 | }, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/agenda/platform-style.js: -------------------------------------------------------------------------------- 1 | export default function platformStyles(appStyle) { 2 | return { 3 | knob: { 4 | width: 38, 5 | height: 7, 6 | marginTop: 10, 7 | borderRadius: 3, 8 | backgroundColor: appStyle.agendaKnobColor 9 | }, 10 | weekdays: { 11 | position: 'absolute', 12 | left: 0, 13 | right: 0, 14 | top: 0, 15 | flexDirection: 'row', 16 | justifyContent: 'space-between', 17 | paddingLeft: 24, 18 | paddingRight: 24, 19 | paddingTop: 15, 20 | paddingBottom: 7, 21 | backgroundColor: appStyle.calendarBackground 22 | }, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/agenda/reservation-list/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | FlatList, 4 | ActivityIndicator, 5 | View 6 | } from 'react-native'; 7 | import Reservation from './reservation'; 8 | import PropTypes from 'prop-types'; 9 | import XDate from 'xdate'; 10 | 11 | import dateutils from '../../dateutils'; 12 | import styleConstructor from './style'; 13 | 14 | class ReactComp extends Component { 15 | static propTypes = { 16 | // specify your item comparison function for increased performance 17 | rowHasChanged: PropTypes.func, 18 | // specify how each item should be rendered in agenda 19 | renderItem: PropTypes.func, 20 | // specify how each date should be rendered. day can be undefined if the item is not first in that day. 21 | renderDay: PropTypes.func, 22 | // specify how empty date content with no items should be rendered 23 | renderEmptyDate: PropTypes.func, 24 | // callback that gets called when day changes while scrolling agenda list 25 | onDayChange: PropTypes.func, 26 | // onScroll ListView event 27 | onScroll: PropTypes.func, 28 | // the list of items that have to be displayed in agenda. If you want to render item as empty date 29 | // the value of date key kas to be an empty array []. If there exists no value for date key it is 30 | // considered that the date in question is not yet loaded 31 | reservations: PropTypes.object, 32 | 33 | selectedDay: PropTypes.instanceOf(XDate), 34 | topDay: PropTypes.instanceOf(XDate), 35 | refreshControl: PropTypes.element, 36 | refreshing: PropTypes.bool, 37 | onRefresh: PropTypes.func, 38 | }; 39 | 40 | constructor(props) { 41 | super(props); 42 | this.styles = styleConstructor(props.theme); 43 | this.state = { 44 | reservations: [] 45 | }; 46 | this.heights=[]; 47 | this.selectedDay = this.props.selectedDay; 48 | this.scrollOver = true; 49 | } 50 | 51 | componentWillMount() { 52 | this.updateDataSource(this.getReservations(this.props).reservations); 53 | } 54 | 55 | updateDataSource(reservations) { 56 | this.setState({ 57 | reservations 58 | }); 59 | } 60 | 61 | updateReservations(props) { 62 | const reservations = this.getReservations(props); 63 | if (this.list && !dateutils.sameDate(props.selectedDay, this.selectedDay)) { 64 | let scrollPosition = 0; 65 | for (let i = 0; i < reservations.scrollPosition; i++) { 66 | scrollPosition += this.heights[i] || 0; 67 | } 68 | this.scrollOver = false; 69 | this.list.scrollToOffset({offset: scrollPosition, animated: true}); 70 | } 71 | this.selectedDay = props.selectedDay; 72 | this.updateDataSource(reservations.reservations); 73 | } 74 | 75 | componentWillReceiveProps(props) { 76 | if (!dateutils.sameDate(props.topDay, this.props.topDay)) { 77 | this.setState({ 78 | reservations: [] 79 | }, () => { 80 | this.updateReservations(props); 81 | }); 82 | } else { 83 | this.updateReservations(props); 84 | } 85 | } 86 | 87 | onScroll(event) { 88 | const yOffset = event.nativeEvent.contentOffset.y; 89 | this.props.onScroll(yOffset); 90 | let topRowOffset = 0; 91 | let topRow; 92 | for (topRow = 0; topRow < this.heights.length; topRow++) { 93 | if (topRowOffset + this.heights[topRow] / 2 >= yOffset) { 94 | break; 95 | } 96 | topRowOffset += this.heights[topRow]; 97 | } 98 | const row = this.state.reservations[topRow]; 99 | if (!row) return; 100 | const day = row.day; 101 | const sameDate = dateutils.sameDate(day, this.selectedDay); 102 | if (!sameDate && this.scrollOver) { 103 | this.selectedDay = day.clone(); 104 | this.props.onDayChange(day.clone()); 105 | } 106 | } 107 | 108 | onRowLayoutChange(ind, event) { 109 | this.heights[ind] = event.nativeEvent.layout.height; 110 | } 111 | 112 | renderRow({item, index}) { 113 | return ( 114 | 115 | 123 | 124 | ); 125 | } 126 | 127 | getReservationsForDay(iterator, props) { 128 | const day = iterator.clone(); 129 | const res = props.reservations[day.toString('yyyy-MM-dd')]; 130 | if (res && res.length) { 131 | return res.map((reservation, i) => { 132 | return { 133 | reservation, 134 | date: i ? false : day, 135 | day 136 | }; 137 | }); 138 | } else if (res) { 139 | return [{ 140 | date: iterator.clone(), 141 | day 142 | }]; 143 | } else { 144 | return false; 145 | } 146 | } 147 | 148 | onListTouch() { 149 | this.scrollOver = true; 150 | } 151 | 152 | getReservations(props) { 153 | if (!props.reservations || !props.selectedDay) { 154 | return {reservations: [], scrollPosition: 0}; 155 | } 156 | let reservations = []; 157 | if (this.state.reservations && this.state.reservations.length) { 158 | const iterator = this.state.reservations[0].day.clone(); 159 | while (iterator.getTime() < props.selectedDay.getTime()) { 160 | const res = this.getReservationsForDay(iterator, props); 161 | if (!res) { 162 | reservations = []; 163 | break; 164 | } else { 165 | reservations = reservations.concat(res); 166 | } 167 | iterator.addDays(1); 168 | } 169 | } 170 | const scrollPosition = reservations.length; 171 | const iterator = props.selectedDay.clone(); 172 | for (let i = 0; i < 31; i++) { 173 | const res = this.getReservationsForDay(iterator, props); 174 | if (res) { 175 | reservations = reservations.concat(res); 176 | } 177 | iterator.addDays(1); 178 | } 179 | 180 | return {reservations, scrollPosition}; 181 | } 182 | 183 | render() { 184 | if (!this.props.reservations || !this.props.reservations[this.props.selectedDay.toString('yyyy-MM-dd')]) { 185 | if (this.props.renderEmptyData) { 186 | return this.props.renderEmptyData(); 187 | } 188 | return (); 189 | } 190 | return ( 191 | this.list = c} 193 | style={this.props.style} 194 | contentContainerStyle={this.styles.content} 195 | renderItem={this.renderRow.bind(this)} 196 | data={this.state.reservations} 197 | onScroll={this.onScroll.bind(this)} 198 | showsVerticalScrollIndicator={false} 199 | scrollEventThrottle={200} 200 | onMoveShouldSetResponderCapture={() => {this.onListTouch(); return false;}} 201 | keyExtractor={(item, index) => String(index)} 202 | refreshControl={this.props.refreshControl} 203 | refreshing={this.props.refreshing || false} 204 | onRefresh={this.props.onRefresh} 205 | /> 206 | ); 207 | } 208 | } 209 | 210 | export default ReactComp; 211 | -------------------------------------------------------------------------------- /src/agenda/reservation-list/reservation.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {View, Text} from 'react-native'; 3 | import {xdateToData} from '../../interface'; 4 | import XDate from 'xdate'; 5 | import dateutils from '../../dateutils'; 6 | import styleConstructor from './style'; 7 | 8 | class ReservationListItem extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.styles = styleConstructor(props.theme); 12 | } 13 | 14 | shouldComponentUpdate(nextProps) { 15 | const r1 = this.props.item; 16 | const r2 = nextProps.item; 17 | let changed = true; 18 | if (!r1 && !r2) { 19 | changed = false; 20 | } else if (r1 && r2) { 21 | if (r1.day.getTime() !== r2.day.getTime()) { 22 | changed = true; 23 | } else if (!r1.reservation && !r2.reservation) { 24 | changed = false; 25 | } else if (r1.reservation && r2.reservation) { 26 | if ((!r1.date && !r2.date) || (r1.date && r2.date)) { 27 | changed = this.props.rowHasChanged(r1.reservation, r2.reservation); 28 | } 29 | } 30 | } 31 | return changed; 32 | } 33 | 34 | renderDate(date, item) { 35 | if (this.props.renderDay) { 36 | return this.props.renderDay(date ? xdateToData(date) : undefined, item); 37 | } 38 | const today = dateutils.sameDate(date, XDate()) ? this.styles.today : undefined; 39 | if (date) { 40 | return ( 41 | 42 | {date.getDate()} 43 | {XDate.locales[XDate.defaultLocale].dayNamesShort[date.getDay()]} 44 | 45 | ); 46 | } else { 47 | return ( 48 | 49 | ); 50 | } 51 | } 52 | 53 | render() { 54 | const {reservation, date} = this.props.item; 55 | let content; 56 | if (reservation) { 57 | const firstItem = date ? true : false; 58 | content = this.props.renderItem(reservation, firstItem); 59 | } else { 60 | content = this.props.renderEmptyDate(date); 61 | } 62 | return ( 63 | 64 | {this.renderDate(date, reservation)} 65 | 66 | {content} 67 | 68 | 69 | ); 70 | } 71 | } 72 | 73 | export default ReservationListItem; 74 | -------------------------------------------------------------------------------- /src/agenda/reservation-list/style.js: -------------------------------------------------------------------------------- 1 | import {StyleSheet} from 'react-native'; 2 | import * as defaultStyle from '../../style'; 3 | 4 | const STYLESHEET_ID = 'stylesheet.agenda.list'; 5 | 6 | export default function styleConstructor(theme = {}) { 7 | const appStyle = {...defaultStyle, ...theme}; 8 | return StyleSheet.create({ 9 | container: { 10 | flexDirection: 'row' 11 | }, 12 | dayNum: { 13 | fontSize: 28, 14 | fontWeight: '200', 15 | color: appStyle.agendaDayNumColor 16 | }, 17 | dayText: { 18 | fontSize: 14, 19 | fontWeight: '300', 20 | color: appStyle.agendaDayTextColor, 21 | marginTop: -5, 22 | backgroundColor: 'rgba(0,0,0,0)' 23 | }, 24 | day: { 25 | width: 63, 26 | alignItems: 'center', 27 | justifyContent: 'flex-start', 28 | marginTop: 32 29 | }, 30 | today: { 31 | color: appStyle.agendaTodayColor 32 | }, 33 | ...(theme[STYLESHEET_ID] || {}) 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /src/agenda/style.js: -------------------------------------------------------------------------------- 1 | import {StyleSheet} from 'react-native'; 2 | import * as defaultStyle from '../style'; 3 | import platformStyles from './platform-style'; 4 | 5 | const STYLESHEET_ID = 'stylesheet.agenda.main'; 6 | 7 | export default function styleConstructor(theme = {}) { 8 | const appStyle = {...defaultStyle, ...theme}; 9 | const { knob, weekdays } = platformStyles(appStyle); 10 | return StyleSheet.create({ 11 | knob, 12 | weekdays, 13 | header: { 14 | overflow: 'hidden', 15 | justifyContent: 'flex-end', 16 | position:'absolute', 17 | height:'100%', 18 | width:'100%', 19 | }, 20 | calendar: { 21 | flex: 1, 22 | borderBottomWidth: 1, 23 | borderColor: appStyle.separatorColor 24 | }, 25 | knobContainer: { 26 | flex: 1, 27 | position: 'absolute', 28 | left: 0, 29 | right: 0, 30 | height: 24, 31 | bottom: 0, 32 | alignItems: 'center', 33 | backgroundColor: appStyle.calendarBackground 34 | }, 35 | weekday: { 36 | width: 32, 37 | textAlign: 'center', 38 | fontSize: 13, 39 | color: appStyle.textSectionTitleColor, 40 | }, 41 | reservations: { 42 | flex: 1, 43 | marginTop: 104, 44 | backgroundColor: appStyle.backgroundColor 45 | }, 46 | ...(theme[STYLESHEET_ID] || {}) 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /src/calendar-list/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | FlatList, Platform, Dimensions, 4 | } from 'react-native'; 5 | import PropTypes from 'prop-types'; 6 | import XDate from 'xdate'; 7 | 8 | import {xdateToData, parseDate} from '../interface'; 9 | import styleConstructor from './style'; 10 | import dateutils from '../dateutils'; 11 | import Calendar from '../calendar'; 12 | import CalendarListItem from './item'; 13 | 14 | const {width} = Dimensions.get('window'); 15 | 16 | class CalendarList extends Component { 17 | static propTypes = { 18 | ...Calendar.propTypes, 19 | 20 | // Max amount of months allowed to scroll to the past. Default = 50 21 | pastScrollRange: PropTypes.number, 22 | 23 | // Max amount of months allowed to scroll to the future. Default = 50 24 | futureScrollRange: PropTypes.number, 25 | 26 | // Enable or disable scrolling of calendar list 27 | scrollEnabled: PropTypes.bool, 28 | 29 | // Enable or disable vertical scroll indicator. Default = false 30 | showScrollIndicator: PropTypes.bool, 31 | 32 | // When true, the calendar list scrolls to top when the status bar is tapped. Default = true 33 | scrollsToTop: PropTypes.bool, 34 | 35 | // Enable or disable paging on scroll 36 | pagingEnabled: PropTypes.bool, 37 | 38 | // Used when calendar scroll is horizontal, default is device width, pagination should be disabled 39 | calendarWidth: PropTypes.number, 40 | 41 | // Whether the scroll is horizontal 42 | horizontal: PropTypes.bool, 43 | // Dynamic calendar height 44 | calendarHeight: PropTypes.number, 45 | }; 46 | 47 | static defaultProps = { 48 | horizontal: false, 49 | calendarWidth: width, 50 | calendarHeight: 360, 51 | pastScrollRange: 50, 52 | futureScrollRange: 50, 53 | showScrollIndicator: false, 54 | scrollEnabled: true, 55 | scrollsToTop: false, 56 | removeClippedSubviews: Platform.OS === 'android' ? false : true, 57 | } 58 | 59 | constructor(props) { 60 | super(props); 61 | this.style = styleConstructor(props.theme); 62 | 63 | const rows = []; 64 | const texts = []; 65 | const date = parseDate(props.current) || XDate(); 66 | for (let i = 0; i <= this.props.pastScrollRange + this.props.futureScrollRange; i++) { 67 | const rangeDate = date.clone().addMonths(i - this.props.pastScrollRange, true); 68 | const rangeDateStr = rangeDate.toString('MMM yyyy'); 69 | texts.push(rangeDateStr); 70 | /* 71 | * This selects range around current shown month [-0, +2] or [-1, +1] month for detail calendar rendering. 72 | * If `this.pastScrollRange` is `undefined` it's equal to `false` or 0 in next condition. 73 | */ 74 | if (this.props.pastScrollRange - 1 <= i && i <= this.props.pastScrollRange + 1 || !this.props.pastScrollRange && i <= this.props.pastScrollRange + 2) { 75 | rows.push(rangeDate); 76 | } else { 77 | rows.push(rangeDateStr); 78 | } 79 | } 80 | 81 | this.state = { 82 | rows, 83 | texts, 84 | openDate: date 85 | }; 86 | 87 | this.onViewableItemsChangedBound = this.onViewableItemsChanged.bind(this); 88 | this.renderCalendarBound = this.renderCalendar.bind(this); 89 | this.getItemLayout = this.getItemLayout.bind(this); 90 | this.onLayout = this.onLayout.bind(this); 91 | } 92 | 93 | onLayout(event) { 94 | if (this.props.onLayout) { 95 | this.props.onLayout(event); 96 | } 97 | } 98 | 99 | scrollToDay(d, offset, animated) { 100 | const day = parseDate(d); 101 | const diffMonths = Math.round(this.state.openDate.clone().setDate(1).diffMonths(day.clone().setDate(1))); 102 | const size = this.props.horizontal ? this.props.calendarWidth : this.props.calendarHeight; 103 | let scrollAmount = (size * this.props.pastScrollRange) + (diffMonths * size) + (offset || 0); 104 | if (!this.props.horizontal) { 105 | let week = 0; 106 | const days = dateutils.page(day, this.props.firstDay); 107 | for (let i = 0; i < days.length; i++) { 108 | week = Math.floor(i / 7); 109 | if (dateutils.sameDate(days[i], day)) { 110 | scrollAmount += 46 * week; 111 | break; 112 | } 113 | } 114 | } 115 | this.listView.scrollToOffset({offset: scrollAmount, animated}); 116 | } 117 | 118 | scrollToMonth(m) { 119 | const month = parseDate(m); 120 | const scrollTo = month || this.state.openDate; 121 | let diffMonths = Math.round(this.state.openDate.clone().setDate(1).diffMonths(scrollTo.clone().setDate(1))); 122 | const size = this.props.horizontal ? this.props.calendarWidth : this.props.calendarHeight; 123 | const scrollAmount = (size * this.props.pastScrollRange) + (diffMonths * size); 124 | //console.log(month, this.state.openDate); 125 | //console.log(scrollAmount, diffMonths); 126 | this.listView.scrollToOffset({offset: scrollAmount, animated: false}); 127 | } 128 | 129 | componentWillReceiveProps(props) { 130 | const current = parseDate(this.props.current); 131 | const nextCurrent = parseDate(props.current); 132 | if (nextCurrent && current && nextCurrent.getTime() !== current.getTime()) { 133 | this.scrollToMonth(nextCurrent); 134 | } 135 | 136 | const rowclone = this.state.rows; 137 | const newrows = []; 138 | for (let i = 0; i < rowclone.length; i++) { 139 | let val = this.state.texts[i]; 140 | if (rowclone[i].getTime) { 141 | val = rowclone[i].clone(); 142 | val.propbump = rowclone[i].propbump ? rowclone[i].propbump + 1 : 1; 143 | } 144 | newrows.push(val); 145 | } 146 | this.setState({ 147 | rows: newrows 148 | }); 149 | } 150 | 151 | onViewableItemsChanged({viewableItems}) { 152 | function rowIsCloseToViewable(index, distance) { 153 | for (let i = 0; i < viewableItems.length; i++) { 154 | if (Math.abs(index - parseInt(viewableItems[i].index)) <= distance) { 155 | return true; 156 | } 157 | } 158 | return false; 159 | } 160 | 161 | const rowclone = this.state.rows; 162 | const newrows = []; 163 | const visibleMonths = []; 164 | for (let i = 0; i < rowclone.length; i++) { 165 | let val = rowclone[i]; 166 | const rowShouldBeRendered = rowIsCloseToViewable(i, 1); 167 | if (rowShouldBeRendered && !rowclone[i].getTime) { 168 | val = this.state.openDate.clone().addMonths(i - this.props.pastScrollRange, true); 169 | } else if (!rowShouldBeRendered) { 170 | val = this.state.texts[i]; 171 | } 172 | newrows.push(val); 173 | if (rowIsCloseToViewable(i, 0)) { 174 | visibleMonths.push(xdateToData(val)); 175 | } 176 | } 177 | if (this.props.onVisibleMonthsChange) { 178 | this.props.onVisibleMonthsChange(visibleMonths); 179 | } 180 | this.setState({ 181 | rows: newrows 182 | }); 183 | } 184 | 185 | renderCalendar({item}) { 186 | return (); 187 | } 188 | 189 | getItemLayout(data, index) { 190 | return {length: this.props.horizontal ? this.props.calendarWidth : this.props.calendarHeight, offset: (this.props.horizontal ? this.props.calendarWidth : this.props.calendarHeight) * index, index}; 191 | } 192 | 193 | getMonthIndex(month) { 194 | let diffMonths = this.state.openDate.diffMonths(month) + this.props.pastScrollRange; 195 | return diffMonths; 196 | } 197 | 198 | render() { 199 | return ( 200 | this.listView = c} 203 | //scrollEventThrottle={1000} 204 | style={[this.style.container, this.props.style]} 205 | initialListSize={this.pastScrollRange + this.futureScrollRange + 1} 206 | data={this.state.rows} 207 | //snapToAlignment='start' 208 | //snapToInterval={this.calendarHeight} 209 | removeClippedSubviews={this.props.removeClippedSubviews} 210 | pageSize={1} 211 | horizontal={this.props.horizontal} 212 | pagingEnabled={this.props.pagingEnabled} 213 | onViewableItemsChanged={this.onViewableItemsChangedBound} 214 | renderItem={this.renderCalendarBound} 215 | showsVerticalScrollIndicator={this.props.showScrollIndicator} 216 | showsHorizontalScrollIndicator={this.props.showScrollIndicator} 217 | scrollEnabled={this.props.scrollingEnabled} 218 | keyExtractor={(item, index) => String(index)} 219 | initialScrollIndex={this.state.openDate ? this.getMonthIndex(this.state.openDate) : false} 220 | getItemLayout={this.getItemLayout} 221 | scrollsToTop={this.props.scrollsToTop} 222 | /> 223 | ); 224 | } 225 | } 226 | 227 | export default CalendarList; 228 | -------------------------------------------------------------------------------- /src/calendar-list/item.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Text, View} from 'react-native'; 3 | import Calendar from '../calendar'; 4 | import styleConstructor from './style'; 5 | 6 | class CalendarListItem extends Component { 7 | static defaultProps = { 8 | hideArrows: true, 9 | hideExtraDays: true, 10 | }; 11 | 12 | constructor(props) { 13 | super(props); 14 | this.style = styleConstructor(props.theme); 15 | } 16 | 17 | shouldComponentUpdate(nextProps) { 18 | const r1 = this.props.item; 19 | const r2 = nextProps.item; 20 | return r1.toString('yyyy MM') !== r2.toString('yyyy MM') || !!(r2.propbump && r2.propbump !== r1.propbump); 21 | } 22 | 23 | render() { 24 | const row = this.props.item; 25 | if (row.getTime) { 26 | return ( 27 | ); 48 | } else { 49 | const text = row.toString(); 50 | return ( 51 | 52 | {text} 53 | 54 | ); 55 | } 56 | } 57 | } 58 | 59 | export default CalendarListItem; 60 | -------------------------------------------------------------------------------- /src/calendar-list/style.js: -------------------------------------------------------------------------------- 1 | import {StyleSheet} from 'react-native'; 2 | import * as defaultStyle from '../style'; 3 | 4 | const STYLESHEET_ID = 'stylesheet.calendar-list.main'; 5 | 6 | export default function getStyle(theme={}) { 7 | const appStyle = {...defaultStyle, ...theme}; 8 | return StyleSheet.create({ 9 | container: { 10 | backgroundColor: appStyle.calendarBackground 11 | }, 12 | placeholder: { 13 | backgroundColor: appStyle.calendarBackground, 14 | alignItems: 'center', 15 | justifyContent: 'center' 16 | }, 17 | placeholderText: { 18 | fontSize: 30, 19 | fontWeight: '200', 20 | color: appStyle.dayTextColor 21 | }, 22 | calendar: { 23 | paddingLeft: 15, 24 | paddingRight: 15 25 | }, 26 | ...(theme[STYLESHEET_ID] || {}) 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /src/calendar/day/basic/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | TouchableOpacity, 4 | Text, 5 | View 6 | } from 'react-native'; 7 | import PropTypes from 'prop-types'; 8 | import {shouldUpdate} from '../../../component-updater'; 9 | 10 | import styleConstructor from './style'; 11 | 12 | class Day extends Component { 13 | static propTypes = { 14 | // TODO: disabled props should be removed 15 | state: PropTypes.oneOf(['disabled', 'today', '']), 16 | 17 | // Specify theme properties to override specific styles for calendar parts. Default = {} 18 | theme: PropTypes.object, 19 | marking: PropTypes.any, 20 | onPress: PropTypes.func, 21 | onLongPress: PropTypes.func, 22 | date: PropTypes.object 23 | }; 24 | 25 | constructor(props) { 26 | super(props); 27 | this.style = styleConstructor(props.theme); 28 | this.onDayPress = this.onDayPress.bind(this); 29 | this.onDayLongPress = this.onDayLongPress.bind(this); 30 | } 31 | 32 | onDayPress() { 33 | this.props.onPress(this.props.date); 34 | } 35 | onDayLongPress() { 36 | this.props.onLongPress(this.props.date); 37 | } 38 | 39 | shouldComponentUpdate(nextProps) { 40 | return shouldUpdate(this.props, nextProps, ['state', 'children', 'marking', 'onPress', 'onLongPress']); 41 | } 42 | 43 | render() { 44 | const containerStyle = [this.style.base]; 45 | const textStyle = [this.style.text]; 46 | const dotStyle = [this.style.dot]; 47 | 48 | let marking = this.props.marking || {}; 49 | if (marking && marking.constructor === Array && marking.length) { 50 | marking = { 51 | marking: true 52 | }; 53 | } 54 | const isDisabled = typeof marking.disabled !== 'undefined' ? marking.disabled : this.props.state === 'disabled'; 55 | let dot; 56 | if (marking.marked) { 57 | dotStyle.push(this.style.visibleDot); 58 | if (marking.dotColor) { 59 | dotStyle.push({backgroundColor: marking.dotColor}); 60 | } 61 | dot = (); 62 | } 63 | 64 | if (marking.selected) { 65 | containerStyle.push(this.style.selected); 66 | if (marking.selectedColor) { 67 | containerStyle.push({backgroundColor: marking.selectedColor}); 68 | } 69 | dotStyle.push(this.style.selectedDot); 70 | textStyle.push(this.style.selectedText); 71 | } else if (isDisabled) { 72 | textStyle.push(this.style.disabledText); 73 | } else if (this.props.state === 'today') { 74 | containerStyle.push(this.style.today); 75 | textStyle.push(this.style.todayText); 76 | } 77 | 78 | return ( 79 | 86 | {String(this.props.children)} 87 | {dot} 88 | 89 | ); 90 | } 91 | } 92 | 93 | export default Day; 94 | -------------------------------------------------------------------------------- /src/calendar/day/basic/style.js: -------------------------------------------------------------------------------- 1 | import {StyleSheet, Platform} from 'react-native'; 2 | import * as defaultStyle from '../../../style'; 3 | 4 | const STYLESHEET_ID = 'stylesheet.day.basic'; 5 | 6 | export default function styleConstructor(theme={}) { 7 | const appStyle = {...defaultStyle, ...theme}; 8 | return StyleSheet.create({ 9 | base: { 10 | width: 32, 11 | height: 32, 12 | alignItems: 'center' 13 | }, 14 | text: { 15 | marginTop: Platform.OS === 'android' ? 4 : 6, 16 | fontSize: appStyle.textDayFontSize, 17 | fontFamily: appStyle.textDayFontFamily, 18 | fontWeight: '300', 19 | color: appStyle.dayTextColor, 20 | backgroundColor: 'rgba(255, 255, 255, 0)' 21 | }, 22 | alignedText: { 23 | marginTop: Platform.OS === 'android' ? 4 : 6 24 | }, 25 | selected: { 26 | backgroundColor: appStyle.selectedDayBackgroundColor, 27 | borderRadius: 16 28 | }, 29 | today: { 30 | backgroundColor: appStyle.todayBackgroundColor 31 | }, 32 | todayText: { 33 | color: appStyle.todayTextColor 34 | }, 35 | selectedText: { 36 | color: appStyle.selectedDayTextColor 37 | }, 38 | disabledText: { 39 | color: appStyle.textDisabledColor 40 | }, 41 | dot: { 42 | width: 4, 43 | height: 4, 44 | marginTop: 1, 45 | borderRadius: 2, 46 | opacity: 0 47 | }, 48 | visibleDot: { 49 | opacity: 1, 50 | backgroundColor: appStyle.dotColor 51 | }, 52 | selectedDot: { 53 | backgroundColor: appStyle.selectedDotColor 54 | }, 55 | ...(theme[STYLESHEET_ID] || {}) 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /src/calendar/day/custom/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | TouchableOpacity, 4 | Text, 5 | } from 'react-native'; 6 | import PropTypes from 'prop-types'; 7 | 8 | import styleConstructor from './style'; 9 | import {shouldUpdate} from '../../../component-updater'; 10 | 11 | class Day extends Component { 12 | static propTypes = { 13 | // TODO: disabled props should be removed 14 | state: PropTypes.oneOf(['selected', 'disabled', 'today', '']), 15 | 16 | // Specify theme properties to override specific styles for calendar parts. Default = {} 17 | theme: PropTypes.object, 18 | marking: PropTypes.any, 19 | onPress: PropTypes.func, 20 | date: PropTypes.object 21 | }; 22 | 23 | constructor(props) { 24 | super(props); 25 | this.style = styleConstructor(props.theme); 26 | this.onDayPress = this.onDayPress.bind(this); 27 | this.onDayLongPress = this.onDayLongPress.bind(this); 28 | } 29 | 30 | onDayPress() { 31 | this.props.onPress(this.props.date); 32 | } 33 | onDayLongPress() { 34 | this.props.onLongPress(this.props.date); 35 | } 36 | 37 | shouldComponentUpdate(nextProps) { 38 | return shouldUpdate(this.props, nextProps, ['state', 'children', 'marking', 'onPress', 'onLongPress']); 39 | } 40 | 41 | render() { 42 | let containerStyle = [this.style.base]; 43 | let textStyle = [this.style.text]; 44 | 45 | let marking = this.props.marking || {}; 46 | if (marking && marking.constructor === Array && marking.length) { 47 | marking = { 48 | marking: true 49 | }; 50 | } 51 | const isDisabled = typeof marking.disabled !== 'undefined' ? marking.disabled : this.props.state === 'disabled'; 52 | 53 | if (marking.selected) { 54 | containerStyle.push(this.style.selected); 55 | } else if (isDisabled) { 56 | textStyle.push(this.style.disabledText); 57 | } else if (this.props.state === 'today') { 58 | containerStyle.push(this.style.today); 59 | textStyle.push(this.style.todayText); 60 | } 61 | 62 | if (marking.customStyles && typeof marking.customStyles === 'object') { 63 | const styles = marking.customStyles; 64 | if (styles.container) { 65 | if (styles.container.borderRadius === undefined) { 66 | styles.container.borderRadius = 16; 67 | } 68 | containerStyle.push(styles.container); 69 | } 70 | if (styles.text) { 71 | textStyle.push(styles.text); 72 | } 73 | } 74 | 75 | return ( 76 | 83 | {String(this.props.children)} 84 | 85 | ); 86 | } 87 | } 88 | 89 | export default Day; 90 | -------------------------------------------------------------------------------- /src/calendar/day/custom/style.js: -------------------------------------------------------------------------------- 1 | import {StyleSheet, Platform} from 'react-native'; 2 | import * as defaultStyle from '../../../style'; 3 | 4 | const STYLESHEET_ID = 'stylesheet.day.single'; 5 | 6 | export default function styleConstructor(theme={}) { 7 | const appStyle = {...defaultStyle, ...theme}; 8 | return StyleSheet.create({ 9 | base: { 10 | width: 32, 11 | height: 32, 12 | alignItems: 'center' 13 | }, 14 | text: { 15 | marginTop: Platform.OS === 'android' ? 4 : 6, 16 | fontSize: appStyle.textDayFontSize, 17 | fontFamily: appStyle.textDayFontFamily, 18 | fontWeight: '300', 19 | color: appStyle.dayTextColor, 20 | backgroundColor: 'rgba(255, 255, 255, 0)' 21 | }, 22 | alignedText: { 23 | marginTop: Platform.OS === 'android' ? 4 : 6 24 | }, 25 | selected: { 26 | backgroundColor: appStyle.selectedDayBackgroundColor, 27 | borderRadius: 16 28 | }, 29 | today: { 30 | backgroundColor: appStyle.todayBackgroundColor 31 | }, 32 | todayText: { 33 | color: appStyle.todayTextColor 34 | }, 35 | selectedText: { 36 | color: appStyle.selectedDayTextColor 37 | }, 38 | disabledText: { 39 | color: appStyle.textDisabledColor 40 | }, 41 | ...(theme[STYLESHEET_ID] || {}) 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /src/calendar/day/multi-dot/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | TouchableOpacity, 4 | Text, 5 | View 6 | } from 'react-native'; 7 | import PropTypes from 'prop-types'; 8 | 9 | import {shouldUpdate} from '../../../component-updater'; 10 | 11 | import styleConstructor from './style'; 12 | 13 | class Day extends Component { 14 | static propTypes = { 15 | // TODO: disabled props should be removed 16 | state: PropTypes.oneOf(['disabled', 'today', '']), 17 | 18 | // Specify theme properties to override specific styles for calendar parts. Default = {} 19 | theme: PropTypes.object, 20 | marking: PropTypes.any, 21 | onPress: PropTypes.func, 22 | onLongPress: PropTypes.func, 23 | date: PropTypes.object 24 | }; 25 | 26 | constructor(props) { 27 | super(props); 28 | this.style = styleConstructor(props.theme); 29 | this.onDayPress = this.onDayPress.bind(this); 30 | this.onDayLongPress = this.onDayLongPress.bind(this); 31 | } 32 | 33 | onDayPress() { 34 | this.props.onPress(this.props.date); 35 | } 36 | 37 | onDayLongPress() { 38 | this.props.onLongPress(this.props.date); 39 | } 40 | 41 | shouldComponentUpdate(nextProps) { 42 | return shouldUpdate(this.props, nextProps, ['state', 'children', 'marking', 'onPress', 'onLongPress']); 43 | } 44 | 45 | renderDots(marking) { 46 | const baseDotStyle = [this.style.dot, this.style.visibleDot]; 47 | if (marking.dots && Array.isArray(marking.dots) && marking.dots.length > 0) { 48 | // Filter out dots so that we we process only those items which have key and color property 49 | const validDots = marking.dots.filter(d => (d && d.color)); 50 | return validDots.map((dot, index) => { 51 | return ( 52 | 54 | ); 55 | }); 56 | } 57 | return; 58 | } 59 | 60 | render() { 61 | const containerStyle = [this.style.base]; 62 | const textStyle = [this.style.text]; 63 | 64 | const marking = this.props.marking || {}; 65 | const dot = this.renderDots(marking); 66 | 67 | if (marking.selected) { 68 | containerStyle.push(this.style.selected); 69 | textStyle.push(this.style.selectedText); 70 | if (marking.selectedColor) { 71 | containerStyle.push({backgroundColor: marking.selectedColor}); 72 | } 73 | } else if (typeof marking.disabled !== 'undefined' ? marking.disabled : this.props.state === 'disabled') { 74 | textStyle.push(this.style.disabledText); 75 | } else if (this.props.state === 'today') { 76 | containerStyle.push(this.style.today); 77 | textStyle.push(this.style.todayText); 78 | } 79 | return ( 80 | 84 | {String(this.props.children)} 85 | {dot} 86 | 87 | ); 88 | } 89 | } 90 | 91 | export default Day; 92 | -------------------------------------------------------------------------------- /src/calendar/day/multi-dot/style.js: -------------------------------------------------------------------------------- 1 | import {StyleSheet, Platform} from 'react-native'; 2 | import * as defaultStyle from '../../../style'; 3 | 4 | const STYLESHEET_ID = 'stylesheet.day.multiDot'; 5 | 6 | export default function styleConstructor(theme={}) { 7 | const appStyle = {...defaultStyle, ...theme}; 8 | return StyleSheet.create({ 9 | base: { 10 | width: 32, 11 | height: 32, 12 | alignItems: 'center' 13 | }, 14 | text: { 15 | marginTop: 4, 16 | fontSize: appStyle.textDayFontSize, 17 | fontFamily: appStyle.textDayFontFamily, 18 | fontWeight: '300', 19 | color: appStyle.dayTextColor, 20 | backgroundColor: 'rgba(255, 255, 255, 0)' 21 | }, 22 | alignedText: { 23 | marginTop: Platform.OS === 'android' ? 4 : 6 24 | }, 25 | selected: { 26 | backgroundColor: appStyle.selectedDayBackgroundColor, 27 | borderRadius: 16 28 | }, 29 | today: { 30 | backgroundColor: appStyle.todayBackgroundColor 31 | }, 32 | todayText: { 33 | color: appStyle.todayTextColor 34 | }, 35 | selectedText: { 36 | color: appStyle.selectedDayTextColor 37 | }, 38 | disabledText: { 39 | color: appStyle.textDisabledColor 40 | }, 41 | dot: { 42 | width: 4, 43 | height: 4, 44 | marginTop: 1, 45 | marginLeft: 1, 46 | marginRight: 1, 47 | borderRadius: 2, 48 | opacity: 0 49 | }, 50 | visibleDot: { 51 | opacity: 1, 52 | backgroundColor: appStyle.dotColor 53 | }, 54 | selectedDot: { 55 | backgroundColor: appStyle.selectedDotColor 56 | }, 57 | ...(theme[STYLESHEET_ID] || {}) 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /src/calendar/day/multi-period/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { TouchableOpacity, Text, View } from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | import {shouldUpdate} from '../../../component-updater'; 5 | 6 | import styleConstructor from './style'; 7 | 8 | class Day extends Component { 9 | static propTypes = { 10 | // TODO: disabled props should be removed 11 | state: PropTypes.oneOf(['disabled', 'today', '']), 12 | 13 | // Specify theme properties to override specific styles for calendar parts. Default = {} 14 | theme: PropTypes.object, 15 | marking: PropTypes.any, 16 | onPress: PropTypes.func, 17 | date: PropTypes.object, 18 | }; 19 | 20 | constructor(props) { 21 | super(props); 22 | this.style = styleConstructor(props.theme); 23 | this.onDayPress = this.onDayPress.bind(this); 24 | } 25 | 26 | onDayPress() { 27 | this.props.onPress(this.props.date); 28 | } 29 | 30 | shouldComponentUpdate(nextProps) { 31 | return shouldUpdate(this.props, nextProps, ['state', 'children', 'marking', 'onPress', 'onLongPress']); 32 | } 33 | 34 | renderPeriods(marking) { 35 | const baseDotStyle = [this.style.dot, this.style.visibleDot]; 36 | if ( 37 | marking.periods && 38 | Array.isArray(marking.periods) && 39 | marking.periods.length > 0 40 | ) { 41 | // Filter out dots so that we we process only those items which have key and color property 42 | const validPeriods = marking.periods.filter(d => d && d.color); 43 | return validPeriods.map((period, index) => { 44 | const style = [ 45 | ...baseDotStyle, 46 | { 47 | backgroundColor: period.color, 48 | }, 49 | ]; 50 | if (period.startingDay) { 51 | style.push({ 52 | borderTopLeftRadius: 2, 53 | borderBottomLeftRadius: 2, 54 | marginLeft: 4, 55 | }); 56 | } 57 | if (period.endingDay) { 58 | style.push({ 59 | borderTopRightRadius: 2, 60 | borderBottomRightRadius: 2, 61 | marginRight: 4, 62 | }); 63 | } 64 | return ; 65 | }); 66 | } 67 | return; 68 | } 69 | 70 | render() { 71 | const containerStyle = [this.style.base]; 72 | const textStyle = [this.style.text]; 73 | 74 | const marking = this.props.marking || {}; 75 | const periods = this.renderPeriods(marking); 76 | 77 | if (marking.selected) { 78 | containerStyle.push(this.style.selected); 79 | textStyle.push(this.style.selectedText); 80 | } else if ( 81 | typeof marking.disabled !== 'undefined' 82 | ? marking.disabled 83 | : this.props.state === 'disabled' 84 | ) { 85 | textStyle.push(this.style.disabledText); 86 | } else if (this.props.state === 'today') { 87 | containerStyle.push(this.style.today); 88 | textStyle.push(this.style.todayText); 89 | } 90 | return ( 91 | 97 | 98 | 99 | {String(this.props.children)} 100 | 101 | 102 | 106 | {periods} 107 | 108 | 109 | ); 110 | } 111 | } 112 | 113 | export default Day; 114 | -------------------------------------------------------------------------------- /src/calendar/day/multi-period/style.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Platform } from 'react-native'; 2 | import * as defaultStyle from '../../../style'; 3 | 4 | const STYLESHEET_ID = 'stylesheet.day.basic'; 5 | 6 | export default function styleConstructor(theme = {}) { 7 | const appStyle = { ...defaultStyle, ...theme }; 8 | return StyleSheet.create({ 9 | base: { 10 | width: 32, 11 | height: 32, 12 | alignItems: 'center', 13 | }, 14 | text: { 15 | marginTop: Platform.OS === 'android' ? 4 : 6, 16 | fontSize: appStyle.textDayFontSize, 17 | fontFamily: appStyle.textDayFontFamily, 18 | fontWeight: '300', 19 | color: appStyle.dayTextColor, 20 | backgroundColor: 'rgba(255, 255, 255, 0)', 21 | }, 22 | alignedText: { 23 | marginTop: Platform.OS === 'android' ? 4 : 6, 24 | }, 25 | selected: { 26 | backgroundColor: appStyle.selectedDayBackgroundColor, 27 | borderRadius: 16, 28 | }, 29 | today: { 30 | backgroundColor: appStyle.todayBackgroundColor 31 | }, 32 | todayText: { 33 | color: appStyle.todayTextColor, 34 | }, 35 | selectedText: { 36 | color: appStyle.selectedDayTextColor, 37 | }, 38 | disabledText: { 39 | color: appStyle.textDisabledColor, 40 | }, 41 | dot: { 42 | // width: 42, 43 | height: 4, 44 | marginVertical: 1, 45 | // borderRadius: 2, 46 | opacity: 0, 47 | }, 48 | leftFiller: { 49 | width: 4, 50 | height: 4, 51 | marginTop: 1, 52 | marginRight: -2, 53 | }, 54 | rightFiller: { 55 | width: 4, 56 | height: 4, 57 | marginTop: 1, 58 | marginLeft: -2, 59 | }, 60 | rounded: { 61 | borderRadius: 2, 62 | }, 63 | visibleDot: { 64 | opacity: 1, 65 | backgroundColor: appStyle.dotColor, 66 | }, 67 | selectedDot: { 68 | backgroundColor: appStyle.selectedDotColor, 69 | }, 70 | startingPeriod: { 71 | width: 18, 72 | height: 4, 73 | marginTop: 1, 74 | borderRadius: 2, 75 | opacity: 0, 76 | }, 77 | ...(theme[STYLESHEET_ID] || {}), 78 | }); 79 | } 80 | -------------------------------------------------------------------------------- /src/calendar/day/period/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { 4 | TouchableWithoutFeedback, 5 | Text, 6 | View 7 | } from 'react-native'; 8 | import {shouldUpdate} from '../../../component-updater'; 9 | import isEqual from 'lodash.isequal'; 10 | 11 | import * as defaultStyle from '../../../style'; 12 | import styleConstructor from './style'; 13 | 14 | class Day extends Component { 15 | static propTypes = { 16 | // TODO: selected + disabled props should be removed 17 | state: PropTypes.oneOf(['selected', 'disabled', 'today', '']), 18 | 19 | // Specify theme properties to override specific styles for calendar parts. Default = {} 20 | theme: PropTypes.object, 21 | marking: PropTypes.any, 22 | 23 | onPress: PropTypes.func, 24 | onLongPress: PropTypes.func, 25 | date: PropTypes.object, 26 | 27 | markingExists: PropTypes.bool, 28 | }; 29 | 30 | constructor(props) { 31 | super(props); 32 | this.theme = {...defaultStyle, ...(props.theme || {})}; 33 | this.style = styleConstructor(props.theme); 34 | this.markingStyle = this.getDrawingStyle(props.marking || []); 35 | this.onDayPress = this.onDayPress.bind(this); 36 | this.onDayLongPress = this.onDayLongPress.bind(this); 37 | } 38 | 39 | onDayPress() { 40 | this.props.onPress(this.props.date); 41 | } 42 | 43 | onDayLongPress() { 44 | this.props.onLongPress(this.props.date); 45 | } 46 | 47 | shouldComponentUpdate(nextProps) { 48 | const newMarkingStyle = this.getDrawingStyle(nextProps.marking); 49 | 50 | if (!isEqual(this.markingStyle, newMarkingStyle)) { 51 | this.markingStyle = newMarkingStyle; 52 | return true; 53 | } 54 | 55 | return shouldUpdate(this.props, nextProps, ['state', 'children', 'onPress', 'onLongPress']); 56 | } 57 | 58 | getDrawingStyle(marking) { 59 | const defaultStyle = {textStyle: {}}; 60 | if (!marking) { 61 | return defaultStyle; 62 | } 63 | if (marking.disabled) { 64 | defaultStyle.textStyle.color = this.theme.textDisabledColor; 65 | } else if (marking.selected) { 66 | defaultStyle.textStyle.color = this.theme.selectedDayTextColor; 67 | } 68 | const resultStyle = ([marking]).reduce((prev, next) => { 69 | if (next.quickAction) { 70 | if (next.first || next.last) { 71 | prev.containerStyle = this.style.firstQuickAction; 72 | prev.textStyle = this.style.firstQuickActionText; 73 | if (next.endSelected && next.first && !next.last) { 74 | prev.rightFillerStyle = '#c1e4fe'; 75 | } else if (next.endSelected && next.last && !next.first) { 76 | prev.leftFillerStyle = '#c1e4fe'; 77 | } 78 | } else if (!next.endSelected) { 79 | prev.containerStyle = this.style.quickAction; 80 | prev.textStyle = this.style.quickActionText; 81 | } else if (next.endSelected) { 82 | prev.leftFillerStyle = '#c1e4fe'; 83 | prev.rightFillerStyle = '#c1e4fe'; 84 | } 85 | return prev; 86 | } 87 | 88 | const color = next.color; 89 | if (next.status === 'NotAvailable') { 90 | prev.textStyle = this.style.naText; 91 | } 92 | if (next.startingDay) { 93 | prev.startingDay = { 94 | color 95 | }; 96 | } 97 | if (next.endingDay) { 98 | prev.endingDay = { 99 | color 100 | }; 101 | } 102 | if (!next.startingDay && !next.endingDay) { 103 | prev.day = { 104 | color 105 | }; 106 | } 107 | if (next.textColor) { 108 | prev.textStyle.color = next.textColor; 109 | } 110 | return prev; 111 | }, defaultStyle); 112 | return resultStyle; 113 | } 114 | 115 | render() { 116 | const containerStyle = [this.style.base]; 117 | const textStyle = [this.style.text]; 118 | let leftFillerStyle = {}; 119 | let rightFillerStyle = {}; 120 | let fillerStyle = {}; 121 | let fillers; 122 | 123 | if (this.props.state === 'disabled') { 124 | textStyle.push(this.style.disabledText); 125 | } else if (this.props.state === 'today') { 126 | containerStyle.push(this.style.today); 127 | textStyle.push(this.style.todayText); 128 | } 129 | 130 | if (this.props.marking) { 131 | containerStyle.push({ 132 | borderRadius: 17 133 | }); 134 | 135 | const flags = this.markingStyle; 136 | if (flags.textStyle) { 137 | textStyle.push(flags.textStyle); 138 | } 139 | if (flags.containerStyle) { 140 | containerStyle.push(flags.containerStyle); 141 | } 142 | if (flags.leftFillerStyle) { 143 | leftFillerStyle.backgroundColor = flags.leftFillerStyle; 144 | } 145 | if (flags.rightFillerStyle) { 146 | rightFillerStyle.backgroundColor = flags.rightFillerStyle; 147 | } 148 | 149 | if (flags.startingDay && !flags.endingDay) { 150 | leftFillerStyle = { 151 | backgroundColor: this.theme.calendarBackground 152 | }; 153 | rightFillerStyle = { 154 | backgroundColor: flags.startingDay.color 155 | }; 156 | containerStyle.push({ 157 | backgroundColor: flags.startingDay.color 158 | }); 159 | } else if (flags.endingDay && !flags.startingDay) { 160 | rightFillerStyle = { 161 | backgroundColor: this.theme.calendarBackground 162 | }; 163 | leftFillerStyle = { 164 | backgroundColor: flags.endingDay.color 165 | }; 166 | containerStyle.push({ 167 | backgroundColor: flags.endingDay.color 168 | }); 169 | } else if (flags.day) { 170 | leftFillerStyle = {backgroundColor: flags.day.color}; 171 | rightFillerStyle = {backgroundColor: flags.day.color}; 172 | // #177 bug 173 | fillerStyle = {backgroundColor: flags.day.color}; 174 | } else if (flags.endingDay && flags.startingDay) { 175 | rightFillerStyle = { 176 | backgroundColor: this.theme.calendarBackground 177 | }; 178 | leftFillerStyle = { 179 | backgroundColor: this.theme.calendarBackground 180 | }; 181 | containerStyle.push({ 182 | backgroundColor: flags.endingDay.color 183 | }); 184 | } 185 | 186 | fillers = ( 187 | 188 | 189 | 190 | 191 | ); 192 | } 193 | 194 | return ( 195 | 198 | 199 | {fillers} 200 | 201 | {String(this.props.children)} 202 | 203 | 204 | 205 | ); 206 | } 207 | } 208 | 209 | export default Day; 210 | -------------------------------------------------------------------------------- /src/calendar/day/period/style.js: -------------------------------------------------------------------------------- 1 | import {StyleSheet} from 'react-native'; 2 | import * as defaultStyle from '../../../style'; 3 | 4 | const STYLESHEET_ID = 'stylesheet.day.period'; 5 | 6 | const FILLER_HEIGHT = 34; 7 | 8 | export default function styleConstructor(theme={}) { 9 | const appStyle = {...defaultStyle, ...theme}; 10 | return StyleSheet.create({ 11 | wrapper: { 12 | flex: 1, 13 | alignItems: 'center', 14 | alignSelf: 'stretch', 15 | marginLeft: -1 16 | }, 17 | base: { 18 | //borderWidth: 1, 19 | width: 38, 20 | height: FILLER_HEIGHT, 21 | alignItems: 'center' 22 | }, 23 | fillers: { 24 | position: 'absolute', 25 | height: FILLER_HEIGHT, 26 | flexDirection: 'row', 27 | left: 0, 28 | right: 0 29 | }, 30 | leftFiller: { 31 | height: FILLER_HEIGHT, 32 | flex: 1 33 | }, 34 | rightFiller: { 35 | height: FILLER_HEIGHT, 36 | flex: 1 37 | }, 38 | text: { 39 | marginTop: 7, 40 | fontSize: appStyle.textDayFontSize, 41 | fontFamily: appStyle.textDayFontFamily, 42 | fontWeight: '300', 43 | color: appStyle.dayTextColor || '#2d4150', 44 | backgroundColor: 'rgba(255, 255, 255, 0)' 45 | }, 46 | today: { 47 | backgroundColor: appStyle.todayBackgroundColor 48 | }, 49 | todayText: { 50 | fontWeight: '500', 51 | color: theme.todayTextColor || appStyle.dayTextColor, 52 | //color: appStyle.textLinkColor 53 | }, 54 | disabledText: { 55 | color: appStyle.textDisabledColor 56 | }, 57 | quickAction: { 58 | backgroundColor: 'white', 59 | borderWidth: 1, 60 | borderColor: '#c1e4fe' 61 | }, 62 | quickActionText: { 63 | marginTop: 6, 64 | color: appStyle.textColor 65 | }, 66 | firstQuickAction: { 67 | backgroundColor: appStyle.textLinkColor 68 | }, 69 | firstQuickActionText: { 70 | color: 'white' 71 | }, 72 | naText: { 73 | color: '#b6c1cd' 74 | }, 75 | ...(theme[STYLESHEET_ID] || {}) 76 | }); 77 | } 78 | -------------------------------------------------------------------------------- /src/calendar/header/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { ActivityIndicator } from 'react-native'; 3 | import { View, Text, TouchableOpacity, Image } from 'react-native'; 4 | import XDate from 'xdate'; 5 | import PropTypes from 'prop-types'; 6 | import styleConstructor from './style'; 7 | import { weekDayNames } from '../../dateutils'; 8 | import { 9 | CHANGE_MONTH_LEFT_ARROW, 10 | CHANGE_MONTH_RIGHT_ARROW 11 | } from '../../testIDs'; 12 | 13 | class CalendarHeader extends Component { 14 | static propTypes = { 15 | theme: PropTypes.object, 16 | hideArrows: PropTypes.bool, 17 | month: PropTypes.instanceOf(XDate), 18 | addMonth: PropTypes.func, 19 | showIndicator: PropTypes.bool, 20 | firstDay: PropTypes.number, 21 | renderArrow: PropTypes.func, 22 | hideDayNames: PropTypes.bool, 23 | weekNumbers: PropTypes.bool, 24 | onPressArrowLeft: PropTypes.func, 25 | onPressArrowRight: PropTypes.func 26 | }; 27 | 28 | static defaultProps = { 29 | monthFormat: 'MMMM yyyy', 30 | }; 31 | 32 | constructor(props) { 33 | super(props); 34 | this.style = styleConstructor(props.theme); 35 | this.addMonth = this.addMonth.bind(this); 36 | this.substractMonth = this.substractMonth.bind(this); 37 | this.onPressLeft = this.onPressLeft.bind(this); 38 | this.onPressRight = this.onPressRight.bind(this); 39 | } 40 | 41 | addMonth() { 42 | this.props.addMonth(1); 43 | } 44 | 45 | substractMonth() { 46 | this.props.addMonth(-1); 47 | } 48 | 49 | shouldComponentUpdate(nextProps) { 50 | if ( 51 | nextProps.month.toString('yyyy MM') !== 52 | this.props.month.toString('yyyy MM') 53 | ) { 54 | return true; 55 | } 56 | if (nextProps.showIndicator !== this.props.showIndicator) { 57 | return true; 58 | } 59 | if (nextProps.hideDayNames !== this.props.hideDayNames) { 60 | return true; 61 | } 62 | return false; 63 | } 64 | 65 | onPressLeft() { 66 | const {onPressArrowLeft} = this.props; 67 | if(typeof onPressArrowLeft === 'function') { 68 | return onPressArrowLeft(this.substractMonth); 69 | } 70 | return this.substractMonth(); 71 | } 72 | 73 | onPressRight() { 74 | const {onPressArrowRight} = this.props; 75 | if(typeof onPressArrowRight === 'function') { 76 | return onPressArrowRight(this.addMonth); 77 | } 78 | return this.addMonth(); 79 | } 80 | 81 | render() { 82 | let leftArrow = ; 83 | let rightArrow = ; 84 | let weekDaysNames = weekDayNames(this.props.firstDay); 85 | if (!this.props.hideArrows) { 86 | leftArrow = ( 87 | 93 | {this.props.renderArrow 94 | ? this.props.renderArrow('left') 95 | : } 99 | 100 | ); 101 | rightArrow = ( 102 | 108 | {this.props.renderArrow 109 | ? this.props.renderArrow('right') 110 | : } 114 | 115 | ); 116 | } 117 | let indicator; 118 | if (this.props.showIndicator) { 119 | indicator = ; 120 | } 121 | return ( 122 | 123 | 124 | {leftArrow} 125 | 126 | 127 | {this.props.month.toString(this.props.monthFormat)} 128 | 129 | {indicator} 130 | 131 | {rightArrow} 132 | 133 | { 134 | !this.props.hideDayNames && 135 | 136 | {this.props.weekNumbers && } 137 | {weekDaysNames.map((day, idx) => ( 138 | {day} 139 | ))} 140 | 141 | } 142 | 143 | ); 144 | } 145 | } 146 | 147 | export default CalendarHeader; 148 | -------------------------------------------------------------------------------- /src/calendar/header/style.js: -------------------------------------------------------------------------------- 1 | import {StyleSheet, Platform} from 'react-native'; 2 | import * as defaultStyle from '../../style'; 3 | 4 | const STYLESHEET_ID = 'stylesheet.calendar.header'; 5 | 6 | export default function(theme={}) { 7 | const appStyle = {...defaultStyle, ...theme}; 8 | return StyleSheet.create({ 9 | header: { 10 | flexDirection: 'row', 11 | justifyContent: 'space-between', 12 | paddingLeft: 10, 13 | paddingRight: 10, 14 | alignItems: 'center' 15 | }, 16 | monthText: { 17 | fontSize: appStyle.textMonthFontSize, 18 | fontFamily: appStyle.textMonthFontFamily, 19 | fontWeight: appStyle.textMonthFontWeight, 20 | color: appStyle.monthTextColor, 21 | margin: 10 22 | }, 23 | arrow: { 24 | padding: 10 25 | }, 26 | arrowImage: { 27 | ...Platform.select({ 28 | ios: { 29 | tintColor: appStyle.arrowColor 30 | }, 31 | android: { 32 | tintColor: appStyle.arrowColor 33 | } 34 | }) 35 | }, 36 | week: { 37 | marginTop: 7, 38 | flexDirection: 'row', 39 | justifyContent: 'space-around' 40 | }, 41 | dayHeader: { 42 | marginTop: 2, 43 | marginBottom: 7, 44 | width: 32, 45 | textAlign: 'center', 46 | fontSize: appStyle.textDayHeaderFontSize, 47 | fontFamily: appStyle.textDayHeaderFontFamily, 48 | color: appStyle.textSectionTitleColor 49 | }, 50 | ...(theme[STYLESHEET_ID] || {}) 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /src/calendar/img/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/src/calendar/img/next.png -------------------------------------------------------------------------------- /src/calendar/img/next@1.5x.android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/src/calendar/img/next@1.5x.android.png -------------------------------------------------------------------------------- /src/calendar/img/next@2x.android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/src/calendar/img/next@2x.android.png -------------------------------------------------------------------------------- /src/calendar/img/next@2x.ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/src/calendar/img/next@2x.ios.png -------------------------------------------------------------------------------- /src/calendar/img/next@3x.android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/src/calendar/img/next@3x.android.png -------------------------------------------------------------------------------- /src/calendar/img/next@4x.android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/src/calendar/img/next@4x.android.png -------------------------------------------------------------------------------- /src/calendar/img/previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/src/calendar/img/previous.png -------------------------------------------------------------------------------- /src/calendar/img/previous@1.5x.android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/src/calendar/img/previous@1.5x.android.png -------------------------------------------------------------------------------- /src/calendar/img/previous@2x.android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/src/calendar/img/previous@2x.android.png -------------------------------------------------------------------------------- /src/calendar/img/previous@2x.ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/src/calendar/img/previous@2x.ios.png -------------------------------------------------------------------------------- /src/calendar/img/previous@3x.android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/src/calendar/img/previous@3x.android.png -------------------------------------------------------------------------------- /src/calendar/img/previous@4x.android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entria/react-native-calendars/73f8a7b497aba06d56397ec86f295fd1ea6adf9c/src/calendar/img/previous@4x.android.png -------------------------------------------------------------------------------- /src/calendar/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | View, 4 | ViewPropTypes 5 | } from 'react-native'; 6 | import PropTypes from 'prop-types'; 7 | 8 | import XDate from 'xdate'; 9 | import dateutils from '../dateutils'; 10 | import {xdateToData, parseDate} from '../interface'; 11 | import styleConstructor from './style'; 12 | import Day from './day/basic'; 13 | import UnitDay from './day/period'; 14 | import MultiDotDay from './day/multi-dot'; 15 | import MultiPeriodDay from './day/multi-period'; 16 | import SingleDay from './day/custom'; 17 | import CalendarHeader from './header'; 18 | import shouldComponentUpdate from './updater'; 19 | 20 | //Fallback when RN version is < 0.44 21 | const viewPropTypes = ViewPropTypes || View.propTypes; 22 | 23 | const EmptyArray = []; 24 | 25 | class Calendar extends Component { 26 | static propTypes = { 27 | // Specify theme properties to override specific styles for calendar parts. Default = {} 28 | theme: PropTypes.object, 29 | // Collection of dates that have to be marked. Default = {} 30 | markedDates: PropTypes.object, 31 | 32 | // Specify style for calendar container element. Default = {} 33 | style: viewPropTypes.style, 34 | // Initially visible month. Default = Date() 35 | current: PropTypes.any, 36 | // Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined 37 | minDate: PropTypes.any, 38 | // Maximum date that can be selected, dates after maxDate will be grayed out. Default = undefined 39 | maxDate: PropTypes.any, 40 | 41 | // If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday. 42 | firstDay: PropTypes.number, 43 | 44 | // Date marking style [simple/period/multi-dot/multi-period]. Default = 'simple' 45 | markingType: PropTypes.string, 46 | 47 | // Hide month navigation arrows. Default = false 48 | hideArrows: PropTypes.bool, 49 | // Display loading indicador. Default = false 50 | displayLoadingIndicator: PropTypes.bool, 51 | // Do not show days of other months in month page. Default = false 52 | hideExtraDays: PropTypes.bool, 53 | 54 | // Handler which gets executed on day press. Default = undefined 55 | onDayPress: PropTypes.func, 56 | // Handler which gets executed on day long press. Default = undefined 57 | onDayLongPress: PropTypes.func, 58 | // Handler which gets executed when visible month changes in calendar. Default = undefined 59 | onMonthChange: PropTypes.func, 60 | onVisibleMonthsChange: PropTypes.func, 61 | // Replace default arrows with custom ones (direction can be 'left' or 'right') 62 | renderArrow: PropTypes.func, 63 | // Provide custom day rendering component 64 | dayComponent: PropTypes.any, 65 | // Month format in calendar title. Formatting values: http://arshaw.com/xdate/#Formatting 66 | monthFormat: PropTypes.string, 67 | // Disables changing month when click on days of other months (when hideExtraDays is false). Default = false 68 | disableMonthChange: PropTypes.bool, 69 | // Hide day names. Default = false 70 | hideDayNames: PropTypes.bool, 71 | // Disable days by default. Default = false 72 | disabledByDefault: PropTypes.bool, 73 | // Show week numbers. Default = false 74 | showWeekNumbers: PropTypes.bool, 75 | // Handler which gets executed when press arrow icon left. It receive a callback can go back month 76 | onPressArrowLeft: PropTypes.func, 77 | // Handler which gets executed when press arrow icon left. It receive a callback can go next month 78 | onPressArrowRight: PropTypes.func 79 | }; 80 | 81 | constructor(props) { 82 | super(props); 83 | this.style = styleConstructor(this.props.theme); 84 | let currentMonth; 85 | if (props.current) { 86 | currentMonth = parseDate(props.current); 87 | } else { 88 | currentMonth = XDate(); 89 | } 90 | this.state = { 91 | currentMonth 92 | }; 93 | 94 | this.updateMonth = this.updateMonth.bind(this); 95 | this.addMonth = this.addMonth.bind(this); 96 | this.pressDay = this.pressDay.bind(this); 97 | this.longPressDay = this.longPressDay.bind(this); 98 | this.shouldComponentUpdate = shouldComponentUpdate; 99 | } 100 | 101 | componentWillReceiveProps(nextProps) { 102 | const current= parseDate(nextProps.current); 103 | if (current && current.toString('yyyy MM') !== this.state.currentMonth.toString('yyyy MM')) { 104 | this.setState({ 105 | currentMonth: current.clone() 106 | }); 107 | } 108 | } 109 | 110 | updateMonth(day, doNotTriggerListeners) { 111 | if (day.toString('yyyy MM') === this.state.currentMonth.toString('yyyy MM')) { 112 | return; 113 | } 114 | this.setState({ 115 | currentMonth: day.clone() 116 | }, () => { 117 | if (!doNotTriggerListeners) { 118 | const currMont = this.state.currentMonth.clone(); 119 | if (this.props.onMonthChange) { 120 | this.props.onMonthChange(xdateToData(currMont)); 121 | } 122 | if (this.props.onVisibleMonthsChange) { 123 | this.props.onVisibleMonthsChange([xdateToData(currMont)]); 124 | } 125 | } 126 | }); 127 | } 128 | 129 | _handleDayInteraction(date, interaction) { 130 | const day = parseDate(date); 131 | const minDate = parseDate(this.props.minDate); 132 | const maxDate = parseDate(this.props.maxDate); 133 | if (!(minDate && !dateutils.isGTE(day, minDate)) && !(maxDate && !dateutils.isLTE(day, maxDate))) { 134 | const shouldUpdateMonth = this.props.disableMonthChange === undefined || !this.props.disableMonthChange; 135 | if (shouldUpdateMonth) { 136 | this.updateMonth(day); 137 | } 138 | if (interaction) { 139 | interaction(xdateToData(day)); 140 | } 141 | } 142 | } 143 | 144 | pressDay(date) { 145 | this._handleDayInteraction(date, this.props.onDayPress); 146 | } 147 | 148 | longPressDay(date) { 149 | this._handleDayInteraction(date, this.props.onDayLongPress); 150 | } 151 | 152 | addMonth(count) { 153 | this.updateMonth(this.state.currentMonth.clone().addMonths(count, true)); 154 | } 155 | 156 | renderDay(day, id) { 157 | const minDate = parseDate(this.props.minDate); 158 | const maxDate = parseDate(this.props.maxDate); 159 | let state = ''; 160 | if (this.props.disabledByDefault) { 161 | state = 'disabled'; 162 | } else if ((minDate && !dateutils.isGTE(day, minDate)) || (maxDate && !dateutils.isLTE(day, maxDate))) { 163 | state = 'disabled'; 164 | } else if (!dateutils.sameMonth(day, this.state.currentMonth)) { 165 | state = 'disabled'; 166 | } else if (dateutils.sameDate(day, XDate())) { 167 | state = 'today'; 168 | } 169 | let dayComp; 170 | if (!dateutils.sameMonth(day, this.state.currentMonth) && this.props.hideExtraDays) { 171 | if (['period', 'multi-period'].includes(this.props.markingType)) { 172 | dayComp = (); 173 | } else { 174 | dayComp = (); 175 | } 176 | } else { 177 | const DayComp = this.getDayComponent(); 178 | const date = day.getDate(); 179 | dayComp = ( 180 | 189 | {date} 190 | 191 | ); 192 | } 193 | return dayComp; 194 | } 195 | 196 | getDayComponent() { 197 | if (this.props.dayComponent) { 198 | return this.props.dayComponent; 199 | } 200 | 201 | switch (this.props.markingType) { 202 | case 'period': 203 | return UnitDay; 204 | case 'multi-dot': 205 | return MultiDotDay; 206 | case 'multi-period': 207 | return MultiPeriodDay; 208 | case 'custom': 209 | return SingleDay; 210 | default: 211 | return Day; 212 | } 213 | } 214 | 215 | getDateMarking(day) { 216 | if (!this.props.markedDates) { 217 | return false; 218 | } 219 | const dates = this.props.markedDates[day.toString('yyyy-MM-dd')] || EmptyArray; 220 | if (dates.length || dates) { 221 | return dates; 222 | } else { 223 | return false; 224 | } 225 | } 226 | 227 | renderWeekNumber (weekNumber) { 228 | return {weekNumber}; 229 | } 230 | 231 | renderWeek(days, id) { 232 | const week = []; 233 | days.forEach((day, id2) => { 234 | week.push(this.renderDay(day, id2)); 235 | }, this); 236 | 237 | if (this.props.showWeekNumbers) { 238 | week.unshift(this.renderWeekNumber(days[days.length - 1].getWeek())); 239 | } 240 | 241 | return ({week}); 242 | } 243 | 244 | render() { 245 | const days = dateutils.page(this.state.currentMonth, this.props.firstDay); 246 | const weeks = []; 247 | while (days.length) { 248 | weeks.push(this.renderWeek(days.splice(0, 7), weeks.length)); 249 | } 250 | let indicator; 251 | const current = parseDate(this.props.current); 252 | if (current) { 253 | const lastMonthOfDay = current.clone().addMonths(1, true).setDate(1).addDays(-1).toString('yyyy-MM-dd'); 254 | if (this.props.displayLoadingIndicator && 255 | !(this.props.markedDates && this.props.markedDates[lastMonthOfDay])) { 256 | indicator = true; 257 | } 258 | } 259 | return ( 260 | 261 | 275 | {weeks} 276 | ); 277 | } 278 | } 279 | 280 | export default Calendar; 281 | -------------------------------------------------------------------------------- /src/calendar/style.js: -------------------------------------------------------------------------------- 1 | import {StyleSheet} from 'react-native'; 2 | import * as defaultStyle from '../style'; 3 | 4 | const STYLESHEET_ID = 'stylesheet.calendar.main'; 5 | 6 | export default function getStyle(theme={}) { 7 | const appStyle = {...defaultStyle, ...theme}; 8 | return StyleSheet.create({ 9 | container: { 10 | paddingLeft: 5, 11 | paddingRight: 5, 12 | backgroundColor: appStyle.calendarBackground 13 | }, 14 | monthView: { 15 | backgroundColor: appStyle.calendarBackground 16 | }, 17 | week: { 18 | marginTop: 7, 19 | marginBottom: 7, 20 | flexDirection: 'row', 21 | justifyContent: 'space-around' 22 | }, 23 | dayContainer: { 24 | width: 32 25 | }, 26 | ...(theme[STYLESHEET_ID] || {}) 27 | }); 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/calendar/updater.js: -------------------------------------------------------------------------------- 1 | import {parseDate} from '../interface'; 2 | 3 | export default function shouldComponentUpdate(nextProps, nextState) { 4 | let shouldUpdate = (nextProps.selected || []).reduce((prev, next, i) => { 5 | const currentSelected = (this.props.selected || [])[i]; 6 | if (!currentSelected || !next || parseDate(currentSelected).getTime() !== parseDate(next).getTime()) { 7 | return { 8 | update: true, 9 | field: 'selected' 10 | }; 11 | } 12 | return prev; 13 | }, {update: false}); 14 | 15 | shouldUpdate = ['markedDates', 'hideExtraDays'].reduce((prev, next) => { 16 | if (!prev.update && nextProps[next] !== this.props[next]) { 17 | return { 18 | update: true, 19 | field: next 20 | }; 21 | } 22 | return prev; 23 | }, shouldUpdate); 24 | 25 | shouldUpdate = ['minDate', 'maxDate', 'current'].reduce((prev, next) => { 26 | const prevDate = parseDate(this.props[next]); 27 | const nextDate = parseDate(nextProps[next]); 28 | if (prev.update) { 29 | return prev; 30 | } else if (prevDate !== nextDate) { 31 | if (prevDate && nextDate && prevDate.getTime() === nextDate.getTime()) { 32 | return prev; 33 | } else { 34 | return { 35 | update: true, 36 | field: next 37 | }; 38 | } 39 | } 40 | return prev; 41 | }, shouldUpdate); 42 | 43 | if (nextState.currentMonth !== this.state.currentMonth) { 44 | shouldUpdate = { 45 | update: true, 46 | field: 'current' 47 | }; 48 | } 49 | //console.log(shouldUpdate.field, shouldUpdate.update); 50 | return shouldUpdate.update; 51 | } 52 | -------------------------------------------------------------------------------- /src/component-updater.js: -------------------------------------------------------------------------------- 1 | const get = require('lodash.get'); 2 | const isEqual = require('lodash.isequal'); 3 | 4 | function shouldUpdate(a, b, paths) { 5 | for (let i = 0; i < paths.length; i++) { 6 | const equals = isEqual(get(a, paths[i]), get(b, paths[i])); 7 | if (!equals) { 8 | return true; 9 | } 10 | } 11 | return false; 12 | } 13 | 14 | module.exports = { 15 | shouldUpdate 16 | }; 17 | -------------------------------------------------------------------------------- /src/component-updater.spec.js: -------------------------------------------------------------------------------- 1 | const {shouldUpdate} = require('./component-updater'); 2 | 3 | describe('component updater', () => { 4 | it('should return true if two different objects are provided with same path', async () => { 5 | const a = {details: 'whoa'}; 6 | const b = {details: 'whoax'}; 7 | const paths = ['details']; 8 | 9 | expect(shouldUpdate(a, b, paths)).toEqual(true); 10 | }); 11 | 12 | it('should return false if two same objects are provided with same path', async () => { 13 | const a = {details: 'whoa'}; 14 | const b = {details: 'whoa'}; 15 | const paths = ['details']; 16 | 17 | expect(shouldUpdate(a, b, paths)).toEqual(false); 18 | }); 19 | 20 | it('should return true if two different deep objects are provided with same path', async () => { 21 | const a = {details: {x: {y: 1}}}; 22 | const b = {details: {x: {y: 2}}}; 23 | const paths = ['details']; 24 | 25 | expect(shouldUpdate(a, b, paths)).toEqual(true); 26 | }); 27 | 28 | it('should return false if two same deep objects are provided with same path', async () => { 29 | const a = {details: {x: {y: 1}}}; 30 | const b = {details: {x: {y: 1}}}; 31 | const paths = ['details']; 32 | 33 | expect(shouldUpdate(a, b, paths)).toEqual(false); 34 | }); 35 | 36 | it('should return false if several same deep objects are provided with same path', async () => { 37 | const a = {details: {x: {y: 1}}, details2: {x: {y: 2}}, porage: 'yes'}; 38 | const b = {details: {x: {y: 1}}, details2: {x: {y: 2}}, porage: 'yes'}; 39 | const paths = ['details', 'details2', 'porage']; 40 | 41 | expect(shouldUpdate(a, b, paths)).toEqual(false); 42 | }); 43 | 44 | it('should return true if several different deep objects are provided with same path', async () => { 45 | const a = {details: {x: {y: 1}}, details2: {x: {y: 2}}, porage: 'yes'}; 46 | const b = {details: {x: {y: 1}}, details2: {x: {y: 2}}, porage: 'no'}; 47 | const paths = ['details', 'details2', 'porage']; 48 | 49 | expect(shouldUpdate(a, b, paths)).toEqual(true); 50 | }); 51 | 52 | it('should return true if two different deep props of objects are provided with same path', async () => { 53 | const a = {details: {x: {y: 1}}, details2: {x: {y: 2}}, porage: 'yes'}; 54 | const b = {details: {x: {y: 2}}, details2: {x: {y: 2}}, porage: 'yes'}; 55 | const paths = ['details.x.y', 'details2', 'porage']; 56 | 57 | expect(shouldUpdate(a, b, paths)).toEqual(true); 58 | }); 59 | 60 | it('should return false if two same deep props of objects are provided with same path', async () => { 61 | const a = {details: {x: {y: 1}, y: '1'}, details2: {x: {y: 2}}, porage: 'yes'}; 62 | const b = {details: {x: {y: 1}}, details2: {x: {y: 2}}, porage: 'yes'}; 63 | const paths = ['details.x.y', 'details2', 'porage']; 64 | 65 | expect(shouldUpdate(a, b, paths)).toEqual(false); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /src/dateutils.js: -------------------------------------------------------------------------------- 1 | const XDate = require('xdate'); 2 | 3 | function sameMonth(a, b) { 4 | return a instanceof XDate && b instanceof XDate && 5 | a.getFullYear() === b.getFullYear() && 6 | a.getMonth() === b.getMonth(); 7 | } 8 | 9 | function sameDate(a, b) { 10 | return a instanceof XDate && b instanceof XDate && 11 | a.getFullYear() === b.getFullYear() && 12 | a.getMonth() === b.getMonth() && 13 | a.getDate() === b.getDate(); 14 | } 15 | 16 | function isGTE(a, b) { 17 | return b.diffDays(a) > -1; 18 | } 19 | 20 | function isLTE(a, b) { 21 | return a.diffDays(b) > -1; 22 | } 23 | 24 | function fromTo(a, b) { 25 | const days = []; 26 | let from = +a, to = +b; 27 | for (; from <= to; from = new XDate(from, true).addDays(1).getTime()) { 28 | days.push(new XDate(from, true)); 29 | } 30 | return days; 31 | } 32 | 33 | function month(xd) { 34 | const year = xd.getFullYear(), month = xd.getMonth(); 35 | const days = new Date(year, month + 1, 0).getDate(); 36 | 37 | const firstDay = new XDate(year, month, 1, 0, 0, 0, true); 38 | const lastDay = new XDate(year, month, days, 0, 0, 0, true); 39 | 40 | return fromTo(firstDay, lastDay); 41 | } 42 | 43 | function weekDayNames(firstDayOfWeek = 0) { 44 | let weekDaysNames = XDate.locales[XDate.defaultLocale].dayNamesShort; 45 | const dayShift = firstDayOfWeek % 7; 46 | if (dayShift) { 47 | weekDaysNames = weekDaysNames.slice(dayShift).concat(weekDaysNames.slice(0, dayShift)); 48 | } 49 | return weekDaysNames; 50 | } 51 | 52 | function page(xd, firstDayOfWeek) { 53 | const days = month(xd); 54 | let before = [], after = []; 55 | 56 | const fdow = ((7 + firstDayOfWeek) % 7) || 7; 57 | const ldow = (fdow + 6) % 7; 58 | 59 | firstDayOfWeek = firstDayOfWeek || 0; 60 | 61 | const from = days[0].clone(); 62 | if (from.getDay() !== fdow) { 63 | from.addDays(-(from.getDay() + 7 - fdow) % 7); 64 | } 65 | 66 | const to = days[days.length - 1].clone(); 67 | const day = to.getDay(); 68 | if (day !== ldow) { 69 | to.addDays((ldow + 7 - day) % 7); 70 | } 71 | 72 | if (isLTE(from, days[0])) { 73 | before = fromTo(from, days[0]); 74 | } 75 | 76 | if (isGTE(to, days[days.length - 1])) { 77 | after = fromTo(days[days.length - 1], to); 78 | } 79 | 80 | return before.concat(days.slice(1, days.length - 1), after); 81 | } 82 | 83 | module.exports = { 84 | weekDayNames, 85 | sameMonth, 86 | sameDate, 87 | month, 88 | page, 89 | fromTo, 90 | isLTE, 91 | isGTE 92 | }; 93 | -------------------------------------------------------------------------------- /src/dateutils.spec.js: -------------------------------------------------------------------------------- 1 | const XDate = require('xdate'); 2 | const dateutils = require('./dateutils'); 3 | 4 | describe('dateutils', function () { 5 | describe('sameMonth()', function () { 6 | it('2014-01-01 === 2014-01-10', function () { 7 | const a = XDate(2014, 0, 1, true); 8 | const b = XDate(2014, 0, 10, true); 9 | expect(dateutils.sameMonth(a, b)).toEqual(true); 10 | }); 11 | it('for non-XDate instances is false', function () { 12 | expect(dateutils.sameMonth('a', 'b')).toEqual(false); 13 | expect(dateutils.sameMonth(123, 345)).toEqual(false); 14 | expect(dateutils.sameMonth(null, false)).toEqual(false); 15 | 16 | const a = XDate(2014, 0, 1, true); 17 | const b = XDate(2014, 0, 10, true); 18 | expect(dateutils.sameMonth(a, undefined)).toEqual(false); 19 | expect(dateutils.sameMonth(null, b)).toEqual(false); 20 | }); 21 | }); 22 | 23 | describe('isLTE()', function () { 24 | it('2014-01-20 >= 2013-12-31', function () { 25 | const a = XDate(2013, 12, 31); 26 | const b = XDate(2014, 1, 20); 27 | expect(dateutils.isLTE(a, b)).toBe(true); 28 | }); 29 | 30 | it('2014-10-20 >= 2014-10-19', function () { 31 | const a = XDate(2014, 10, 19); 32 | const b = XDate(2014, 10, 20); 33 | expect(dateutils.isLTE(a, b)).toBe(true); 34 | }); 35 | 36 | it('2014-10-20 >= 2014-09-30', function () { 37 | const a = XDate(2014, 9, 30); 38 | const b = XDate(2014, 10, 20); 39 | expect(dateutils.isLTE(a, b)).toBe(true); 40 | }); 41 | 42 | it('works for dates that differ by less than a day', function () { 43 | const a = XDate(2014, 9, 30, 0, 1, 0); 44 | const b = XDate(2014, 9, 30, 1, 0, 1); 45 | expect(dateutils.isLTE(a, b)).toBe(true); 46 | expect(dateutils.isLTE(b, a)).toBe(true); 47 | }); 48 | }); 49 | 50 | describe('isGTE()', function () { 51 | it('2014-01-20 >= 2013-12-31', function () { 52 | const a = XDate(2013, 12, 31); 53 | const b = XDate(2014, 1, 20); 54 | expect(dateutils.isGTE(b, a)).toBe(true); 55 | }); 56 | 57 | it('2014-10-20 >= 2014-10-19', function () { 58 | const a = XDate(2014, 10, 19); 59 | const b = XDate(2014, 10, 20); 60 | expect(dateutils.isGTE(b, a)).toBe(true); 61 | }); 62 | 63 | it('2014-10-20 >= 2014-09-30', function () { 64 | const a = XDate(2014, 9, 30); 65 | const b = XDate(2014, 10, 20); 66 | expect(dateutils.isGTE(b, a)).toBe(true); 67 | }); 68 | 69 | it('works for dates that differ by less than a day', function () { 70 | const a = XDate(2014, 9, 30, 0, 1, 0); 71 | const b = XDate(2014, 9, 30, 1, 0, 1); 72 | expect(dateutils.isGTE(a, b)).toBe(true); 73 | expect(dateutils.isGTE(b, a)).toBe(true); 74 | }); 75 | }); 76 | 77 | describe('month()', function () { 78 | it('2014 May', function () { 79 | const days = dateutils.month(XDate(2014, 4, 1)); 80 | expect(days.length).toBe(31); 81 | }); 82 | 83 | it('2014 August', function () { 84 | const days = dateutils.month(XDate(2014, 7, 1)); 85 | expect(days.length).toBe(31); 86 | }); 87 | }); 88 | 89 | describe('page()', function () { 90 | it('2014 March', function () { 91 | const days = dateutils.page(XDate(2014, 2, 23, true)); 92 | expect(days.length).toBe(42); 93 | expect(days[0].toString()) 94 | .toBe(XDate(2014, 1, 23, 0, 0, 0, true).toString()); 95 | expect(days[days.length - 1].toString()) 96 | .toBe(XDate(2014, 3, 5, 0, 0, 0, true).toString()); 97 | }); 98 | 99 | it('2014 May', function () { 100 | const days = dateutils.page(XDate(2014, 4, 23)); 101 | expect(days.length).toBe(35); 102 | }); 103 | 104 | it('2014 June', function () { 105 | const days = dateutils.page(XDate(2014, 5, 23)); 106 | expect(days.length).toBe(35); 107 | }); 108 | 109 | it('2014 August', function () { 110 | const days = dateutils.page(XDate(2014, 7, 23)); 111 | expect(days.length).toBe(42); 112 | }); 113 | 114 | it('2014 October', function () { 115 | const days = dateutils.page(XDate(2014, 9, 21)); 116 | expect(days.length).toBe(35); 117 | }); 118 | 119 | it('has all days in ascending order', function () { 120 | let days, i, len; 121 | 122 | days = dateutils.page(XDate(2014, 2, 1)); 123 | for (i = 0, len = days.length - 1; i < len; i++) { 124 | expect(days[i].diffDays(days[i + 1])).toBe(1); 125 | } 126 | days = dateutils.page(XDate(2014, 9, 1)); 127 | for (i = 0, len = days.length - 1; i < len; i++) { 128 | expect(days[i].diffDays(days[i + 1])).toBe(1); 129 | } 130 | }); 131 | }); 132 | 133 | }); 134 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default as Calendar } from './calendar'; 2 | export { default as CalendarList } from './calendar-list'; 3 | export { default as Agenda } from './agenda'; 4 | export { default as LocaleConfig } from 'xdate'; 5 | -------------------------------------------------------------------------------- /src/input.js: -------------------------------------------------------------------------------- 1 | export class VelocityTracker { 2 | constructor() { 3 | this.history = []; 4 | this.lastPosition = undefined; 5 | this.lastTimestamp = undefined; 6 | } 7 | 8 | add(position) { 9 | const timestamp = new Date().valueOf(); 10 | if (this.lastPosition && timestamp > this.lastTimestamp) { 11 | const diff = position - this.lastPosition; 12 | if (diff > 0.001 || diff < -0.001) { 13 | this.history.push(diff / (timestamp - this.lastTimestamp)); 14 | } 15 | } 16 | this.lastPosition = position; 17 | this.lastTimestamp = timestamp; 18 | } 19 | 20 | estimateSpeed() { 21 | const finalTrend = this.history.slice(-3); 22 | const sum = finalTrend.reduce((r, v) => r + v, 0); 23 | return sum / finalTrend.length; 24 | } 25 | 26 | reset() { 27 | this.history = []; 28 | this.lastPosition = undefined; 29 | this.lastTimestamp = undefined; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/interface.js: -------------------------------------------------------------------------------- 1 | const XDate = require('xdate'); 2 | 3 | function padNumber(n) { 4 | if (n < 10) { 5 | return '0' + n; 6 | } 7 | return n; 8 | } 9 | 10 | function xdateToData(xdate) { 11 | const dateString = xdate.toString('yyyy-MM-dd'); 12 | return { 13 | year: xdate.getFullYear(), 14 | month: xdate.getMonth() + 1, 15 | day: xdate.getDate(), 16 | timestamp: XDate(dateString, true).getTime(), 17 | dateString: dateString 18 | }; 19 | } 20 | 21 | function parseDate(d) { 22 | if (!d) { 23 | return; 24 | } else if (d.timestamp) { // conventional data timestamp 25 | return XDate(d.timestamp, true); 26 | } else if (d instanceof XDate) { // xdate 27 | return XDate(d.toString('yyyy-MM-dd'), true); 28 | } else if (d.getTime) { // javascript date 29 | const dateString = d.getFullYear() + '-' + padNumber((d.getMonth() + 1)) + '-' + padNumber(d.getDate()); 30 | return XDate(dateString, true); 31 | } else if (d.year) { 32 | const dateString = d.year + '-' + padNumber(d.month) + '-' + padNumber(d.day); 33 | return XDate(dateString, true); 34 | } else if (d) { // timestamp nuber or date formatted as string 35 | return XDate(d, true); 36 | } 37 | } 38 | 39 | module.exports = { 40 | xdateToData, 41 | parseDate 42 | }; 43 | 44 | -------------------------------------------------------------------------------- /src/interface.spec.js: -------------------------------------------------------------------------------- 1 | const iface = require('./interface'); 2 | const XDate = require('xdate'); 3 | 4 | describe('calendar interface', () => { 5 | describe('input', () => { 6 | it('should return undefined if date is undefined', () => { 7 | const date = iface.parseDate(); 8 | expect(date).toBe(undefined); 9 | }); 10 | 11 | it('should accept UTC timestamp as argument', () => { 12 | const date = iface.parseDate(1479832134398); 13 | expect(date.getTime()).toEqual(1479832134398); 14 | expect(date.getTimezoneOffset()).toEqual(0); 15 | }); 16 | 17 | it('should accept datestring as argument', () => { 18 | const date = iface.parseDate('2012-03-16'); 19 | expect(date.toString('yyyy-MM-dd')).toEqual('2012-03-16'); 20 | expect(date.getTimezoneOffset()).toEqual(0); 21 | }); 22 | 23 | it('should expect object with UTC timestamp as argument', () => { 24 | const date = iface.parseDate({timestamp: 1479832134398}); 25 | expect(date.getTime()).toEqual(1479832134398); 26 | expect(date.getTimezoneOffset()).toEqual(0); 27 | }); 28 | 29 | it('should accept XDate as argument', () => { 30 | const testDate = XDate('2016-11-22 00:00:00+3'); 31 | expect(testDate.toISOString()).toEqual('2016-11-21T21:00:00Z'); 32 | const time = 1479772800000; 33 | expect(XDate(time, true).toISOString()).toEqual('2016-11-22T00:00:00Z'); 34 | }); 35 | 36 | it('should accept Date as argument', () => { 37 | const testDate = new Date(2015, 5, 5, 12, 0); 38 | const date = iface.parseDate(testDate); 39 | expect(date.toString('yyyy-MM-dd')).toEqual('2015-06-05'); 40 | }); 41 | 42 | it('should accept data as argument', () => { 43 | const testDate = { 44 | year: 2015, 45 | month: 5, 46 | day: 6 47 | }; 48 | const date = iface.parseDate(testDate); 49 | expect(date.toString('yyyy-MM-dd')).toEqual('2015-05-06'); 50 | }); 51 | }); 52 | 53 | describe('output', () => { 54 | it('should convert xdate to data', () => { 55 | const time = 1479772800000; 56 | const testDate = XDate(time, true); 57 | expect((testDate).toISOString()).toEqual('2016-11-22T00:00:00Z'); 58 | const data = iface.xdateToData(testDate); 59 | expect(data).toEqual({ 60 | year: 2016, 61 | month: 11, 62 | day: 22, 63 | timestamp: 1479772800000, 64 | dateString: '2016-11-22' 65 | }); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /src/style.js: -------------------------------------------------------------------------------- 1 | import {Platform} from 'react-native'; 2 | 3 | export const foregroundColor = '#ffffff'; 4 | export const backgroundColor = '#f4f4f4'; 5 | export const separatorColor = '#e8e9ec'; 6 | 7 | export const processedColor = '#a7e0a3'; 8 | export const processingColor = '#ffce5c'; 9 | export const failedColor = 'rgba(246, 126, 126,1)'; 10 | 11 | export const textDefaultColor = '#2d4150'; 12 | export const textColor = '#43515c'; 13 | export const textLinkColor = '#00adf5'; 14 | export const textSecondaryColor = '#7a92a5'; 15 | 16 | export const textDayFontFamily = 'System'; 17 | export const textMonthFontFamily = 'System'; 18 | export const textDayHeaderFontFamily = 'System'; 19 | 20 | export const textMonthFontWeight = '300'; 21 | 22 | export const textDayFontSize = 16; 23 | export const textMonthFontSize = 16; 24 | export const textDayHeaderFontSize = 13; 25 | 26 | export const calendarBackground = foregroundColor; 27 | export const textSectionTitleColor = '#b6c1cd'; 28 | export const selectedDayBackgroundColor = textLinkColor; 29 | export const selectedDayTextColor = foregroundColor; 30 | export const todayBackgroundColor = undefined; 31 | export const todayTextColor = textLinkColor; 32 | export const dayTextColor = textDefaultColor; 33 | export const textDisabledColor = '#d9e1e8'; 34 | export const dotColor = textLinkColor; 35 | export const selectedDotColor = foregroundColor; 36 | export const arrowColor = textLinkColor; 37 | export const monthTextColor = textDefaultColor; 38 | export const agendaDayTextColor = '#7a92a5'; 39 | export const agendaDayNumColor = '#7a92a5'; 40 | export const agendaTodayColor = textLinkColor; 41 | export const agendaKnobColor = Platform.OS === 'ios' ? '#f2F4f5' : '#4ac4f7'; 42 | -------------------------------------------------------------------------------- /src/testIDs.js: -------------------------------------------------------------------------------- 1 | const PREFIX = 'native.calendar'; 2 | 3 | module.exports = { 4 | CHANGE_MONTH_LEFT_ARROW: `${PREFIX}.CHANGE_MONTH_LEFT_ARROW`, 5 | CHANGE_MONTH_RIGHT_ARROW: `${PREFIX}.CHANGE_MONTH_RIGHT_ARROW` 6 | }; --------------------------------------------------------------------------------