diff --git a/ios/WidgetsShowcase.xcodeproj/project.pbxproj b/ios/WidgetsShowcase.xcodeproj/project.pbxproj index 65f3c6a..d884bfb 100644 --- a/ios/WidgetsShowcase.xcodeproj/project.pbxproj +++ b/ios/WidgetsShowcase.xcodeproj/project.pbxproj @@ -1325,6 +1325,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = WidgetsShowcase; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -1348,6 +1349,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = WidgetsShowcase; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; diff --git a/packages/Calendar/src/Calendar/CalendarView.js b/packages/Calendar/src/Calendar/CalendarView.js new file mode 100644 index 0000000..f140ad0 --- /dev/null +++ b/packages/Calendar/src/Calendar/CalendarView.js @@ -0,0 +1,107 @@ +// @flow +import React, { useContext, useCallback } from 'react'; +import { View, StyleSheet } from 'react-native'; +import Month from './Month'; + +import CalendarContext from '../common/CalendarContext'; +import { primaryColor } from '../theme'; +import Header from '../common/Header'; + + +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: 5, + }, + clearTextContainer: { + marginLeft: 15, + }, + clearText: { + fontSize: 14, + color: primaryColor, + }, + row: { + flexDirection: 'row', + }, +}); + +type Props = { + renderDate: (date: Date) => React.Node, + setValue: number => void, + value: ?Date, + setView: React.Node => void, + type: 'multi' | 'range' | 'single', +} + +function dateRange(date1, date2) { + const diffTime = date2.getTime() - date1.getTime(); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + const startDate = diffDays < 0 ? date2 : date1; + const dates = Array(Math.abs(diffDays)).fill(new Date(startDate)).map(m => new Date(m.setDate(m.getDate() + 1))); + return [startDate, ...dates]; +} + +function CalendarView({ + renderDate, setValue, value, setView, type, +}: Props) { + const [months, setMonths] = useContext(CalendarContext); + + const shift = useCallback((val) => { + setMonths(mnths => mnths.map(d => new Date( + d.getFullYear(), + d.getMonth() + val, + 1 + ))); + }, []); + + const selectDate = useCallback((d, long) => { + setValue((prev) => { + if (type === 'multi') { + if (Array.isArray(prev)) { + // logic to remove the date if already Selected + const isAlreadySelected = prev.filter(dates => dates.toDateString() === d.toDateString()); + return isAlreadySelected.length > 0 + ? [...prev.filter(dates => dates.toDateString() !== d.toDateString())] + : [...prev, d]; + } + if (long) { + return prev ? [prev, d] : [d]; + } + } else if (type === 'range') { + if (Array.isArray(prev)) { + return d; + } + if (prev) { + return dateRange(prev, d); + } + return d; + } + return d; + }); + }, []); + + function clearSelect() { + setValue(null); + } + + clearSelect.title = 'Clear'; + return ( + <> + {months.map(month => ( + + + + ))} +
+ + ); +} + + +export default CalendarView; diff --git a/packages/Calendar/src/Day.js b/packages/Calendar/src/Calendar/Day.js similarity index 70% rename from packages/Calendar/src/Day.js rename to packages/Calendar/src/Calendar/Day.js index f9ee445..13cd230 100644 --- a/packages/Calendar/src/Day.js +++ b/packages/Calendar/src/Calendar/Day.js @@ -1,14 +1,8 @@ // @flow import React from 'react'; import { View, TouchableOpacity, Text, StyleSheet } from 'react-native'; -import { getTheme } from '@sharingapples/theme'; +import { textColor, backgroundColor, primaryFontColor, primaryColor, disabledFontColor } from '../theme'; -const theme = getTheme(); -const calendarTheme = theme.onCalendar || theme; -const textColor = calendarTheme.onBackground; -const backgroundColor = calendarTheme.background; -const primaryFontColor = theme.onPrimary; -const disabledFontColor = theme.disabled; const styles = StyleSheet.create({ container: { @@ -16,11 +10,13 @@ const styles = StyleSheet.create({ alignItems: 'center', paddingHorizontal: 3, paddingVertical: 3, - borderLeftWidth: 1, - borderRightWidth: 1, - borderTopWidth: 1, - borderBottomWidth: 1, - margin: -1, + borderWidth: StyleSheet.hairlineWidth, + margin: -StyleSheet.hairlineWidth, + // because of inconsistency in android for border each has to be defined + borderLeftColor: 'transparent', + borderRightColor: 'transparent', + borderTopColor: 'transparent', + borderBottomColor: 'transparent', }, textContainer: { paddingHorizontal: 6, @@ -31,6 +27,9 @@ const styles = StyleSheet.create({ width: 30, height: 30, }, + text: { + fontSize: 14, + }, }); type Props = { @@ -55,10 +54,9 @@ function Day({ const dateObj = new Date(date); const isCurrentMonth = currentMonth === dateObj.getMonth(); const isToday = dateObj.toDateString() === new Date().toDateString(); - return ( { // stack this to selected date selectDate(dateObj, true); @@ -70,15 +68,14 @@ function Day({ > {dateObj.getDate()} diff --git a/packages/Calendar/src/Calendar/Month.js b/packages/Calendar/src/Calendar/Month.js new file mode 100644 index 0000000..4b8aa3f --- /dev/null +++ b/packages/Calendar/src/Calendar/Month.js @@ -0,0 +1,104 @@ +// @flow +import React from 'react'; +import { View, StyleSheet, TouchableOpacity, Text } from 'react-native'; +import Week from './Week'; +import MonthSelection from '../MonthSelection'; +import YearSelection from '../YearSelection'; +import { WEEK_DAYS } from '../common/util'; +import { textColor } from '../theme'; + +type Props = { + date: Date, + setView: React.Node => void, +} + + +const styles = StyleSheet.create({ + header: { + flexDirection: 'row', + justifyContent: 'center', + paddingVertical: 5, + }, + dateText: { + fontSize: 14, + color: textColor, + }, + nav: { + paddingHorizontal: 5, + }, + navText: { + fontSize: 14, + }, + daysContainer: { + width: '100%', + flexDirection: 'row', + marginTop: 5, + paddingVertical: 5, + marginBottom: 5, + borderBottomColor: textColor, + borderBottomWidth: StyleSheet.hairlineWidth, + }, + day: { + flex: 1, + alignItems: 'center', + }, + dayText: { + fontSize: 14, + color: textColor, + }, +}); + +const NUM_OF_WEEKS = 6; +const WEEK_DIFF = 7 * 86400 * 1000; + +function getStartOfMonth(date) { + const first = new Date(date.getFullYear(), date.getMonth(), 1); + return first.getTime() - first.getDay() * 86400 * 1000; +} + +function Month({ date, setView, ...other }: Props) { + const start = getStartOfMonth(date); + const dateString = date.toString(); + const month = dateString.substr(4, 3); + const year = dateString.substr(11, 4); + const weeks = new Array(NUM_OF_WEEKS).fill(null).map((c, i) => start + i * WEEK_DIFF); + return ( + <> + + + setView(() => MonthSelection)} + style={styles.nav} + > + {month} + + setView(() => YearSelection)} + style={styles.nav} + > + {year} + + + + + {WEEK_DAYS.map(d => ( + + {d} + + ))} + + + + {weeks.map(week => ( + + ))} + + ); +} + +export default Month; diff --git a/packages/Calendar/src/Week.js b/packages/Calendar/src/Calendar/Week.js similarity index 93% rename from packages/Calendar/src/Week.js rename to packages/Calendar/src/Calendar/Week.js index c793905..8337432 100644 --- a/packages/Calendar/src/Week.js +++ b/packages/Calendar/src/Calendar/Week.js @@ -2,7 +2,7 @@ import React from 'react'; import { View, StyleSheet } from 'react-native'; import Day from './Day'; -import { getDateBorderStyle, DAY_DIFF } from './util'; +import { getDateBorderStyle, DAY_DIFF } from '../common/util'; const styles = StyleSheet.create({ container: { diff --git a/packages/Calendar/src/Calendar/index.js b/packages/Calendar/src/Calendar/index.js new file mode 100644 index 0000000..da70759 --- /dev/null +++ b/packages/Calendar/src/Calendar/index.js @@ -0,0 +1,3 @@ +import CalendarView from './CalendarView'; + +export default CalendarView; diff --git a/packages/Calendar/src/Header.js b/packages/Calendar/src/Header.js deleted file mode 100644 index 6cfc9ff..0000000 --- a/packages/Calendar/src/Header.js +++ /dev/null @@ -1,123 +0,0 @@ -// @flow -import React from 'react'; -import { View, Text, Image, StyleSheet, TouchableWithoutFeedback, TouchableOpacity } from 'react-native'; -import { getTheme } from '@sharingapples/theme'; -import right from './assets/right.png'; -import left from './assets/left.png'; - -const theme = getTheme(); -const calendarTheme = theme.onCalendar || theme; -const textColor = calendarTheme.onBackground; -const primaryColor = calendarTheme.primary; - - -const styles = StyleSheet.create({ - container: { - justifyContent: 'space-between', - flexDirection: 'row', - paddingBottom: 5, - }, - titleContainer: { - flex: 1, - alignItems: 'center', - flexDirection: 'row', - justifyContent: 'center', - }, - dateText: { - fontSize: 16, - color: textColor, - fontWeight: 'bold', - }, - clearTextContainer: { - marginLeft: 15, - }, - clearText: { - fontSize: 14, - color: primaryColor, - }, - dayContainer: { - flexDirection: 'row', - width: '100%', - marginTop: 3, - borderBottomColor: theme.colorDisabled, - paddingBottom: 5, - borderBottomWidth: StyleSheet.hairlineWidth, - marginBottom: 5, - }, - dayText: { - flex: 1, - fontSize: 12, - color: textColor, - textAlign: 'center', - }, - left: { - position: 'absolute', - left: 0, - }, - right: { - position: 'absolute', - right: 0, - }, - icon: { - paddingLeft: 10, - paddingRight: 10, - tintColor: textColor, - }, -}); - -type Props = { - date: {}, - prevMonth: boolean, - nextMonth: boolean, - multiple: boolean, - clearSelection: boolean => void, - shiftMonth: number => void, -} - -const WEEK_DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; - -function Header({ date, prevMonth, nextMonth, multiple, shiftMonth, clearSelection }: Props) { - const dateString = date.toString(); - const month = dateString.substr(4, 3); - const year = dateString.substr(11, 4); - return ( - <> - - - - {month} {year} - - {multiple && ( - clearSelection(null)} - > - Clear Selection - - )} - - {prevMonth && ( - - shiftMonth(-1)}> - - - - )} - - {nextMonth && ( - - shiftMonth(1)}> - - - - )} - - - {WEEK_DAYS.map(d => ( - {d}))} - - - ); -} - -export default React.memo(Header); diff --git a/packages/Calendar/src/Month.js b/packages/Calendar/src/Month.js deleted file mode 100644 index b43f5a2..0000000 --- a/packages/Calendar/src/Month.js +++ /dev/null @@ -1,34 +0,0 @@ -// @flow -import React from 'react'; -import Week from './Week'; - -type Props = { - month: number, - onSelect: (date: Date) => void, - start: number, - selectedDate: Date, -} - -const NUM_OF_WEEKS = 5; -const WEEK_DIFF = 7 * 86400 * 1000; - -function getStartOfMonth(date) { - const first = new Date(date.getFullYear(), date.getMonth(), 1); - return first.getTime() - first.getDay() * 86400 * 1000; -} - - -function Month({ date, ...other }: Props) { - const start = getStartOfMonth(date); - const weeks = new Array(NUM_OF_WEEKS).fill(null).map((c, i) => start + i * WEEK_DIFF); - return weeks.map(week => ( - - )); -} - -export default Month; diff --git a/packages/Calendar/src/MonthSelection/MonthView.js b/packages/Calendar/src/MonthSelection/MonthView.js new file mode 100644 index 0000000..70de2c4 --- /dev/null +++ b/packages/Calendar/src/MonthSelection/MonthView.js @@ -0,0 +1,121 @@ +// @flow +import React, { useState, useCallback } from 'react'; +import { View, StyleSheet, TouchableOpacity, Text } from 'react-native'; + +import { textColor } from '../theme'; +import { ALL_MONTHS } from '../common/util'; +import YearView from '../YearSelection'; +import Header from '../common/Header'; + + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + header: { + width: '100%', + alignItems: 'center', + padding: 5, + }, + dateText: { + fontSize: 14, + color: textColor, + }, + nav: { + padding: 5, + }, + month: { + flex: 1, + paddingVertical: 12, + alignItems: 'center', + }, + row: { + flexDirection: 'row', + }, + navBar: { + width: '100%', + position: 'absolute', + flexDirection: 'row', + justifyContent: 'space-between', + paddingHorizontal: 16, + paddingVertical: 10, + }, +}); + +function renderHeader(year, setView) { + return ( + + setView(() => YearView)} + > + + {year} + + + + ); +} + +function renderAllMonth(year, selectMonth) { + const months = []; + for (let i = 0; i < 3; i += 1) { + const row = [0, 1, 2, 3].map((m) => { + const month = m + 4 * i; + return ( + selectMonth(year, month)} + > + {ALL_MONTHS[month]} + + ); + }); + months.push({row}); + } + return months; +} + +type Props = { + setView: React.Node => void, + value: Date | Array, +} + +function MonthView({ setView, value }: Props) { + const [months, setMonths] = useState(value); + + const shift = useCallback((val) => { + setMonths(mnths => mnths.map(d => new Date( + d.getFullYear() + val, + d.getMonth(), + 1 + ))); + }, []); + + const selectMonth = useCallback((year, month) => { + setMonths(mnths => mnths.map((d, idx) => new Date(year, month + idx, 1))); + setView(() => null); + }, []); + + + function back() { + setView(() => null); + } + + back.title = 'Back'; + + return ( + <> + {months.map((month, idx) => ( + + {renderHeader(month.getFullYear() + idx, setView)} + {renderAllMonth(month.getFullYear() + idx, selectMonth)} + + ))} +
+ + ); +} + +export default MonthView; diff --git a/packages/Calendar/src/MonthSelection/index.js b/packages/Calendar/src/MonthSelection/index.js new file mode 100644 index 0000000..cf01128 --- /dev/null +++ b/packages/Calendar/src/MonthSelection/index.js @@ -0,0 +1,3 @@ +import MonthView from './MonthView'; + +export default MonthView; diff --git a/packages/Calendar/src/YearSelection/YearView.js b/packages/Calendar/src/YearSelection/YearView.js new file mode 100644 index 0000000..41d8082 --- /dev/null +++ b/packages/Calendar/src/YearSelection/YearView.js @@ -0,0 +1,120 @@ +// @flow +import React, { useCallback, useState } from 'react'; +import { View, StyleSheet, TouchableOpacity, Text } from 'react-native'; + +import { textColor } from '../theme'; +import MonthSelection from '../MonthSelection'; +import Header from '../common/Header'; + +const YEARS_DIFF = 20; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + dateText: { + fontSize: 14, + color: textColor, + }, + header: { + alignItems: 'center', + width: '100%', + paddingVertical: 10, + }, + year: { + flex: 1, + paddingVertical: 12, + alignItems: 'center', + }, + row: { + flexDirection: 'row', + }, + nav: { + padding: 5, + }, + navBar: { + width: '100%', + position: 'absolute', + flexDirection: 'row', + justifyContent: 'space-between', + paddingHorizontal: 16, + paddingVertical: 10, + }, +}); + +function renderHeaderTitle() { + return ( + <> + + + Years + + + + ); +} + + +function renderAllYears(initalYear, selectYear) { + const years = []; + for (let i = 0; i < 5; i += 1) { + const yearStart = initalYear + i * 1; + const row = [0, 1, 2, 3].map((y) => { + const year = yearStart + 5 * y; + return ( + selectYear(year)} + > + {year} + + ); + }); + years.push({row}); + } + return years; +} + +type Props = { + setView: React.Node => void, + value: Date | Array, +} + +function YearView({ setView, value }: Props) { + const [months, setMonths] = useState(value); + + const shift = useCallback((val) => { + setMonths(mnths => mnths.map(d => new Date( + d.getFullYear() + val * YEARS_DIFF, + d.getMonth(), + 1 + ))); + }, []); + + const selectYear = useCallback((year) => { + setMonths(mnths => mnths.map((d, idx) => new Date(year + idx, d.getMonth(), 1))); + setView(() => MonthSelection); + }, [setMonths, setView]); + + function back() { + setView(() => null); + } + + back.title = 'Back'; + + + return ( + <> + {months.map((month, idx) => ( + + {renderHeaderTitle()} + {renderAllYears(month.getFullYear() + idx * YEARS_DIFF, selectYear)} + + ))} +
+ + ); +} + +export default YearView; diff --git a/packages/Calendar/src/YearSelection/index.js b/packages/Calendar/src/YearSelection/index.js new file mode 100644 index 0000000..fccb5f1 --- /dev/null +++ b/packages/Calendar/src/YearSelection/index.js @@ -0,0 +1,3 @@ +import YearView from './YearView'; + +export default YearView; diff --git a/packages/Calendar/src/common/Button.js b/packages/Calendar/src/common/Button.js new file mode 100644 index 0000000..6f27cd4 --- /dev/null +++ b/packages/Calendar/src/common/Button.js @@ -0,0 +1,30 @@ +// @flow +import React from 'react'; +import { TouchableOpacity, Text, StyleSheet } from 'react-native'; +import { primaryColor } from '../theme'; + +type Props = { + onPress: number => void, + title: string, +} + +const styles = StyleSheet.create({ + container: { + paddingHorizontal: 5, + marginRight: 10, + }, + text: { + color: primaryColor, + }, +}); + +const Button = ({ onPress, title }: Props) => ( + onPress()} + > + {title} + +); + +export default Button; diff --git a/packages/Calendar/src/common/CalendarContext.js b/packages/Calendar/src/common/CalendarContext.js new file mode 100644 index 0000000..6353dc0 --- /dev/null +++ b/packages/Calendar/src/common/CalendarContext.js @@ -0,0 +1,3 @@ +import React from 'react'; + +export default React.createContext(); diff --git a/packages/Calendar/src/common/Header.js b/packages/Calendar/src/common/Header.js new file mode 100644 index 0000000..714f39e --- /dev/null +++ b/packages/Calendar/src/common/Header.js @@ -0,0 +1,38 @@ +// @flow +import React from 'react'; +import { View, StyleSheet } from 'react-native'; +import IconButton from './IconButton'; +import Button from './Button'; +import left from '../assets/left.png'; +import right from '../assets/right.png'; + +const styles = StyleSheet.create({ + container: { + width: '100%', + position: 'absolute', + flexDirection: 'row', + paddingHorizontal: 16, + paddingVertical: 10, + justifyContent: 'space-between', + }, + row: { + flexDirection: 'row', + }, +}); + +type Props = { + shift: number => void, + action: () => void, +} + +const Header = ({ shift, action }: Props) => ( + + shift(-1)} /> + + {action &&