From b2daeadc15bc772dbe002f7950e7e5ec3b966fad Mon Sep 17 00:00:00 2001 From: Farhoud Shapouran Date: Sun, 12 Nov 2023 10:17:12 +0300 Subject: [PATCH 1/5] refactor: types, functions and components --- src/CalendarContext.tsx | 12 ++-- src/components/Day.tsx | 109 ++++++++++++++++++++++++++++++++ src/components/DaySelector.tsx | 101 +++++------------------------ src/components/YearSelector.tsx | 8 +-- src/types.ts | 8 +++ src/utils.ts | 52 +++++++++------ 6 files changed, 175 insertions(+), 115 deletions(-) create mode 100644 src/components/Day.tsx diff --git a/src/CalendarContext.tsx b/src/CalendarContext.tsx index fd19137..492ede8 100644 --- a/src/CalendarContext.tsx +++ b/src/CalendarContext.tsx @@ -1,11 +1,13 @@ import { createContext, useContext } from 'react'; import { CalendarViews } from './enums'; -import type { DateType, CalendarTheme, CalendarModes } from './types'; +import type { + DateType, + CalendarTheme, + CalendarModes, + CalendarState, +} from './types'; -export interface CalendarContextType { - calendarView: CalendarViews; - selectedDate: DateType; - currentDate: DateType; +export interface CalendarContextType extends CalendarState { mode: CalendarModes; locale: string | ILocale; displayFullDays: boolean; diff --git a/src/components/Day.tsx b/src/components/Day.tsx new file mode 100644 index 0000000..08501e7 --- /dev/null +++ b/src/components/Day.tsx @@ -0,0 +1,109 @@ +import React, { useCallback } from 'react'; +import { View, Text, Pressable, StyleSheet } from 'react-native'; +import { CalendarTheme, IDayObject, DateType } from '../types'; +import { CALENDAR_HEIGHT } from '../enums'; +import { getDate, getFormated } from '../utils'; + +type Props = { + day?: IDayObject; + hour: number; + minute: number; + theme?: CalendarTheme; + isToday: boolean; + selected: boolean; + onSelectDate: (date: DateType) => void; +}; + +const Day = ({ + day, + hour, + minute, + theme, + isToday, + selected, + onSelectDate, +}: Props) => { + const dayContainerStyle = + day && day.isCurrentMonth ? theme?.dayContainerStyle : { opacity: 0.3 }; + + const todayItemStyle = isToday + ? { + borderWidth: 2, + borderColor: theme?.selectedItemColor || '#0047FF', + ...theme?.todayContainerStyle, + } + : null; + + const activeItemStyle = selected + ? { + borderColor: theme?.selectedItemColor || '#0047FF', + backgroundColor: theme?.selectedItemColor || '#0047FF', + } + : null; + + const textStyle = selected + ? { color: '#fff', ...theme?.selectedTextStyle } + : isToday + ? { + ...theme?.calendarTextStyle, + color: theme?.selectedItemColor || '#0047FF', + ...theme?.todayTextStyle, + } + : theme?.calendarTextStyle; + + const handleSelectDate = useCallback( + (date: string) => { + const newDate = getDate(date).hour(hour).minute(minute); + + onSelectDate(getFormated(newDate)); + }, + [onSelectDate, hour, minute] + ); + + return ( + + {day ? ( + handleSelectDate(day.date)} + style={[ + styles.dayContainer, + dayContainerStyle, + todayItemStyle, + activeItemStyle, + day.disabled && styles.disabledDay, + ]} + testID={day.date} + accessibilityRole="button" + > + + {day.text} + + + ) : null} + + ); +}; + +const styles = StyleSheet.create({ + dayCell: { + width: '14.2%', + height: CALENDAR_HEIGHT / 7 - 1, + }, + dayContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + margin: 1.5, + borderRadius: 100, + }, + dayTextContainer: { + justifyContent: 'center', + alignItems: 'center', + }, + disabledDay: { + opacity: 0.3, + }, +}); + +export default React.memo(Day); diff --git a/src/components/DaySelector.tsx b/src/components/DaySelector.tsx index f6274bc..ccb847b 100644 --- a/src/components/DaySelector.tsx +++ b/src/components/DaySelector.tsx @@ -1,16 +1,12 @@ import React, { useMemo } from 'react'; -import { Text, View, Pressable, StyleSheet } from 'react-native'; +import { Text, View, StyleSheet } from 'react-native'; import { useCalendarContext } from '../CalendarContext'; -import { CALENDAR_HEIGHT } from '../enums'; +import Day from './Day'; import { getParsedDate, getMonthDays, - getDate, - getFormated, getWeekdaysMin, - getToday, - getFormatedDate, - dateFormat, + areDatesOnSameDay, } from '../utils'; const DaySelector = () => { @@ -23,6 +19,7 @@ const DaySelector = () => { maximumDate, theme, } = useCalendarContext(); + const today = new Date(); const { year, month, hour, minute } = getParsedDate(currentDate); const days = useMemo( () => { @@ -37,12 +34,6 @@ const DaySelector = () => { [month, year, displayFullDays, minimumDate, maximumDate] ); - const handleSelectDate = (date: string) => { - const newDate = getDate(date).hour(hour).minute(minute); - - onSelectDate(getFormated(newDate)); - }; - return ( { {days?.map((day, index) => { - const dayContainerStyle = - day && day.isCurrentMonth - ? theme?.dayContainerStyle - : { opacity: 0.3 }; - - const todayItemStyle = - day && day.date === getToday() - ? { - borderWidth: 2, - borderColor: theme?.selectedItemColor || '#0047FF', - ...theme?.todayContainerStyle, - } - : null; - - const activeItemStyle = - day && day.date === getFormatedDate(selectedDate, dateFormat) - ? { - borderColor: theme?.selectedItemColor || '#0047FF', - backgroundColor: theme?.selectedItemColor || '#0047FF', - } - : null; - - const textStyle = - day && day.date === getFormatedDate(selectedDate, dateFormat) - ? { color: '#fff', ...theme?.selectedTextStyle } - : day && day.date === getToday() - ? { - ...theme?.calendarTextStyle, - color: theme?.selectedItemColor || '#0047FF', - ...theme?.todayTextStyle, - } - : theme?.calendarTextStyle; - + const isToday = areDatesOnSameDay(day?.date, today); + const selected = areDatesOnSameDay(day?.date, selectedDate); return ( - - {day ? ( - handleSelectDate(day.date)} - style={[ - styles.dayContainer, - dayContainerStyle, - todayItemStyle, - activeItemStyle, - day.disabled && styles.disabledDay, - ]} - testID={day.date} - accessibilityRole="button" - > - - {day.text} - - - ) : null} - + ); })} @@ -148,24 +97,6 @@ const styles = StyleSheet.create({ flexDirection: 'row', alignContent: 'flex-start', }, - dayCell: { - width: '14.2%', - height: CALENDAR_HEIGHT / 7 - 1, - }, - dayContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - margin: 1.5, - borderRadius: 100, - }, - dayTextContainer: { - justifyContent: 'center', - alignItems: 'center', - }, - disabledDay: { - opacity: 0.3, - }, }); export default DaySelector; diff --git a/src/components/YearSelector.tsx b/src/components/YearSelector.tsx index 45c12f8..fad1bd2 100644 --- a/src/components/YearSelector.tsx +++ b/src/components/YearSelector.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { Text, View, Pressable, StyleSheet } from 'react-native'; import { useCalendarContext } from '../CalendarContext'; import { getDateYear } from '../utils'; @@ -8,13 +8,13 @@ const YearSelector = () => { useCalendarContext(); const currentYear = getDateYear(currentDate); const selectedYear = getDateYear(selectedDate); - const rowArray = [1, 2, 3]; const colArray = [1, 2, 3, 4]; let year = 12 * Math.ceil(currentYear / 12) - 12; if (year < 0) year = 0; - function generateColumns() { + const generateColumns = useCallback(() => { + const rowArray = [1, 2, 3]; const column = rowArray.map(() => { const cellYear = year++; const activeItemStyle = @@ -48,7 +48,7 @@ const YearSelector = () => { ); }); return column; - } + }, [onSelectYear, selectedYear, year, theme]); return ( diff --git a/src/types.ts b/src/types.ts index 7569734..3d84426 100644 --- a/src/types.ts +++ b/src/types.ts @@ -46,3 +46,11 @@ export type HeaderProps = { buttonPrevIcon?: ReactNode; buttonNextIcon?: ReactNode; }; + +export interface IDayObject { + text: string; + day: number; + date: string; + disabled: boolean; + isCurrentMonth: boolean; +} diff --git a/src/utils.ts b/src/utils.ts index 7807d9a..85d290c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,13 +1,5 @@ import dayjs from 'dayjs'; -import type { DateType } from './types'; - -export interface IDayObject { - text: string; - day: number; - date: string; - disabled: boolean; - isCurrentMonth: boolean; -} +import type { DateType, IDayObject } from './types'; export const calendarFormat = 'YYYY-MM-DD HH:mm'; export const dateFormat = 'YYYY-MM-DD'; @@ -33,6 +25,20 @@ export const getDateYear = (date: DateType) => dayjs(date).year(); export const getToday = () => dayjs().format(dateFormat); +export const areDatesOnSameDay = (a: DateType, b: DateType) => { + if (!a || !b) { + return false; + } + + const date_a = dayjs(a); + const date_b = dayjs(b); + return ( + date_a.isSame(date_b, 'year') && + date_a.isSame(date_b, 'month') && + date_a.isSame(date_b, 'day') + ); +}; + export const getFormatedDate = (date: DateType, format: string) => dayjs(date).format(format); @@ -40,8 +46,10 @@ export const getDate = (date: DateType) => dayjs(date, calendarFormat); /** * Get detailed date object + * * @param date Get detailed date object - * @returns + * + * @returns parsed date object */ export const getParsedDate = (date: DateType) => { return { @@ -55,11 +63,12 @@ export const getParsedDate = (date: DateType) => { /** * Calculate month days array based on current date * - * @param {DateType} datetime - The current date that selected - * @param {boolean} displayFullDays - * @param {DateType} minimumDate - min selectable date - * @param {DateType} maximumDate - max selectable date - * @returns {IDayObject[]} days array based on current date + * @param datetime - The current date that selected + * @param displayFullDays + * @param minimumDate - min selectable date + * @param maximumDate - max selectable date + * + * @returns days array based on current date */ export const getMonthDays = ( datetime: DateType = dayjs(), @@ -106,12 +115,13 @@ export const getMonthDays = ( /** * Generate day object for displaying inside day cell * - * @param {number} day - number of day - * @param {dayjs.Dayjs} date - calculated date based on day, month, and year - * @param {DateType} minDate - min selectable date - * @param {DateType} maxDate - max selectable date - * @param {boolean} isCurrentMonth - define the day is in the current month - * @returns {IDayObject} days object based on current date + * @param day - number of day + * @param date - calculated date based on day, month, and year + * @param minDate - min selectable date + * @param maxDate - max selectable date + * @param isCurrentMonth - define the day is in the current month + * + * @returns days object based on current date */ const generateDayObject = ( day: number, From dd5c91b868da6c84e6c78864f31d21b1b0c6ace1 Mon Sep 17 00:00:00 2001 From: Farhoud Shapouran Date: Sun, 12 Nov 2023 17:10:28 +0300 Subject: [PATCH 2/5] fix: year range selector based current date --- src/CalendarContext.tsx | 5 +- src/DateTimePicker.tsx | 23 ++++--- src/components/Header.tsx | 108 +++++++++++++++++++------------- src/components/YearSelector.tsx | 66 ++++++++++--------- src/enums.ts | 1 + src/types.ts | 3 +- src/utils.ts | 46 +++++++------- 7 files changed, 149 insertions(+), 103 deletions(-) diff --git a/src/CalendarContext.tsx b/src/CalendarContext.tsx index 492ede8..9bf428b 100644 --- a/src/CalendarContext.tsx +++ b/src/CalendarContext.tsx @@ -24,8 +24,9 @@ export interface CalendarContextType extends CalendarState { const CalendarContext = createContext({ calendarView: CalendarViews.day, - selectedDate: Date.now(), - currentDate: Date.now(), + selectedDate: new Date(), + currentDate: new Date(), + currentYear: new Date().getFullYear(), mode: 'datetime', locale: 'en', minimumDate: null, diff --git a/src/DateTimePicker.tsx b/src/DateTimePicker.tsx index 38f30db..3e48a37 100644 --- a/src/DateTimePicker.tsx +++ b/src/DateTimePicker.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useReducer } from 'react'; -import { getNow, getFormated, getDate } from './utils'; +import { getFormated, getDate, getDateYear } from './utils'; import CalendarContext from './CalendarContext'; import { CalendarViews, CalendarActionKind } from './enums'; import type { @@ -31,7 +31,7 @@ interface PropTypes extends CalendarTheme, HeaderProps { } const DateTimePicker = ({ - value = getNow(), + value = new Date(), mode = 'datetime', locale = 'en', minimumDate = null, @@ -97,6 +97,11 @@ const DateTimePicker = ({ ...prevState, currentDate: action.payload, }; + case CalendarActionKind.CHANGE_CURRENT_YEAR: + return { + ...prevState, + currentYear: action.payload, + }; case CalendarActionKind.CHANGE_SELECTED_DATE: return { ...prevState, @@ -106,8 +111,9 @@ const DateTimePicker = ({ }, { calendarView: mode === 'time' ? CalendarViews.time : CalendarViews.day, - selectedDate: value ? getFormated(value) : getNow(), - currentDate: value ? getFormated(value) : getNow(), + selectedDate: value ? getFormated(value) : new Date(), + currentDate: value ? getFormated(value) : new Date(), + currentYear: value ? getDateYear(value) : new Date().getFullYear(), } ); @@ -120,6 +126,10 @@ const DateTimePicker = ({ type: CalendarActionKind.CHANGE_CURRENT_DATE, payload: value, }); + dispatch({ + type: CalendarActionKind.CHANGE_CURRENT_YEAR, + payload: getDateYear(value), + }); }, [value]); useEffect(() => { @@ -173,10 +183,9 @@ const DateTimePicker = ({ }); }, onChangeYear: (year: number) => { - const newDate = getDate(state.currentDate).add(year, 'year'); dispatch({ - type: CalendarActionKind.CHANGE_CURRENT_DATE, - payload: getFormated(newDate), + type: CalendarActionKind.CHANGE_CURRENT_YEAR, + payload: year, }); }, }; diff --git a/src/components/Header.tsx b/src/components/Header.tsx index cf430bf..076cd9f 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,9 +1,10 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { View, Text, Pressable, StyleSheet, Image } from 'react-native'; import { useCalendarContext } from '../CalendarContext'; import dayjs from 'dayjs'; import { CalendarViews } from '../enums'; import type { HeaderProps } from '../types'; +import { getDateYear, getYearRange, YEAR_PAGE_SIZE } from '../utils'; const arrow_left = require('../assets/images/arrow_left.png'); const arrow_right = require('../assets/images/arrow_right.png'); @@ -12,6 +13,7 @@ const Header = ({ buttonPrevIcon, buttonNextIcon }: HeaderProps) => { const { currentDate, selectedDate, + currentYear, onChangeMonth, onChangeYear, calendarView, @@ -28,8 +30,9 @@ const Header = ({ buttonPrevIcon, buttonNextIcon }: HeaderProps) => { calendarView === CalendarViews.day ? onChangeMonth(-1) : calendarView === CalendarViews.month - ? onChangeYear(-1) - : calendarView === CalendarViews.year && onChangeYear(-12) + ? onChangeYear(currentYear - 1) + : calendarView === CalendarViews.year && + onChangeYear(currentYear - YEAR_PAGE_SIZE) } testID="btn-prev" accessibilityRole="button" @@ -58,8 +61,9 @@ const Header = ({ buttonPrevIcon, buttonNextIcon }: HeaderProps) => { calendarView === CalendarViews.day ? onChangeMonth(1) : calendarView === CalendarViews.month - ? onChangeYear(1) - : calendarView === CalendarViews.year && onChangeYear(12) + ? onChangeYear(currentYear + 1) + : calendarView === CalendarViews.year && + onChangeYear(currentYear + YEAR_PAGE_SIZE) } testID="btn-next" accessibilityRole="button" @@ -81,48 +85,66 @@ const Header = ({ buttonPrevIcon, buttonNextIcon }: HeaderProps) => { ); + const yearSelector = useCallback(() => { + const years = getYearRange(currentYear); + return ( + { + setCalendarView( + calendarView === CalendarViews.year + ? CalendarViews.day + : CalendarViews.year + ); + onChangeYear(getDateYear(currentDate)); + }} + testID="btn-year" + accessibilityRole="button" + > + + + {calendarView === CalendarViews.year + ? `${years.at(0)} - ${years.at(-1)}` + : dayjs(currentDate).format('YYYY')} + + + + ); + }, [ + calendarView, + currentDate, + currentYear, + setCalendarView, + onChangeYear, + theme, + ]); + + const monthSelector = ( + + setCalendarView( + calendarView === CalendarViews.month + ? CalendarViews.day + : CalendarViews.month + ) + } + testID="btn-month" + accessibilityRole="button" + > + + + {dayjs(currentDate).locale(locale).format('MMMM')} + + + + ); + const renderSelectors = ( <> - - setCalendarView( - calendarView === CalendarViews.month - ? CalendarViews.day - : CalendarViews.month - ) - } - testID="btn-month" - accessibilityRole="button" - > - - - {dayjs(currentDate).locale(locale).format('MMMM')} - - - - - - setCalendarView( - calendarView === CalendarViews.year - ? CalendarViews.day - : CalendarViews.year - ) - } - testID="btn-year" - accessibilityRole="button" - > - - - {calendarView === CalendarViews.year - ? dayjs(selectedDate).format('YYYY') - : dayjs(currentDate).format('YYYY')} - - - + {calendarView !== CalendarViews.year ? monthSelector : null} + {yearSelector()} - {mode === 'datetime' ? ( + {mode === 'datetime' && calendarView !== CalendarViews.year ? ( setCalendarView( diff --git a/src/components/YearSelector.tsx b/src/components/YearSelector.tsx index fad1bd2..8d70922 100644 --- a/src/components/YearSelector.tsx +++ b/src/components/YearSelector.tsx @@ -1,62 +1,69 @@ import React, { useCallback } from 'react'; -import { Text, View, Pressable, StyleSheet } from 'react-native'; +import { + Text, + View, + Pressable, + StyleSheet, + TextStyle, + ViewStyle, +} from 'react-native'; import { useCalendarContext } from '../CalendarContext'; -import { getDateYear } from '../utils'; +import { getDateYear, getYearRange } from '../utils'; const YearSelector = () => { - const { currentDate, selectedDate, onSelectYear, theme } = + const { currentDate, currentYear, selectedDate, onSelectYear, theme } = useCalendarContext(); - const currentYear = getDateYear(currentDate); const selectedYear = getDateYear(selectedDate); - const colArray = [1, 2, 3, 4]; - let year = 12 * Math.ceil(currentYear / 12) - 12; - if (year < 0) year = 0; - - const generateColumns = useCallback(() => { - const rowArray = [1, 2, 3]; - const column = rowArray.map(() => { - const cellYear = year++; - const activeItemStyle = - cellYear === selectedYear + const generateCells = useCallback(() => { + const years = getYearRange(currentYear); + const activeYear = getDateYear(currentDate); + const column = years.map((year) => { + const activeItemStyle: ViewStyle = + year === selectedYear ? { borderColor: theme?.selectedItemColor || '#0047FF', backgroundColor: theme?.selectedItemColor || '#0047FF', } - : null; + : year === activeYear + ? { + borderColor: theme?.selectedItemColor || '#0047FF', + } + : {}; - const textStyle = - cellYear === selectedYear + const textStyle: TextStyle = + year === selectedYear ? { color: '#fff', ...theme?.selectedTextStyle } - : theme?.calendarTextStyle; + : year === activeYear + ? { + color: theme?.selectedItemColor || '#0047FF', + fontWeight: 'bold', + } + : { ...theme?.calendarTextStyle }; return ( onSelectYear(cellYear)} + key={year} + onPress={() => onSelectYear(year)} style={styles.yearCell} accessibilityRole="button" > - - {cellYear} + + {year} ); }); return column; - }, [onSelectYear, selectedYear, year, theme]); + }, [onSelectYear, selectedYear, currentYear, currentDate, theme]); return ( - {colArray.map((index) => ( - - {generateColumns()} - - ))} + {generateCells()} ); }; @@ -72,8 +79,9 @@ const styles = StyleSheet.create({ yearCell: { width: '33.3%', }, - yearsRow: { + years: { flexDirection: 'row', + flexWrap: 'wrap', width: '100%', }, year: { diff --git a/src/enums.ts b/src/enums.ts index c2712c7..6a9a8bc 100644 --- a/src/enums.ts +++ b/src/enums.ts @@ -8,6 +8,7 @@ export enum CalendarViews { export enum CalendarActionKind { SET_CALENDAR_VIEW = 'SET_CALENDAR_VIEW', CHANGE_CURRENT_DATE = 'CHANGE_CURRENT_DATE', + CHANGE_CURRENT_YEAR = 'CHANGE_CURRENT_YEAR', CHANGE_SELECTED_DATE = 'CHANGE_SELECTED_DATE', } diff --git a/src/types.ts b/src/types.ts index 3d84426..7783b3f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -12,7 +12,8 @@ export type HeaderButtonPositions = 'around' | 'right' | 'left'; export type CalendarState = { calendarView: CalendarViews; selectedDate: DateType; - currentDate: DateType; + currentDate: DateType; // used for latest state of calendar based on Month and Year + currentYear: number; // used for pagination in YearSelector }; export type CalendarAction = { diff --git a/src/utils.ts b/src/utils.ts index 85d290c..8c1c07a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,8 +1,9 @@ import dayjs from 'dayjs'; import type { DateType, IDayObject } from './types'; -export const calendarFormat = 'YYYY-MM-DD HH:mm'; -export const dateFormat = 'YYYY-MM-DD'; +export const CALENDAR_FORMAT = 'YYYY-MM-DD HH:mm'; +export const DATE_FORMAT = 'YYYY-MM-DD'; +export const YEAR_PAGE_SIZE = 12; export const getMonths = () => dayjs.months(); @@ -15,40 +16,43 @@ export const getWeekdaysShort = () => dayjs.weekdaysShort(); export const getWeekdaysMin = () => dayjs.weekdaysMin(); export const getFormated = (date: DateType) => - dayjs(date).format(calendarFormat); - -export const getNow = () => dayjs().format(calendarFormat); + dayjs(date).format(CALENDAR_FORMAT); export const getDateMonth = (date: DateType) => dayjs(date).month(); export const getDateYear = (date: DateType) => dayjs(date).year(); -export const getToday = () => dayjs().format(dateFormat); +export const getToday = () => dayjs().format(DATE_FORMAT); -export const areDatesOnSameDay = (a: DateType, b: DateType) => { +export function areDatesOnSameDay(a: DateType, b: DateType) { if (!a || !b) { return false; } - const date_a = dayjs(a); - const date_b = dayjs(b); - return ( - date_a.isSame(date_b, 'year') && - date_a.isSame(date_b, 'month') && - date_a.isSame(date_b, 'day') - ); -}; + const date_a = dayjs(a).format(DATE_FORMAT); + const date_b = dayjs(b).format(DATE_FORMAT); + + return date_a === date_b; +} export const getFormatedDate = (date: DateType, format: string) => dayjs(date).format(format); -export const getDate = (date: DateType) => dayjs(date, calendarFormat); +export const getDate = (date: DateType) => dayjs(date, CALENDAR_FORMAT); + +export const getYearRange = (year: number) => { + const endYear = YEAR_PAGE_SIZE * Math.ceil(year / YEAR_PAGE_SIZE); + let startYear = endYear === year ? endYear : endYear - YEAR_PAGE_SIZE; + + if (startYear < 0) startYear = 0; + return Array.from({ length: YEAR_PAGE_SIZE }, (_, i) => startYear + i); +}; /** * Get detailed date object - * + * * @param date Get detailed date object - * + * * @returns parsed date object */ export const getParsedDate = (date: DateType) => { @@ -67,7 +71,7 @@ export const getParsedDate = (date: DateType) => { * @param displayFullDays * @param minimumDate - min selectable date * @param maximumDate - max selectable date - * + * * @returns days array based on current date */ export const getMonthDays = ( @@ -120,7 +124,7 @@ export const getMonthDays = ( * @param minDate - min selectable date * @param maxDate - max selectable date * @param isCurrentMonth - define the day is in the current month - * + * * @returns days object based on current date */ const generateDayObject = ( @@ -140,7 +144,7 @@ const generateDayObject = ( return { text: day.toString(), day: day, - date: getFormatedDate(date, dateFormat), + date: getFormatedDate(date, DATE_FORMAT), disabled, isCurrentMonth, }; From 72822daaf22e4f0a48452b6e10d345c2e3e93980 Mon Sep 17 00:00:00 2001 From: Farhoud Shapouran Date: Sun, 12 Nov 2023 18:17:30 +0300 Subject: [PATCH 3/5] refactor: days grid --- example/app.json | 6 ++++- example/src/App.tsx | 4 +++ src/components/Day.tsx | 31 +++++------------------ src/components/DaySelector.tsx | 42 ++++++++++++++++++++++---------- src/components/Header.tsx | 12 +++++++-- src/components/MonthSelector.tsx | 1 + src/components/YearSelector.tsx | 1 + 7 files changed, 56 insertions(+), 41 deletions(-) diff --git a/example/app.json b/example/app.json index 459d457..8c278d5 100644 --- a/example/app.json +++ b/example/app.json @@ -2,7 +2,9 @@ "expo": { "name": "React Native UI DatePicker", "slug": "react-native-ui-datepicker-example", + "description": "Customizable React Native DateTime Picker component for Android, iOS, and Web. It includes date, time, and datetime modes and supports different locales.", "version": "1.0.0", + "githubUrl": "https://github.com/farhoudshapouran/react-native-ui-datepicker", "orientation": "portrait", "icon": "./assets/icon.png", "userInterfaceStyle": "light", @@ -22,7 +24,9 @@ } }, "web": { - "favicon": "./assets/calendar.png" + "favicon": "./assets/calendar.png", + "themeColor": "#FFFFFF", + "display": "fullscreen" } } } diff --git a/example/src/App.tsx b/example/src/App.tsx index e58e8d5..7143d32 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -58,6 +58,7 @@ export default function App() { ]} onPress={() => setTheme(item)} accessibilityRole="button" + accessibilityLabel="Set Active Theme" /> ))} @@ -73,6 +74,7 @@ export default function App() { ]} onPress={() => setLocale(item)} accessibilityRole="button" + accessibilityLabel={item.toUpperCase()} > void; + onSelectDate: (date: string) => void; }; -const Day = ({ - day, - hour, - minute, - theme, - isToday, - selected, - onSelectDate, -}: Props) => { +const Day = ({ day, theme, isToday, selected, onSelectDate }: Props) => { const dayContainerStyle = day && day.isCurrentMonth ? theme?.dayContainerStyle : { opacity: 0.3 }; @@ -51,21 +40,12 @@ const Day = ({ } : theme?.calendarTextStyle; - const handleSelectDate = useCallback( - (date: string) => { - const newDate = getDate(date).hour(hour).minute(minute); - - onSelectDate(getFormated(newDate)); - }, - [onSelectDate, hour, minute] - ); - return ( {day ? ( handleSelectDate(day.date)} + onPress={() => onSelectDate(day.date)} style={[ styles.dayContainer, dayContainerStyle, @@ -75,6 +55,7 @@ const Day = ({ ]} testID={day.date} accessibilityRole="button" + accessibilityLabel={day.date} > {day.text} diff --git a/src/components/DaySelector.tsx b/src/components/DaySelector.tsx index ccb847b..12350d0 100644 --- a/src/components/DaySelector.tsx +++ b/src/components/DaySelector.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useMemo, useCallback } from 'react'; import { Text, View, StyleSheet } from 'react-native'; import { useCalendarContext } from '../CalendarContext'; import Day from './Day'; @@ -7,6 +7,8 @@ import { getMonthDays, getWeekdaysMin, areDatesOnSameDay, + getDate, + getFormated, } from '../utils'; const DaySelector = () => { @@ -19,19 +21,37 @@ const DaySelector = () => { maximumDate, theme, } = useCalendarContext(); - const today = new Date(); const { year, month, hour, minute } = getParsedDate(currentDate); - const days = useMemo( + + const daysGrid = useMemo( () => { + const today = new Date(); return getMonthDays( currentDate, displayFullDays, minimumDate, maximumDate - ); + ).map((day) => { + const isToday = areDatesOnSameDay(day?.date, today); + const selected = areDatesOnSameDay(day?.date, selectedDate); + return { + ...day, + isToday, + selected, + }; + }); }, // eslint-disable-next-line react-hooks/exhaustive-deps - [month, year, displayFullDays, minimumDate, maximumDate] + [month, year, displayFullDays, minimumDate, maximumDate, selectedDate] + ); + + const handleSelectDate = useCallback( + (date: string) => { + const newDate = getDate(date).hour(hour).minute(minute); + + onSelectDate(getFormated(newDate)); + }, + [onSelectDate, hour, minute] ); return ( @@ -47,19 +67,15 @@ const DaySelector = () => { ))} - {days?.map((day, index) => { - const isToday = areDatesOnSameDay(day?.date, today); - const selected = areDatesOnSameDay(day?.date, selectedDate); + {daysGrid?.map((day, index) => { return ( ); })} diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 076cd9f..9b5e835 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -23,6 +23,9 @@ const Header = ({ buttonPrevIcon, buttonNextIcon }: HeaderProps) => { locale, } = useCalendarContext(); + const currentMonthText = dayjs(currentDate).locale(locale).format('MMMM'); + const currentYearText = dayjs(currentDate).format('YYYY'); + const renderPrevButton = ( { } testID="btn-prev" accessibilityRole="button" + accessibilityLabel="Prev" > { } testID="btn-next" accessibilityRole="button" + accessibilityLabel="Next" > { }} testID="btn-year" accessibilityRole="button" + accessibilityLabel={currentYearText} > {calendarView === CalendarViews.year ? `${years.at(0)} - ${years.at(-1)}` - : dayjs(currentDate).format('YYYY')} + : currentYearText} @@ -129,10 +135,11 @@ const Header = ({ buttonPrevIcon, buttonNextIcon }: HeaderProps) => { } testID="btn-month" accessibilityRole="button" + accessibilityLabel={currentMonthText} > - {dayjs(currentDate).locale(locale).format('MMMM')} + {currentMonthText} @@ -154,6 +161,7 @@ const Header = ({ buttonPrevIcon, buttonNextIcon }: HeaderProps) => { ) } accessibilityRole="button" + accessibilityLabel={dayjs(selectedDate).format('HH:mm')} > diff --git a/src/components/MonthSelector.tsx b/src/components/MonthSelector.tsx index dbf95c4..7d2b6b4 100644 --- a/src/components/MonthSelector.tsx +++ b/src/components/MonthSelector.tsx @@ -30,6 +30,7 @@ const MonthSelector = () => { style={styles.monthCell} onPress={() => onSelectMonth(index)} accessibilityRole="button" + accessibilityLabel={item} > { onPress={() => onSelectYear(year)} style={styles.yearCell} accessibilityRole="button" + accessibilityLabel={year.toString()} > Date: Sun, 12 Nov 2023 18:29:39 +0300 Subject: [PATCH 4/5] fix: default value of DateTimePicker component --- src/DateTimePicker.tsx | 2 +- src/components/Header.tsx | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/DateTimePicker.tsx b/src/DateTimePicker.tsx index 3e48a37..99406a5 100644 --- a/src/DateTimePicker.tsx +++ b/src/DateTimePicker.tsx @@ -31,7 +31,7 @@ interface PropTypes extends CalendarTheme, HeaderProps { } const DateTimePicker = ({ - value = new Date(), + value, mode = 'datetime', locale = 'en', minimumDate = null, diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 9b5e835..b5ccada 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -24,7 +24,6 @@ const Header = ({ buttonPrevIcon, buttonNextIcon }: HeaderProps) => { } = useCalendarContext(); const currentMonthText = dayjs(currentDate).locale(locale).format('MMMM'); - const currentYearText = dayjs(currentDate).format('YYYY'); const renderPrevButton = ( { }} testID="btn-year" accessibilityRole="button" - accessibilityLabel={currentYearText} + accessibilityLabel={dayjs(currentDate).format('YYYY')} > {calendarView === CalendarViews.year ? `${years.at(0)} - ${years.at(-1)}` - : currentYearText} + : dayjs(currentDate).format('YYYY')} From 3f23c30cb90bd377301770c8746ba4ff33d4b817 Mon Sep 17 00:00:00 2001 From: Farhoud Shapouran Date: Sun, 12 Nov 2023 18:32:36 +0300 Subject: [PATCH 5/5] chore: bump version number to 1.0.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c68c8b7..91da603 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-ui-datepicker", - "version": "1.0.8", + "version": "1.0.9", "description": "Customizable datetime picker for React Native", "main": "lib/commonjs/index", "module": "lib/module/index",