diff --git a/README.md b/README.md index f4217d4..74fce8a 100644 --- a/README.md +++ b/README.md @@ -56,18 +56,48 @@ For more, take a look at the `/example` directory. ![react-native-ui-datepicker-styles](https://user-images.githubusercontent.com/7857656/227260476-30ee8c25-f809-4dcf-bccf-cd1ffab8795a.jpg) -## Available props +## Calendar props | Name | Type | Default | Description | | ------------------------ | --------------- | --------------- | -------------------------------------------------------------------------------------- | -| value | `DateType` | `Dayjs` | DatePicker value to display selected date | -| onChange | `Function` | `() => {}` | Called when the new date selected from DatePicker | -| mode | `string` | `'datetime'` | Defines the DatePicker mode `['datetime', 'date', 'time']` | +| mode | `string` | `'single'` | Defines the DatePicker mode `['single', 'range', 'multiple']` | | locale | `string` | `'en'` | Defines the DatePicker locale | | minDate | `DateType` | `null` | Defines DatePicker minimum selectable date | | maxDate | `DateType` | `null` | Defines DatePicker maximum selectable date | | firstDayOfWeek | `number` | `0` | Defines the starting day of week, number 0-6, 0 - Sunday, 6 - Saturday | | displayFullDays | `boolean` | `false` | Defines show previous and next month's days in the current calendar view | + + +## Single Mode props + +| Name | Type | Default | Description | +| ------------ | --------------- | ----------------- | -------------------------------------------------------------- | +| date | `DateType` | `undefined` | Date value to display selected date | +| onChange | `Function` | `({date}) => {}` | Called when the new date selected from DatePicker | +| timePicker | `boolean` | `false` | Defines show or hide time picker | + + +## Range Mode props + +| Name | Type | Default | Description | +| ------------ | --------------- | -------------------------------- | ----------------------------------------------------------------- | +| startDate | `DateType` | `undefined` | Start date value to display selected start date | +| endDate | `DateType` | `undefined` | End date value to display selected end date | +| onChange | `Function` | `({startDate, endDate}) => {}` | Called when the new start or end date selected from DatePicker | + + +## Multiple Mode props + +| Name | Type | Default | Description | +| ------------ | --------------- | ------------------ | -------------------------------------------------------------- | +| dates | `DateType[]` | `[]` | Dates array to display selected dates | +| onChange | `Function` | `({dates}) => {}` | Called when the new dates selected from DatePicker | + + +## Styling props + +| Name | Type | Default | Description | +| ------------------------ | --------------- | --------------- | -------------------------------------------------------------------------------------- | | calendarTextStyle | `TextStyle` | `null` | Defines all text styles inside the calendar (Days, Months, Years, Hours, and Minutes) | | selectedTextStyle | `TextStyle` | `null` | Defines selected (Day, Month, Year) text styles | | selectedItemColor | `string` | `'#0047FF'` | Defines selected (Day, Month, Year) background and border colors | diff --git a/example/src/App.tsx b/example/src/App.tsx index 5aeffcc..4ee85ff 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -3,12 +3,13 @@ import { StyleSheet, View, Text, - Pressable, - Image, - Linking, SafeAreaView, TouchableOpacity, + Pressable, } from 'react-native'; +import ThemeSelector, { ITheme } from './components/ThemeSelector'; +import LocaleSelector from './components/LocaleSelector'; +import GithubLink from './components/GithubLink'; import BouncyCheckbox from 'react-native-bouncy-checkbox'; import DateTimePicker, { DateType, ModeType } from 'react-native-ui-datepicker'; import dayjs from 'dayjs'; @@ -18,11 +19,6 @@ import 'dayjs/locale/es'; import 'dayjs/locale/fr'; import 'dayjs/locale/tr'; -interface ITheme { - mainColor: string; - activeTextColor: string; -} - const Themes: ITheme[] = [ { mainColor: '#0047FF', activeTextColor: '#fff' }, { mainColor: '#00D27A', activeTextColor: '#fff' }, @@ -34,8 +30,6 @@ const Themes: ITheme[] = [ { mainColor: '#FAD7DD', activeTextColor: '#932338' }, ]; -const Locales = ['en', 'de', 'es', 'fr', 'tr']; - export default function App() { const [mode, setMode] = useState('single'); const [timePicker, setTimePicker] = useState(false); @@ -79,60 +73,16 @@ export default function App() { React Native UI DatePicker - - {Themes.map((item, index) => ( - setTheme(item)} - accessibilityRole="button" - accessibilityLabel="Set Active Theme" - /> - ))} - - - - Locale: - - {Locales.map((item, index) => ( - setLocale(item)} - accessibilityRole="button" - accessibilityLabel={item.toUpperCase()} - > - - {item.toUpperCase()} - - - ))} - + + + + + - - - Linking.openURL( - 'https://github.com/farhoudshapouran/react-native-ui-datepicker' - ) - } - accessibilityRole="button" - accessibilityLabel="Check repository on GitHub" - > - - Check repository on GitHub - - + + ); @@ -372,40 +306,6 @@ const styles = StyleSheet.create({ width: '100%', }, title: { fontSize: 18, fontWeight: 'bold' }, - themeContainer: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-around', - marginBottom: 10, - width: 330, - }, - themeButton: { - borderWidth: 4, - width: 32, - height: 32, - borderRadius: 32, - margin: 5, - shadowRadius: 20, - shadowColor: '#000', - shadowOpacity: 0.1, - shadowOffset: { width: 0, height: 0 }, - }, - localeContainer: { - flexDirection: 'row', - alignItems: 'center', - marginBottom: 20, - }, - localeButton: { - alignItems: 'center', - justifyContent: 'center', - width: 36, - height: 36, - borderRadius: 36, - margin: 2, - }, - localeButtonText: { - fontSize: 15, - }, modesContainer: { flexDirection: 'row', alignItems: 'center', @@ -452,18 +352,4 @@ const styles = StyleSheet.create({ todayButtonText: { fontWeight: 'bold', }, - githubContainer: { - paddingVertical: 20, - }, - githubLink: { - flexDirection: 'row', - alignItems: 'center', - }, - githubLogo: { - width: 22, - height: 22, - }, - githubText: { - marginLeft: 8, - }, }); diff --git a/example/src/components/GithubLink.tsx b/example/src/components/GithubLink.tsx new file mode 100644 index 0000000..601711c --- /dev/null +++ b/example/src/components/GithubLink.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { + StyleSheet, + View, + Text, + Pressable, + Image, + Linking, +} from 'react-native'; + +export default function GithubLink() { + return ( + + + Linking.openURL( + 'https://github.com/farhoudshapouran/react-native-ui-datepicker' + ) + } + accessibilityRole="button" + accessibilityLabel="Check repository on GitHub" + > + + Check repository on GitHub + + + ); +} + +const styles = StyleSheet.create({ + githubContainer: { + paddingVertical: 20, + }, + githubLink: { + flexDirection: 'row', + alignItems: 'center', + }, + githubLogo: { + width: 22, + height: 22, + }, + githubText: { + marginLeft: 8, + }, +}); diff --git a/example/src/components/LocaleSelector.tsx b/example/src/components/LocaleSelector.tsx new file mode 100644 index 0000000..8fd7bf7 --- /dev/null +++ b/example/src/components/LocaleSelector.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { StyleSheet, View, Text, Pressable } from 'react-native'; + +const Locales = ['en', 'de', 'es', 'fr', 'tr']; + +type Props = { + locale: string; + setLocale: (locale: string) => void; + mainColor?: string; + activeTextColor?: string; +}; + +export default function LocaleSelector({ + locale, + setLocale, + mainColor, + activeTextColor, +}: Props) { + return ( + + + Locale: + + {Locales.map((item, index) => ( + setLocale(item)} + accessibilityRole="button" + accessibilityLabel={item.toUpperCase()} + > + + {item.toUpperCase()} + + + ))} + + ); +} + +const styles = StyleSheet.create({ + localeContainer: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 20, + }, + localeButton: { + alignItems: 'center', + justifyContent: 'center', + width: 36, + height: 36, + borderRadius: 36, + margin: 2, + }, + localeButtonText: { + fontSize: 15, + }, +}); diff --git a/example/src/components/ThemeSelector.tsx b/example/src/components/ThemeSelector.tsx new file mode 100644 index 0000000..4604a60 --- /dev/null +++ b/example/src/components/ThemeSelector.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { StyleSheet, View, Pressable } from 'react-native'; + +export interface ITheme { + mainColor: string; + activeTextColor: string; +} + +type Props = { + themes: ITheme[]; + setTheme: (theme: ITheme) => void; +}; + +export default function ThemeSelector({ themes = [], setTheme }: Props) { + return ( + + {themes.map((item, index) => ( + setTheme(item)} + accessibilityRole="button" + accessibilityLabel="Set Active Theme" + /> + ))} + + ); +} + +const styles = StyleSheet.create({ + themeContainer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-around', + marginBottom: 10, + width: 330, + }, + themeButton: { + borderWidth: 4, + width: 32, + height: 32, + borderRadius: 32, + margin: 5, + shadowRadius: 20, + shadowColor: '#000', + shadowOpacity: 0.1, + shadowOffset: { width: 0, height: 0 }, + }, +}); diff --git a/src/DateTimePicker.tsx b/src/DateTimePicker.tsx index eb60651..6837b62 100644 --- a/src/DateTimePicker.tsx +++ b/src/DateTimePicker.tsx @@ -62,7 +62,7 @@ const DateTimePicker = ( props: DatePickerSingleProps | DatePickerRangeProps | DatePickeMultipleProps ) => { const { - mode, + mode = 'single', locale = 'en', displayFullDays = false, timePicker = false, diff --git a/src/components/Calendar.tsx b/src/components/Calendar.tsx index 9455909..9075c70 100644 --- a/src/components/Calendar.tsx +++ b/src/components/Calendar.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react'; +import React, { ReactNode, memo } from 'react'; import { View, StyleSheet } from 'react-native'; import { useCalendarContext } from '../CalendarContext'; import type { CalendarViews } from '../enums'; @@ -46,4 +46,4 @@ const styles = StyleSheet.create({ }, }); -export default Calendar; +export default memo(Calendar); diff --git a/src/components/TimePicker/Wheel.tsx b/src/components/TimePicker/Wheel.tsx index 2038252..864c16a 100644 --- a/src/components/TimePicker/Wheel.tsx +++ b/src/components/TimePicker/Wheel.tsx @@ -23,13 +23,13 @@ export interface WheelStyleProps { export interface WheelProps extends WheelStyleProps { value: number; - setValue: (value: number) => void; + setValue?: (value: number) => void; items: number[]; } const Wheel = ({ value, - setValue, + setValue = () => {}, items, containerStyle, textStyle, @@ -41,15 +41,13 @@ const Wheel = ({ }: WheelProps) => { const translateY = useRef(new Animated.Value(0)).current; const renderCount = - displayCount * 2 < items.length - ? displayCount * 4 + 1 - : displayCount * 2 - 1; + displayCount * 2 < items.length ? displayCount * 8 : displayCount * 2 - 1; const circular = items.length >= displayCount; const height = typeof containerStyle?.height === 'number' ? containerStyle.height : 130; const radius = wheelHeight != null ? wheelHeight / 2 : height / 2; - const valueIndex = items.indexOf(value); + const valueIndex = useMemo(() => items.indexOf(value), [items, value]); const panResponder = useMemo(() => { return PanResponder.create({ @@ -102,11 +100,14 @@ const Wheel = ({ return Array.from({ length: renderCount }, (_, index) => { let targetIndex = valueIndex + index - centerIndex; if (targetIndex < 0 || targetIndex >= items.length) { + if (!circular) { + return 0; + } targetIndex = (targetIndex + items.length) % items.length; } return items[targetIndex] || 0; }); - }, [renderCount, valueIndex, items]); + }, [renderCount, valueIndex, items, circular]); const animatedAngles = useMemo(() => { //translateY.setValue(0); @@ -142,9 +143,7 @@ const Wheel = ({ const animatedAngle = animatedAngles[index]; return ( displayValues.length / 2 ? 'Post' : 'Before' - }${displayValue + index}`} + key={`${displayValue}-${index}`} style={[ textStyle, // eslint-disable-next-line react-native/no-inline-styles @@ -202,4 +201,9 @@ const styles = StyleSheet.create({ }, }); -export default memo(Wheel); +export default memo(Wheel, (prevProps, nextProps) => { + return ( + prevProps.value === nextProps.value && + prevProps.setValue === nextProps.setValue + ); +}); diff --git a/src/components/TimeSelector.tsx b/src/components/TimeSelector.tsx index c655b0a..4d77ded 100644 --- a/src/components/TimeSelector.tsx +++ b/src/components/TimeSelector.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { Text, View, StyleSheet } from 'react-native'; import { useCalendarContext } from '../CalendarContext'; import Wheel from './TimePicker/Wheel'; @@ -9,10 +9,29 @@ function createNumberList(num: number) { return new Array(num).fill(0).map((_, index) => index); } +const hours = createNumberList(24); +const minutes = createNumberList(60); + const TimeSelector = () => { - const { date, currentDate, onSelectDate, theme } = useCalendarContext(); + const { date, onSelectDate, theme } = useCalendarContext(); const { hour, minute } = getParsedDate(date); + const handleChangeHour = useCallback( + (value: number) => { + const newDate = getDate(date).hour(value); + onSelectDate(getFormated(newDate)); + }, + [date, onSelectDate] + ); + + const handleChangeMinute = useCallback( + (value: number) => { + const newDate = getDate(date).minute(value); + onSelectDate(getFormated(newDate)); + }, + [date, onSelectDate] + ); + return ( { { - const newDate = getDate(currentDate).hour(value); - onSelectDate(getFormated(newDate)); - }} + setValue={handleChangeHour} /> { { - const newDate = getDate(currentDate).minute(value); - onSelectDate(getFormated(newDate)); - }} + setValue={handleChangeMinute} />